5757
5858PORT_STOPBITS = 1
5959PORT_BYTESIZE = 8
60- PORT_PARITY = None
61- PORT_BAUDRATE = 300 # 9600 # 19200 # 115200 # use slow serial to get some contention
60+ PORT_PARITY = "N"
61+ PORT_BAUDRATE = 19200 # 115200 # use slow serial to get some contention
6262PORT_TIMEOUT = 1.5
6363
6464has_pyserial = False
7373try :
7474 # Configure minimalmodbus to use the specified port serial framing
7575 import minimalmodbus
76- minimalmodbus .STOPBITS = PORT_STOPBITS
77- minimalmodbus .BYTESIZE = PORT_BYTESIZE
78- minimalmodbus .PARITY = PORT_PARITY
79- minimalmodbus .BAUDRATE = PORT_BAUDRATE
80- minimalmodbus .TIMEOUT = PORT_TIMEOUT
81-
8276 has_minimalmodbus = True
8377except ImportError :
8478 logging .warning ( "Failed to import minimalmodbus; skipping some tests" )
@@ -120,18 +114,20 @@ def test_pymodbus_version():
120114 )
121115)
122116def test_pymodbus_rs485_sync ():
123- """Raw pymodbus API to communicate via ttyS0 client --> ttyS{1,2,...} servers. Supports the client
124- and at least one server."""
117+ """Raw pymodbus API to communicate via ttyS0 client --> ttyS{1,2,...} servers. Supported when
118+ the client and at least one server RS-485 port is available.
119+
120+ """
125121 from pymodbus .client import ModbusSerialClient
126122 from pymodbus .framer import FramerType
127123
128124 serial_args = dict (
129125 timeout = .5 ,
130126 # retries=3,
131- baudrate = 19200 ,
132- bytesize = 8 ,
133- parity = "N" ,
134- stopbits = 2 ,
127+ baudrate = PORT_BAUDRATE ,
128+ bytesize = PORT_BYTESIZE ,
129+ parity = PORT_PARITY ,
130+ stopbits = PORT_STOPBITS ,
135131 # handle_local_echo=False,
136132 )
137133
@@ -145,7 +141,7 @@ async def server_start( port, unit ):
145141 slaves = {
146142 unit : ModbusSlaveContext (
147143 di = ModbusSparseDataBlock ({a :v for a ,v in enumerate (range (100 ))}),
148- co = ModbusSparseDataBlock ({a :v % len ( SERVER_ttyS ) for a ,v in enumerate (range (100 ))}),
144+ co = ModbusSparseDataBlock ({a :v % 2 for a ,v in enumerate (range (100 ))}),
149145 hr = ModbusSparseDataBlock ({a :v for a ,v in enumerate (range (100 ))}),
150146 ir = ModbusSparseDataBlock ({a :v for a ,v in enumerate (range (100 ))}),
151147 )
@@ -154,8 +150,8 @@ async def server_start( port, unit ):
154150 logging .warning ( "Starting Modbus Serial server for unit {unit} on {port} w/ {context}" .format (
155151 unit = unit , port = port , context = context ))
156152
157- from pymodbus .server import ModbusSerialServer as modbus_server_rtu
158- # from .remote.pymodbus_fixes import modbus_server_rtu
153+ # from pymodbus.server import ModbusSerialServer as modbus_server_rtu
154+ from .remote .pymodbus_fixes import modbus_server_rtu
159155 server = modbus_server_rtu (
160156 port = port ,
161157 context = context ,
@@ -222,6 +218,8 @@ def reader():
222218 unit = 1 + a % len (SERVER_ttyS )
223219 expect = not bool ( a % 2 )
224220 rr = client .read_coils ( a , count = 1 , slave = unit )
221+ if not rr .isError ():
222+ logging .warning ( "unit {unit} coil {a} == {value!r}" .format ( unit = unit , a = a , value = rr .bits ))
225223 if rr .isError () or rr .bits [0 ] != expect :
226224 logging .warning ( "Expected unit {unit} coil {a} == {expect}, got {val}" .format (
227225 unit = unit , a = a , expect = expect , val = ( rr if rr .isError () else rr .bits [0 ] )))
@@ -257,12 +255,12 @@ def simulated_modbus_rtu( tty ):
257255
258256 """
259257 return start_modbus_simulator (
260- '-vvv ' , '--log' , '.' .join ( [
258+ '-vvvv ' , '--log' , '.' .join ( [
261259 'serial_test' , 'modbus_sim' , 'log' , os .path .basename ( tty )] ),
262260 '--evil' , 'delay:.01-.1' ,
263261 '--address' , tty ,
264- ' 1 - 1000 = 0' ,
265- '40001 - 41000 = 0' ,
262+ ' 1 - 1000 = 1, 0' ,
263+ '40001 - 41000 = 1,2,3,4,5,6,7,8,9, 0' ,
266264 # Configure Modbus/RTU simulator to use specified port serial framing
267265 '--config' , json .dumps ( {
268266 'stopbits' : PORT_STOPBITS ,
@@ -295,18 +293,53 @@ def simulated_modbus_rtu_ttyS2( request ):
295293 or not os .path .exists (PORT_MASTER ) or not os .path .exists ("ttyS1" ),
296294 reason = "Needs SERIAL_TEST and fcntl/O_NONBLOCK and minimalmodbus and pyserial, and ttyS{0,1}" )
297295def test_rs485_basic ( simulated_modbus_rtu_ttyS1 ):
298- """Use MinimalModbus to test RS485 read/write. """
296+ """Use MinimalModbus to test RS485 read/write. The minimalmodbus API doesn't use 1-based Modbus data
297+ addressing, but zero-based Modbus/RTU command addressing."""
299298
300299 command ,address = simulated_modbus_rtu_ttyS1
301300
302301 comm = minimalmodbus .Instrument ( port = PORT_MASTER , slaveaddress = 1 )
302+ comm .serial .timeout = PORT_TIMEOUT
303+ comm .serial .stopbits = PORT_STOPBITS
304+ comm .serial .bytesize = PORT_BYTESIZE
305+ comm .serial .parity = PORT_PARITY
306+ comm .serial .baudrate = PORT_BAUDRATE
307+ comm .serial .timeout = PORT_TIMEOUT
308+
309+ logging .warning ( "{instrument!r}" .format ( instrument = comm ))
303310 comm .debug = True
304- val = comm .read_register ( 1 )
305- assert val == 0
306- comm .write_register ( 1 , 99 )
307- val = comm .read_register ( 1 )
311+ val = comm .read_register ( 0 )
312+ assert val == 1
313+ comm .write_register ( 0 , 99 )
314+ val = comm .read_register ( 0 )
308315 assert val == 99
309- comm .write_register ( 1 , 0 )
316+ comm .write_register ( 0 , 1 )
317+
318+
319+ @pytest .mark .skipif (
320+ 'SERIAL_TEST' not in os .environ or not has_o_nonblock or not has_minimalmodbus or not has_pyserial
321+ or not os .path .exists (PORT_MASTER ) or not os .path .exists ("ttyS1" ),
322+ reason = "Needs SERIAL_TEST and fcntl/O_NONBLOCK and minimalmodbus and pyserial, and ttyS{0,1}" )
323+ def test_rs485_read ( simulated_modbus_rtu_ttyS1 ):
324+ """Use pymodbus to test RS485 read/write to a simulated device. """
325+
326+ command ,address = simulated_modbus_rtu_ttyS1
327+ Defaults .Timeout = PORT_TIMEOUT
328+ client = modbus_client_rtu (
329+ port = PORT_MASTER , stopbits = PORT_STOPBITS , bytesize = PORT_BYTESIZE ,
330+ parity = PORT_PARITY , baudrate = PORT_BAUDRATE ,
331+ )
332+
333+ for a in range ( 10 ):
334+ unit = 1
335+ expect = bool ( a % 2 )
336+ rr = client .read_coils ( a , count = 1 , slave = unit )
337+ if not rr .isError ():
338+ logging .warning ( "unit {unit} coil {a} == {value!r}" .format ( unit = unit , a = a , value = rr .bits ))
339+ assert (not rr .isError ()) and rr .bits [0 ] == expect , \
340+ "Expected unit {unit} coil {a} == {expect}, got {val}" .format (
341+ unit = unit , a = a , expect = expect , val = ( rr if rr .isError () else rr .bits [0 ] ))
342+
310343
311344
312345@pytest .mark .skipif (
0 commit comments