diff --git a/core/src/drivers/plugins/python/modbus_master/modbus_master_connection.py b/core/src/drivers/plugins/python/modbus_master/modbus_master_connection.py index e9c81a5d..f1ba0ccb 100644 --- a/core/src/drivers/plugins/python/modbus_master/modbus_master_connection.py +++ b/core/src/drivers/plugins/python/modbus_master/modbus_master_connection.py @@ -9,10 +9,11 @@ class ModbusConnectionManager: # pylint: disable=too-many-instance-attributes """Manages Modbus TCP connections with retry logic.""" - def __init__(self, host: str, port: int, timeout_ms: int): + def __init__(self, host: str, port: int, timeout_ms: int, slave_id: int = 1): self.host = host self.port = port self.timeout = timeout_ms / 1000.0 # Convert to seconds + self.slave_id = slave_id # Unit/Slave ID for Modbus TCP gateways # Retry configuration self.retry_delay_base = 2.0 # initial delay between attempts (seconds) diff --git a/core/src/drivers/plugins/python/modbus_master/modbus_master_plugin.py b/core/src/drivers/plugins/python/modbus_master/modbus_master_plugin.py index b494b70e..ab66a566 100644 --- a/core/src/drivers/plugins/python/modbus_master/modbus_master_plugin.py +++ b/core/src/drivers/plugins/python/modbus_master/modbus_master_plugin.py @@ -74,7 +74,7 @@ def __init__(self, device_config: Any, sba: SafeBufferAccess, plugin_logger: Plu self.logger = plugin_logger self._stop_event = threading.Event() self.connection_manager = ModbusConnectionManager( - device_config.host, device_config.port, device_config.timeout_ms + device_config.host, device_config.port, device_config.timeout_ms, device_config.slave_id ) self.name = f"ModbusSlave-{device_config.name}-{device_config.host}:{device_config.port}" @@ -149,22 +149,23 @@ def run(self): # pylint: disable=too-many-locals count = point.length # 1:1 mapping for boolean operations # Perform Modbus read based on function code - # Note: pymodbus 3.x requires count as keyword argument + # Note: pymodbus 3.10+ uses device_id parameter (formerly slave) + # device_id is used for Modbus TCP gateways that forward to RS485 devices if point.fc == 1: # Read Coils response = self.connection_manager.client.read_coils( - address, count=count + address, count=count, device_id=self.connection_manager.slave_id ) elif point.fc == 2: # Read Discrete Inputs response = self.connection_manager.client.read_discrete_inputs( - address, count=count + address, count=count, device_id=self.connection_manager.slave_id ) elif point.fc == 3: # Read Holding Registers response = self.connection_manager.client.read_holding_registers( - address, count=count + address, count=count, device_id=self.connection_manager.slave_id ) elif point.fc == 4: # Read Input Registers response = self.connection_manager.client.read_input_registers( - address, count=count + address, count=count, device_id=self.connection_manager.slave_id ) else: self.logger.warn(f"[{self.name}] Unsupported read FC: {point.fc}") @@ -327,10 +328,12 @@ def run(self): # pylint: disable=too-many-locals continue # Perform Modbus write operation + # Note: pymodbus 3.10+ uses device_id parameter (formerly slave) + # device_id is used for Modbus TCP gateways that forward to RS485 devices if point.fc == 5: # Write Single Coil if len(values_to_write) > 0: response = self.connection_manager.client.write_coil( - address, values_to_write[0] + address, values_to_write[0], device_id=self.connection_manager.slave_id ) else: self.logger.error( @@ -341,7 +344,7 @@ def run(self): # pylint: disable=too-many-locals elif point.fc == 6: # Write Single Register if len(values_to_write) > 0: response = self.connection_manager.client.write_register( - address, values_to_write[0] + address, values_to_write[0], device_id=self.connection_manager.slave_id ) else: self.logger.error( @@ -351,11 +354,11 @@ def run(self): # pylint: disable=too-many-locals continue elif point.fc == 15: # Write Multiple Coils response = self.connection_manager.client.write_coils( - address, values_to_write + address, values_to_write, device_id=self.connection_manager.slave_id ) elif point.fc == 16: # Write Multiple Registers response = self.connection_manager.client.write_registers( - address, values_to_write + address, values_to_write, device_id=self.connection_manager.slave_id ) else: self.logger.warn(f"[{self.name}] Unsupported write FC: {point.fc}") diff --git a/core/src/drivers/plugins/python/shared/plugin_config_decode/modbus_master_config_model.py b/core/src/drivers/plugins/python/shared/plugin_config_decode/modbus_master_config_model.py index e848b1a6..b8c63587 100644 --- a/core/src/drivers/plugins/python/shared/plugin_config_decode/modbus_master_config_model.py +++ b/core/src/drivers/plugins/python/shared/plugin_config_decode/modbus_master_config_model.py @@ -73,6 +73,7 @@ def __init__(self): self.host: str = "127.0.0.1" self.port: int = 502 self.timeout_ms: int = 1000 + self.slave_id: int = 1 # Unit/Slave ID for Modbus TCP gateways (0-255) self.io_points: List['ModbusIoPointConfig'] = [] @classmethod @@ -89,6 +90,7 @@ def from_dict(cls, data: Dict[str, Any]) -> 'ModbusDeviceConfig': device.host = config.get("host", "127.0.0.1") device.port = config.get("port", 502) device.timeout_ms = config.get("timeout_ms", 1000) + device.slave_id = config.get("slave_id", 1) # Parse I/O points io_points_data = config.get("io_points", []) @@ -110,6 +112,8 @@ def validate(self) -> None: raise ValueError(f"Invalid port: {self.port}. Must be a positive integer for device {self.name}.") if not isinstance(self.timeout_ms, int) or self.timeout_ms <= 0: raise ValueError(f"Invalid timeout_ms: {self.timeout_ms}. Must be a positive integer for device {self.name}.") + if not isinstance(self.slave_id, int) or not (0 <= self.slave_id <= 255): + raise ValueError(f"Invalid slave_id: {self.slave_id}. Must be an integer between 0 and 255 for device {self.name}.") for i, point in enumerate(self.io_points): if not isinstance(point, ModbusIoPointConfig): @@ -126,7 +130,7 @@ def validate(self) -> None: raise ValueError(f"Invalid cycle_time_ms: {point.cycle_time_ms}. Must be a positive integer for device {self.name}, point {i}.") def __repr__(self) -> str: - return f"ModbusDeviceConfig(name='{self.name}', host='{self.host}', port={self.port}, io_points={len(self.io_points)})" + return f"ModbusDeviceConfig(name='{self.name}', host='{self.host}', port={self.port}, slave_id={self.slave_id}, io_points={len(self.io_points)})" class ModbusMasterConfig(PluginConfigContract): """