@@ -49,10 +49,62 @@ public class ModbusSerialProtocol extends AbstractProtocol<ModbusSerialAgent, Mo
4949 protected final Map <String , List <BatchReadRequest >> cachedBatches = new ConcurrentHashMap <>(); // Cached batch requests per group
5050 protected final Map <String , ScheduledFuture <?>> batchPollingTasks = new ConcurrentHashMap <>();
5151 private final Object modbusLock = new Object ();
52- private SerialPort serialPort ;
52+ private SerialPortWrapper serialPort ;
5353 private String connectionString ;
5454 private Set <RegisterRange > illegalRegisters ;
55-
55+
56+ // Serial port wrapper interface for testing
57+ public interface SerialPortWrapper {
58+ boolean openPort ();
59+ boolean closePort ();
60+ boolean isOpen ();
61+ int writeBytes (byte [] buffer , long bytesToWrite );
62+ int readBytes (byte [] buffer , long bytesToRead , long offset );
63+ int bytesAvailable ();
64+ }
65+
66+ // Real SerialPort implementation
67+ private static class RealSerialPortWrapper implements SerialPortWrapper {
68+ private final SerialPort port ;
69+
70+ RealSerialPortWrapper (SerialPort port ) {
71+ this .port = port ;
72+ }
73+
74+ @ Override
75+ public boolean openPort () {
76+ return port .openPort ();
77+ }
78+
79+ @ Override
80+ public boolean closePort () {
81+ return port .closePort ();
82+ }
83+
84+ @ Override
85+ public boolean isOpen () {
86+ return port .isOpen ();
87+ }
88+
89+ @ Override
90+ public int writeBytes (byte [] buffer , long bytesToWrite ) {
91+ return port .writeBytes (buffer , (int ) bytesToWrite );
92+ }
93+
94+ @ Override
95+ public int readBytes (byte [] buffer , long bytesToRead , long offset ) {
96+ return port .readBytes (buffer , (int ) bytesToRead , (int ) offset );
97+ }
98+
99+ @ Override
100+ public int bytesAvailable () {
101+ return port .bytesAvailable ();
102+ }
103+ }
104+
105+ // For testing: inject a mock serial port wrapper
106+ public static SerialPortWrapper mockSerialPortForTesting = null ;
107+
56108 public ModbusSerialProtocol (ModbusSerialAgent agent ) {
57109 super (agent );
58110 }
@@ -86,12 +138,19 @@ protected void doStart(Container container) throws Exception {
86138
87139 connectionString = "modbus-rtu://" + portName + "?baud=" + baudRate + "&data=" + dataBits + "&stop=" + stopBits + "&parity=" + parity ;
88140
89- serialPort = SerialPort .getCommPort (portName );
90- serialPort .setBaudRate (baudRate );
91- serialPort .setNumDataBits (dataBits );
92- serialPort .setNumStopBits (stopBits );
93- serialPort .setParity (mapParityToSerialPort (agent .getParityValue ()));
94- serialPort .setComPortTimeouts (SerialPort .TIMEOUT_READ_BLOCKING , 50 , 0 );
141+ // Use mock serial port for testing if available
142+ if (mockSerialPortForTesting != null ) {
143+ serialPort = mockSerialPortForTesting ;
144+ LOG .info ("Using mock serial port for testing" );
145+ } else {
146+ SerialPort sp = SerialPort .getCommPort (portName );
147+ sp .setBaudRate (baudRate );
148+ sp .setNumDataBits (dataBits );
149+ sp .setNumStopBits (stopBits );
150+ sp .setParity (mapParityToSerialPort (agent .getParityValue ()));
151+ sp .setComPortTimeouts (SerialPort .TIMEOUT_READ_BLOCKING , 50 , 0 );
152+ serialPort = new RealSerialPortWrapper (sp );
153+ }
95154
96155 if (serialPort .openPort ()) {
97156 setConnectionStatus (ConnectionStatus .CONNECTED );
@@ -357,6 +416,47 @@ private Object parseModbusResponse(byte[] response, byte functionCode, ModbusAge
357416 buffer .order (ByteOrder .BIG_ENDIAN );
358417 return buffer .getInt ();
359418 }
419+ } else if (byteCount == 8 ) {
420+ // Four registers - could be 64-bit integer or double precision float
421+ byte [] dataBytes = new byte [8 ];
422+ System .arraycopy (response , 3 , dataBytes , 0 , 8 );
423+
424+ if (dataType == ModbusAgentLink .ModbusDataType .LREAL ) {
425+ ByteBuffer buffer = ByteBuffer .wrap (dataBytes );
426+ buffer .order (ByteOrder .BIG_ENDIAN );
427+ double value = buffer .getDouble ();
428+
429+ // Filter out NaN and Infinity values to prevent database issues
430+ if (Double .isNaN (value ) || Double .isInfinite (value )) {
431+ LOG .warning ("Modbus response contains invalid double value (NaN or Infinity), ignoring update" );
432+ return null ;
433+ }
434+
435+ return value ;
436+ } else if (dataType == ModbusAgentLink .ModbusDataType .LINT ) {
437+ // 64-bit signed integer
438+ ByteBuffer buffer = ByteBuffer .wrap (dataBytes );
439+ buffer .order (ByteOrder .BIG_ENDIAN );
440+ return buffer .getLong ();
441+ } else if (dataType == ModbusAgentLink .ModbusDataType .ULINT ) {
442+ // 64-bit unsigned integer - use BigInteger
443+ ByteBuffer buffer = ByteBuffer .wrap (dataBytes );
444+ buffer .order (ByteOrder .BIG_ENDIAN );
445+ long signedValue = buffer .getLong ();
446+
447+ // Convert to unsigned BigInteger
448+ if (signedValue >= 0 ) {
449+ return java .math .BigInteger .valueOf (signedValue );
450+ } else {
451+ // Handle negative as unsigned
452+ return java .math .BigInteger .valueOf (signedValue ).add (java .math .BigInteger .ONE .shiftLeft (64 ));
453+ }
454+ } else {
455+ // Default: treat as 64-bit signed integer
456+ ByteBuffer buffer = ByteBuffer .wrap (dataBytes );
457+ buffer .order (ByteOrder .BIG_ENDIAN );
458+ return buffer .getLong ();
459+ }
360460 }
361461 }
362462
@@ -437,7 +537,7 @@ private boolean writeSingleHoldingRegister(int unitId, int address, Object value
437537 private int readWithTimeout (byte [] buffer , long timeoutMs ) throws InterruptedException {
438538 long startTime = System .currentTimeMillis ();
439539 int totalBytesRead = 0 ;
440-
540+
441541 while (totalBytesRead < buffer .length && (System .currentTimeMillis () - startTime ) < timeoutMs ) {
442542 int available = serialPort .bytesAvailable ();
443543 if (available > 0 ) {
@@ -446,7 +546,7 @@ private int readWithTimeout(byte[] buffer, long timeoutMs) throws InterruptedExc
446546 }
447547 Thread .sleep (5 );
448548 }
449-
549+
450550 return totalBytesRead ;
451551 }
452552
@@ -720,6 +820,46 @@ private Object extractValueFromBatchResponse(byte[] response, int registerOffset
720820 buffer .order (ByteOrder .BIG_ENDIAN );
721821 return buffer .getInt ();
722822 }
823+ } else if (registerCount == 4 ) {
824+ // Four registers - could be 64-bit integer or double precision float
825+ byte [] dataBytes = new byte [8 ];
826+ System .arraycopy (response , byteOffset , dataBytes , 0 , 8 );
827+
828+ if (dataType == ModbusAgentLink .ModbusDataType .LREAL ) {
829+ ByteBuffer buffer = ByteBuffer .wrap (dataBytes );
830+ buffer .order (ByteOrder .BIG_ENDIAN );
831+ double value = buffer .getDouble ();
832+
833+ if (Double .isNaN (value ) || Double .isInfinite (value )) {
834+ LOG .warning ("Batch response contains invalid double value (NaN or Infinity), ignoring" );
835+ return null ;
836+ }
837+
838+ return value ;
839+ } else if (dataType == ModbusAgentLink .ModbusDataType .LINT ) {
840+ // 64-bit signed integer
841+ ByteBuffer buffer = ByteBuffer .wrap (dataBytes );
842+ buffer .order (ByteOrder .BIG_ENDIAN );
843+ return buffer .getLong ();
844+ } else if (dataType == ModbusAgentLink .ModbusDataType .ULINT ) {
845+ // 64-bit unsigned integer - use BigInteger
846+ ByteBuffer buffer = ByteBuffer .wrap (dataBytes );
847+ buffer .order (ByteOrder .BIG_ENDIAN );
848+ long signedValue = buffer .getLong ();
849+
850+ // Convert to unsigned BigInteger
851+ if (signedValue >= 0 ) {
852+ return java .math .BigInteger .valueOf (signedValue );
853+ } else {
854+ // Handle negative as unsigned
855+ return java .math .BigInteger .valueOf (signedValue ).add (java .math .BigInteger .ONE .shiftLeft (64 ));
856+ }
857+ } else {
858+ // Default: treat as 64-bit signed integer
859+ ByteBuffer buffer = ByteBuffer .wrap (dataBytes );
860+ buffer .order (ByteOrder .BIG_ENDIAN );
861+ return buffer .getLong ();
862+ }
723863 }
724864 }
725865 }
@@ -923,12 +1063,19 @@ private void resetAgent() {
9231063 int dataBits = agent .getDataBits ();
9241064 int stopBits = agent .getStopBits ();
9251065
926- serialPort = SerialPort .getCommPort (portName );
927- serialPort .setBaudRate (baudRate );
928- serialPort .setNumDataBits (dataBits );
929- serialPort .setNumStopBits (stopBits );
930- serialPort .setParity (mapParityToSerialPort (agent .getParityValue ()));
931- serialPort .setComPortTimeouts (SerialPort .TIMEOUT_READ_BLOCKING , 50 , 0 );
1066+ // Use mock serial port for testing if available
1067+ if (mockSerialPortForTesting != null ) {
1068+ serialPort = mockSerialPortForTesting ;
1069+ LOG .info ("Using mock serial port for testing (reset)" );
1070+ } else {
1071+ SerialPort sp = SerialPort .getCommPort (portName );
1072+ sp .setBaudRate (baudRate );
1073+ sp .setNumDataBits (dataBits );
1074+ sp .setNumStopBits (stopBits );
1075+ sp .setParity (mapParityToSerialPort (agent .getParityValue ()));
1076+ sp .setComPortTimeouts (SerialPort .TIMEOUT_READ_BLOCKING , 50 , 0 );
1077+ serialPort = new RealSerialPortWrapper (sp );
1078+ }
9321079
9331080 if (!serialPort .openPort ()) {
9341081 setConnectionStatus (ConnectionStatus .ERROR );
0 commit comments