Repository: edgexfoundry/device-modbus-go (go)

Issue:
TCP Broken pipe on disconnect / reconnect
<!--🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅

Hello there! 😄

To expedite issue processing please search open and closed issues before submitting a new one.
Existing issues often contain information about workarounds, resolution, or progress updates.

Please do not remove sections of this template when submitting your issue.  If the section is not relevant to your issue, leave the section but enter "n/a" for not applicable.

Note some sections are marked "REQUIRED" and must be provided.  Never mark required sections "n/a".

🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅-->


# 🐞 Bug Report

### Affected Services [**REQUIRED**]
The issue is located in:  *device-modbus-go*


### Is this a regression?

Kinda.

The code at v3.1 used to open and close connection for each read request, which would give problems at high freqnency polls for certain devices. The current fix of using a client map keeps a persistent connection. Now in case the connection ever drops even temporarily, read/write requests fails until the container is restarted.

### Description and Minimal Reproduction [**REQUIRED**]

Connect any modbus device over TCP and start polling such as through autoevents. Disconnect and reconnect the device to the network.

## 🔥 Exception or Error
<pre><code>
level=ERROR ts=2025-01-15T11:51:55.458149687Z app=device-modbus source=executor.go:55 msg="AutoEvent - error occurs when reading resource DI8: error reading Regex DeviceResource(s) DI8 for DVC_1 -> write tcp 172.18.0.10:55972->XXX.XXX.XXX.XXX:502: write: broken pipe"
2025/01/15 17:21:55 modbus: sending 0b 32 00 00 00 06 03 02 00 0d 00 01
level=INFO ts=2025-01-15T11:51:55.459155047Z app=device-modbus source=driver.go:137 msg="Read command failed. Cmd:DI13 err:write tcp 172.18.0.10:55972->XXX.XXX.XXX.XXX:502: write: broken pipe \n"
level=ERROR ts=2025-01-15T11:51:55.459204454Z app=device-modbus source=executor.go:55 msg="AutoEvent - error occurs when reading resource DI13: error reading Regex DeviceResource(s) DI13 for DVC_1 -> write tcp 172.18.0.10:55972->XXX.XXX.XXX.XXX:502: write: broken pipe"
2025/01/15 17:21:55 modbus: sending 0b 33 00 00 00 06 03 02 00 0c 00 01
level=INFO ts=2025-01-15T11:51:55.459254348Z app=device-modbus source=driver.go:137 msg="Read command failed. Cmd:DI12 err:write tcp 172.18.0.10:55972->XXX.XXX.XXX.XXX:502: write: broken pipe \n"
level=ERROR ts=2025-01-15T11:51:55.459301749Z app=device-modbus source=executor.go:55 msg="AutoEvent - error occurs when reading resource DI12: error reading Regex DeviceResource(s) DI12 for DVC_1 -> write tcp 172.18.0.10:55972->XXX.XXX.XXX.XXX:502: write: broken pipe"
2025/01/15 17:21:55 modbus: sending 0b 34 00 00 00 06 03 02 00 09 00 01
level=INFO ts=2025-01-15T11:51:55.459347214Z app=device-modbus source=driver.go:137 msg="Read command failed. Cmd:DI9 err:write tcp 172.18.0.10:55972->XXX.XXX.XXX.XXX:502: write: broken pipe \n"
</code></pre>


## 🌍  Your Environment
**Deployment Environment:**

**EdgeX Version [**REQUIRED**]:**
Latest code at commit [2f075c7](https://github.com/edgexfoundry/device-modbus-go/commit/2f075c7931a58d709b8688ebd481c390ae67f934)

**Anything else relevant?**



Pull request:
fix: Remove client when command failed
if command failed, remove the Modbus client from the cache to allow re-establish connection next time

fixes #606

<!-- Expected Commit Message Description (imported automatically by GitHub) -->
<!-- Must conform to [conventional commits guidelines](https://github.com/edgexfoundry/device-modbus-go/blob/main/.github/Contributing.md) -->
<!-- Expected Commit message must contain Closes/Fixes #IssueNumber statement when there is a related issue -->

<!-- Add additional detailed description of need for change if no related issue -->

**If your build fails** due to your commit message not passing the build checks, please review the guidelines here: https://github.com/edgexfoundry/device-modbus-go/blob/main/.github/Contributing.md

## PR Checklist
Please check if your PR fulfills the following requirements:

- [x] I am not introducing a breaking change (if you are, flag in conventional commit message with `BREAKING CHANGE:` describing the break)
- [x] I am not introducing a new dependency (add notes below if you are)
- [ ] I have added unit tests for the new feature or bug fix (if not, why?) not impact
- [x] I have fully tested (add details below) this the new feature or bug fix (if not, why?)
- [ ] I have opened a PR for the related docs change (if not, why?) not impact
  <link to docs PR>

## Testing Instructions
<!-- How can the reviewers test your change? -->
- Deploy core services, device-modubs, modbus sim
- Test command, restart modbus-sim, and test command again. 

## New Dependency Instructions (If applicable)
<!-- Please follow [vetting instructions](https://wiki.edgexfoundry.org/display/FA/Vetting+Process+for+3rd+Party+Dependencies) and place results here -->

Patch:
diff --git a/go.mod b/go.mod
index efff5db..e173495 100644
--- a/go.mod
+++ b/go.mod
@@ -3,8 +3,8 @@ module github.com/edgexfoundry/device-modbus-go
 go 1.23
 
 require (
-	github.com/edgexfoundry/device-sdk-go/v4 v4.0.0-dev.25
-	github.com/edgexfoundry/go-mod-core-contracts/v4 v4.0.0-dev.28
+	github.com/edgexfoundry/device-sdk-go/v4 v4.0.0-dev.24
+	github.com/edgexfoundry/go-mod-core-contracts/v4 v4.0.0-dev.26
 	github.com/goburrow/modbus v0.1.0
 	github.com/spf13/cast v1.7.1
 	github.com/stretchr/testify v1.10.0
@@ -17,11 +17,11 @@ require (
 	github.com/cenkalti/backoff/v4 v4.3.0 // indirect
 	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
 	github.com/eclipse/paho.mqtt.golang v1.5.0 // indirect
-	github.com/edgexfoundry/go-mod-bootstrap/v4 v4.0.0-dev.28 // indirect
+	github.com/edgexfoundry/go-mod-bootstrap/v4 v4.0.0-dev.27 // indirect
 	github.com/edgexfoundry/go-mod-configuration/v4 v4.0.0-dev.14 // indirect
 	github.com/edgexfoundry/go-mod-messaging/v4 v4.0.0-dev.18 // indirect
 	github.com/edgexfoundry/go-mod-registry/v4 v4.0.0-dev.4 // indirect
-	github.com/edgexfoundry/go-mod-secrets/v4 v4.0.0-dev.10 // indirect
+	github.com/edgexfoundry/go-mod-secrets/v4 v4.0.0-dev.9 // indirect
 	github.com/fsnotify/fsnotify v1.7.0 // indirect
 	github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa // indirect
 	github.com/fxamacker/cbor/v2 v2.7.0 // indirect
diff --git a/go.sum b/go.sum
index 5996713..7e2ef80 100644
--- a/go.sum
+++ b/go.sum
@@ -70,20 +70,20 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/eclipse/paho.mqtt.golang v1.5.0 h1:EH+bUVJNgttidWFkLLVKaQPGmkTUfQQqjOsyvMGvD6o=
 github.com/eclipse/paho.mqtt.golang v1.5.0/go.mod h1:du/2qNQVqJf/Sqs4MEL77kR8QTqANF7XU7Fk0aOTAgk=
-github.com/edgexfoundry/device-sdk-go/v4 v4.0.0-dev.25 h1:h31adyPJ+S3YjJcATdKDg3SwE+bLnVQkCzJhDsdv9d0=
-github.com/edgexfoundry/device-sdk-go/v4 v4.0.0-dev.25/go.mod h1:E7fxPSDDLuAnQk1I74m+QBZ/QXay+777x8rJxtKm7Zs=
-github.com/edgexfoundry/go-mod-bootstrap/v4 v4.0.0-dev.28 h1:DmwT+FZlKms2wKdqBw8wAdn4PoMZT5R7TCxAxfyovfg=
-github.com/edgexfoundry/go-mod-bootstrap/v4 v4.0.0-dev.28/go.mod h1:W22AZ7peC6DLN8QKdDhEFaiiL9NFQfk2Hslp/IySqdU=
+github.com/edgexfoundry/device-sdk-go/v4 v4.0.0-dev.24 h1:ltPV5vnpBps6fuojykuUu+slaZnAtOM7mqNRJsOlD90=
+github.com/edgexfoundry/device-sdk-go/v4 v4.0.0-dev.24/go.mod h1:Qlh9FD6LCUrsp3x2b0et0LnMPYGzvXXul9OT0OimeJk=
+github.com/edgexfoundry/go-mod-bootstrap/v4 v4.0.0-dev.27 h1:d1JiYIoFHMHkCeHoYTx4tfRrpPJjVmFAYDUiBfqCOHs=
+github.com/edgexfoundry/go-mod-bootstrap/v4 v4.0.0-dev.27/go.mod h1:JY9Def/1rzes+aBwmI3ge9z+gnPyJdWTP+o+76IrBkg=
 github.com/edgexfoundry/go-mod-configuration/v4 v4.0.0-dev.14 h1:A2LI3YVXGqZS0myTiBrNMFIqIWbH0rNNmLsDPHeI1RM=
 github.com/edgexfoundry/go-mod-configuration/v4 v4.0.0-dev.14/go.mod h1:Sj4PTuZJI5bjQvFh8rz0U1egEu+jkEnlWoHXXUYjgXs=
-github.com/edgexfoundry/go-mod-core-contracts/v4 v4.0.0-dev.28 h1:eli2uheGD7d5qMDFPzNhm3fAEyoDLTgqKWQoDacaF8A=
-github.com/edgexfoundry/go-mod-core-contracts/v4 v4.0.0-dev.28/go.mod h1:D35HIMZkFFy82shKtPYaEL3Nn+ZNEjUjZI1RLn1j23E=
+github.com/edgexfoundry/go-mod-core-contracts/v4 v4.0.0-dev.26 h1:sLzjJnQDukzo6+tRn0coHH+vIMK+L8NL53TWiy5MGbs=
+github.com/edgexfoundry/go-mod-core-contracts/v4 v4.0.0-dev.26/go.mod h1:D35HIMZkFFy82shKtPYaEL3Nn+ZNEjUjZI1RLn1j23E=
 github.com/edgexfoundry/go-mod-messaging/v4 v4.0.0-dev.18 h1:gTaPm8iwdVO0vi8TLuNRYaeNno46Nm6+S04OpubBLlk=
 github.com/edgexfoundry/go-mod-messaging/v4 v4.0.0-dev.18/go.mod h1:h7it/lhC7QhcLKW+KA+D9mCd8Ng3GxT8kSegZMrdEFM=
 github.com/edgexfoundry/go-mod-registry/v4 v4.0.0-dev.4 h1:/IZtXURVZXSKaxHEWVSYHXrNJqoVk2n6OcUR+GmPbgw=
 github.com/edgexfoundry/go-mod-registry/v4 v4.0.0-dev.4/go.mod h1:S5wQ050kYTbZebHqdCC48ojiRghSfnzNLnJLo+FWZH0=
-github.com/edgexfoundry/go-mod-secrets/v4 v4.0.0-dev.10 h1:TU9OBsMeroavoC6Sej8FTTs1scjBhjEHdKrJ5iGHYl8=
-github.com/edgexfoundry/go-mod-secrets/v4 v4.0.0-dev.10/go.mod h1:7L7xJTUayyswdvZNI2GkFZKq7Q33aIn3YyqjBB3L3TI=
+github.com/edgexfoundry/go-mod-secrets/v4 v4.0.0-dev.9 h1:sDiUztFcBnXZ/MeTMj6MRW0vHuI9UY69r4LavedFKRg=
+github.com/edgexfoundry/go-mod-secrets/v4 v4.0.0-dev.9/go.mod h1:SIsFdwhloJEcw4iPuyjidYEoRle4lmerPmTM2IA7fhc=
 github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
 github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
diff --git a/internal/driver/config.go b/internal/driver/config.go
index 204fb04..8716f71 100644
--- a/internal/driver/config.go
+++ b/internal/driver/config.go
@@ -33,9 +33,9 @@ type ConnectionInfo struct {
 
 func (info *ConnectionInfo) String() string {
 	if info.Protocol == ProtocolTCP {
-		return fmt.Sprintf("%s:%s:%d:%d", info.Protocol, info.Address, info.Port, info.UnitID)
+		return fmt.Sprintf("%s:%s:%d", info.Protocol, info.Address, info.Port)
 	}
-	return fmt.Sprintf("%s:%s:%d:%d:%d:%d:%d:%s", info.Protocol, info.Address, info.Port, info.UnitID, info.BaudRate, info.DataBits, info.StopBits, info.Parity)
+	return fmt.Sprintf("%s:%s:%d:%d:%d:%d:%s", info.Protocol, info.Address, info.Port, info.BaudRate, info.DataBits, info.StopBits, info.Parity)
 }
 
 func createConnectionInfo(protocols map[string]models.ProtocolProperties) (info *ConnectionInfo, err error) {
diff --git a/internal/driver/driver.go b/internal/driver/driver.go
index aab27df..0a49a15 100644
--- a/internal/driver/driver.go
+++ b/internal/driver/driver.go
@@ -51,6 +51,13 @@ func (d *Driver) createDeviceClient(info *ConnectionInfo) (DeviceClient, error)
 	return c, nil
 }
 
+func (d *Driver) removeDeviceClient(info *ConnectionInfo) {
+	d.clientMutex.Lock()
+	defer d.clientMutex.Unlock()
+	key := info.String()
+	delete(d.clientMap, key)
+}
+
 func (d *Driver) DisconnectDevice(deviceName string, protocols map[string]models.ProtocolProperties) error {
 	d.Logger.Warn("Driver's DisconnectDevice function didn't implement")
 	return nil
@@ -134,7 +141,8 @@ func (d *Driver) HandleReadCommands(deviceName string, protocols map[string]mode
 	for i, req := range reqs {
 		res, err := handleReadCommandRequest(deviceClient, req)
 		if err != nil {
-			driver.Logger.Errorf("Read command failed. Cmd:%v err:%v \n", req.DeviceResourceName, err)
+			driver.Logger.Errorf("Read command failed, remove the Modbus client from the cache to allow re-establish connection next time. Cmd: %v err:%v", req.DeviceResourceName, err)
+			d.removeDeviceClient(connectionInfo)
 			return responses, err
 		}
 
@@ -193,7 +201,8 @@ func (d *Driver) HandleWriteCommands(deviceName string, protocols map[string]mod
 	for i, req := range reqs {
 		err = handleWriteCommandRequest(deviceClient, req, params[i])
 		if err != nil {
-			d.Logger.Error(err.Error())
+			driver.Logger.Errorf("Write command failed, remove the Modbus client from the cache to allow re-establish connection next time. Cmd: %v err:%v", req.DeviceResourceName, err)
+			d.removeDeviceClient(connectionInfo)
 			break
 		}
 	}


Test patch:
diff --git a/internal/driver/driver_test.go b/internal/driver/driver_test.go
index ca0ae95..7b60b11 100644
--- a/internal/driver/driver_test.go
+++ b/internal/driver/driver_test.go
@@ -75,7 +75,7 @@ func TestDriver_createDeviceClient(t *testing.T) {
 				addressMap:          make(map[string]chan bool),
 				workingAddressCount: make(map[string]int),
 				clientMap: map[string]DeviceClient{
-					"modbus-tcp:172.0.0.1:502:1": &ModbusClient{},
+					"modbus-tcp:172.0.0.1:502": &ModbusClient{},
 				},
 			},
 			args: args{
@@ -102,7 +102,7 @@ func TestDriver_createDeviceClient(t *testing.T) {
 				addressMap:          make(map[string]chan bool),
 				workingAddressCount: make(map[string]int),
 				clientMap: map[string]DeviceClient{
-					"modbus-rtu:172.0.0.1:502:1:9600:8:1:N": &ModbusClient{},
+					"modbus-rtu:172.0.0.1:502:9600:8:1:N": &ModbusClient{},
 				},
 			},
 			args: args{