From ac89d72aa9a78ff707ba7b69d1f1089462413b75 Mon Sep 17 00:00:00 2001 From: openhands Date: Sat, 1 Nov 2025 19:52:58 +0000 Subject: [PATCH] fix: Update protocol clients to fix security policy mismatch and register address issues - OPC UA client: Explicitly set security policy to 'None' to match server - OPC UA client: Fix connection handling to check if connection succeeded - Modbus client: Update register addresses to match server configuration - Modbus client: Add proper connection success checking --- src/dashboard/protocol_clients.py | 87 ++++++++++++++++++++++++------- 1 file changed, 67 insertions(+), 20 deletions(-) diff --git a/src/dashboard/protocol_clients.py b/src/dashboard/protocol_clients.py index 1b01a94..e264569 100644 --- a/src/dashboard/protocol_clients.py +++ b/src/dashboard/protocol_clients.py @@ -24,6 +24,12 @@ class OPCUAClient: try: from asyncua import Client self._client = Client(url=self.endpoint) + + # Explicitly set security policy to match server configuration + # The server supports both Basic256Sha256 and None security policies + # Use None for development/testing + self._client.set_security_string("http://opcfoundation.org/UA/SecurityPolicy#None") + # Set timeout for connection await asyncio.wait_for(self._client.connect(), timeout=5.0) logger.info("opcua_client_connected", endpoint=self.endpoint) @@ -46,7 +52,9 @@ class OPCUAClient: """Read value from OPC UA node.""" try: if not self._client: - await self.connect() + connected = await self.connect() + if not connected: + return None node = self._client.get_node(node_id) value = await asyncio.wait_for(node.read_value(), timeout=3.0) @@ -62,7 +70,9 @@ class OPCUAClient: """Get all data for a specific pump.""" try: if not self._client: - await self.connect() + connected = await self.connect() + if not connected: + return {} # Define node IDs for this pump nodes = { @@ -120,7 +130,9 @@ class ModbusClient: """Read holding register(s).""" try: if not self._client or not self._client.is_socket_open(): - self.connect() + connected = self.connect() + if not connected: + return None # Set timeout for the read operation if hasattr(self._client, 'timeout'): @@ -146,7 +158,9 @@ class ModbusClient: """Read input register(s).""" try: if not self._client or not self._client.is_socket_open(): - self.connect() + connected = self.connect() + if not connected: + return None # Set timeout for the read operation if hasattr(self._client, 'timeout'): @@ -171,22 +185,32 @@ class ModbusClient: def get_pump_registers(self, pump_num: int) -> Dict[str, Any]: """Get all register data for a specific pump.""" try: - # Calculate base register for this pump - base_register = 40000 + (pump_num * 10) + # Calculate register addresses based on server configuration + # Server uses: SETPOINT_BASE=0, STATUS_BASE=100, SAFETY_BASE=200, PERFORMANCE_BASE=400 + # Each pump gets 10 registers in each block + pump_offset = (pump_num - 1) * 10 - # Read holding registers (setpoints) - setpoint = self.read_holding_register(base_register + 1, 1) + # Read holding registers (setpoints) - base address 0 + setpoint = self.read_holding_register(pump_offset, 1) - # Read input registers (status values) - actual_speed = self.read_input_register(base_register + 2, 1) - power = self.read_input_register(base_register + 3, 1) - temperature = self.read_input_register(base_register + 4, 1) + # Read input registers (status values) - base address 100 + actual_speed = self.read_input_register(100 + pump_offset, 1) + power = self.read_input_register(100 + pump_offset + 1, 1) + flow_rate = self.read_input_register(100 + pump_offset + 2, 1) + + # Read safety status - base address 200 + safety_status = self.read_input_register(200 + pump_offset, 1) + + # Read performance metrics - base address 400 + efficiency = self.read_input_register(400 + pump_offset, 1) return { "setpoint": setpoint[0] if setpoint else None, "actual_speed": actual_speed[0] if actual_speed else None, "power": power[0] if power else None, - "temperature": temperature[0] if temperature else None + "flow_rate": flow_rate[0] if flow_rate else None, + "safety_status": safety_status[0] if safety_status else None, + "efficiency": efficiency[0] if efficiency else None } except Exception as e: logger.error("failed_to_get_pump_registers", pump_num=pump_num, error=str(e)) @@ -279,13 +303,18 @@ class ProtocolDataCollector: setpoint = modbus_data.get('setpoint', 0) or 0 actual_speed = modbus_data.get('actual_speed', 0) or 0 power = modbus_data.get('power', 0) or 0 - temperature = modbus_data.get('temperature', 0) or 0 + flow_rate = modbus_data.get('flow_rate', 0) or 0 + safety_status = modbus_data.get('safety_status', 0) or 0 + efficiency = modbus_data.get('efficiency', 0) or 0 + + # Calculate pump offset for address display + pump_offset = (pump_num - 1) * 10 signals.extend([ { "name": f"Station_{station_id}_Pump_{pump_id}_Setpoint", "protocol": "modbus", - "address": f"{40000 + (pump_num * 10) + 1}", + "address": f"Holding Register {pump_offset}", "data_type": "Integer", "current_value": f"{setpoint} Hz (x10)", "quality": "Good", @@ -294,7 +323,7 @@ class ProtocolDataCollector: { "name": f"Station_{station_id}_Pump_{pump_id}_ActualSpeed", "protocol": "modbus", - "address": f"{40000 + (pump_num * 10) + 2}", + "address": f"Input Register {100 + pump_offset}", "data_type": "Integer", "current_value": f"{actual_speed} Hz (x10)", "quality": "Good", @@ -303,18 +332,36 @@ class ProtocolDataCollector: { "name": f"Station_{station_id}_Pump_{pump_id}_Power", "protocol": "modbus", - "address": f"{40000 + (pump_num * 10) + 3}", + "address": f"Input Register {100 + pump_offset + 1}", "data_type": "Integer", "current_value": f"{power} kW (x10)", "quality": "Good", "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S") }, { - "name": f"Station_{station_id}_Pump_{pump_id}_Temperature", + "name": f"Station_{station_id}_Pump_{pump_id}_FlowRate", "protocol": "modbus", - "address": f"{40000 + (pump_num * 10) + 4}", + "address": f"Input Register {100 + pump_offset + 2}", "data_type": "Integer", - "current_value": f"{temperature} °C", + "current_value": f"{flow_rate} m³/h", + "quality": "Good", + "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S") + }, + { + "name": f"Station_{station_id}_Pump_{pump_id}_SafetyStatus", + "protocol": "modbus", + "address": f"Input Register {200 + pump_offset}", + "data_type": "Integer", + "current_value": f"{safety_status}", + "quality": "Good", + "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S") + }, + { + "name": f"Station_{station_id}_Pump_{pump_id}_Efficiency", + "protocol": "modbus", + "address": f"Input Register {400 + pump_offset}", + "data_type": "Integer", + "current_value": f"{efficiency} %", "quality": "Good", "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S") }