Skip to content

Commit 394bab2

Browse files
committed
Progess toward pymodbus 3.7 support
1 parent 2844dc6 commit 394bab2

16 files changed

+854
-868
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,5 @@ dist/
2424
.vagrant
2525
.pytest_cache/
2626
.cache/
27+
*.aux
28+
*.toc

README.org

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2296,14 +2296,6 @@ val, = via.write( operations )
22962296
input buffer which (in concert with our proper serial read
22972297
=modbus_rtu_read=) allows us to implement correct Modbus/RTU framing.
22982298

2299-
**** =modbus_sparse_data_block=
2300-
2301-
The provided =ModbusSparseDataBlock= incorrectly deduces the base address,
2302-
and is wildly inefficient for large data blocks. We correctly deduce the
2303-
base register address. The provided =.validate= method is O(N+V) for data
2304-
blocks of size N when validating V registers; we provide an O(V)
2305-
implementation.
2306-
23072299
* Deterministic Finite Automata
23082300

23092301
A cpppo.dfa will consume symbols from its source iterable, and yield
@@ -2327,6 +2319,9 @@ val, = via.write( operations )
23272319
+-------+
23282320
#+END_SRC
23292321

2322+
#+RESULTS:
2323+
[[file:abplus.png]]
2324+
23302325
This machine is easily created like this:
23312326

23322327
#+LATEX: {\scriptsize
@@ -2364,6 +2359,9 @@ val, = via.write( operations )
23642359
+----------------------------------------+
23652360
#+END_SRC
23662361

2362+
#+RESULTS:
2363+
[[file:abplus_csv.png]]
2364+
23672365
This is implemented:
23682366

23692367
#+LATEX: {\scriptsize
@@ -2407,6 +2405,9 @@ val, = via.write( operations )
24072405
None None None None
24082406
#+END_SRC
24092407

2408+
#+RESULTS:
2409+
[[file:abplus_regex.png]]
2410+
24102411
The =True= transition out of each state ensures that the =cpppo.state=
24112412
machine will yield a None (non-transition) when encountering an invalid
24122413
symbol in the language described by the regular expression grammar. Only if

README.pdf

13.3 KB
Binary file not shown.

README.txt

Lines changed: 735 additions & 744 deletions
Large diffs are not rendered by default.

abplus.png

3.07 KB
Loading

abplus_csv.png

7.44 KB
Loading

abplus_regex.png

6.14 KB
Loading

bin/modbus_sim.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,8 @@
8989
# import the various server implementations
9090
#---------------------------------------------------------------------------#
9191
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
92-
from pymodbus.pdu.register_read_message import ReadRegistersResponseBase
93-
from pymodbus.pdu.register_write_message import WriteSingleRegisterResponse, WriteMultipleRegistersResponse
94-
from pymodbus.framer import FramerType, FRAMER_NAME_TO_CLASS
92+
from pymodbus.pdu.register_message import ReadHoldingRegistersResponse, WriteSingleRegisterResponse, WriteMultipleRegistersResponse
93+
from pymodbus.framer import FRAMER_NAME_TO_CLASS, FramerType
9594
from pymodbus.exceptions import NotImplementedException
9695
from pymodbus.datastore.store import ModbusSparseDataBlock
9796

@@ -109,9 +108,7 @@
109108
sys.path.insert( 0, os.path.dirname( os.path.dirname( os.path.dirname( os.path.abspath( __file__ )))))
110109
import cpppo
111110

112-
from cpppo.remote.pymodbus_fixes import (
113-
modbus_sparse_data_block, modbus_server_rtu_printing, modbus_server_tcp_printing, Defaults
114-
)
111+
from cpppo.remote.pymodbus_fixes import modbus_server_rtu_printing, modbus_server_tcp_printing, Defaults
115112

116113

117114
#---------------------------------------------------------------------------#
@@ -190,7 +187,7 @@ def register_decode( txt, default=None ):
190187

191188
def register_definitions( registers, default=None ):
192189
"""Parse the register ranges, as: registers[, registers ...], and produce a keywords dictionary
193-
suitable for construction of modbus_sparse_data_block instances for a ModbusSlaveContext, for
190+
suitable for construction of ModbusSparseDataBlock instances for a ModbusSlaveContext, for
194191
'hr' (Holding Registers), 'co' (Coils), etc.:
195192
196193
40001=999
@@ -336,6 +333,13 @@ class StartAsyncServer( object ):
336333
:param address: An optional (interface, port) to bind to.
337334
:param slaves: An optional single (or list of) Slave IDs to serve
338335
336+
Assumes that the self.server_async asyncio program will identify failure
337+
conditions (ie. cannot connect), and signal itself to stop. The
338+
modbus_server_... implementations do this by monitoring the success of
339+
.listen/.connect and schedule themselves an async .shutdown on failure.
340+
341+
Otherwise, they print the successfully bound i'face:port or serial device.
342+
339343
'''
340344
def __init__( self, *args, registers=None, slaves=None, **kwds ):
341345
global context
@@ -459,7 +463,8 @@ def main( argv=None ):
459463
starter_kwds = {}
460464
try:
461465
# See if it's an <interface>[:<port>]. If it has '/' in it, assume its a device
462-
assert '/' not in address
466+
assert '/' not in args.address and not os.path.exists( args.address ), \
467+
"appears to be a file: {address}".format( address=args.address )
463468
starter = StartTcpServerLogging
464469
framer = FramerType.SOCKET
465470
address = cpppo.parse_ip_port( args.address, default=(None,Defaults.Port) )
@@ -494,7 +499,6 @@ def main( argv=None ):
494499
assert args.range == 1, \
495500
"A range of serial ports is unsupported"
496501

497-
logging.info( "Modbus Framer: {framer}".format( framer=framer ))
498502
framer = FRAMER_NAME_TO_CLASS[framer]
499503

500504
#---------------------------------------------------------------------------#
@@ -536,7 +540,7 @@ def buildFrame(self, message):
536540
delay = self.delay
537541
if isinstance( delay, (list,tuple) ):
538542
delay = random.uniform( *delay )
539-
time.sleep( delay )
543+
time.sleep( delay ) # blocks the entire asyncio program! No other I/O occurs.
540544

541545
return packet
542546

@@ -577,7 +581,7 @@ def buildFrame(self, message):
577581
packet = super( EvilFramerCorruptResponse, self ).buildFrame( message )
578582
message.transaction_id ^= 0xFFFF
579583
elif self.what == "registers":
580-
if isinstance( message, ReadRegistersResponseBase ):
584+
if isinstance( message, ReadHoldingRegistersResponse ):
581585
# These have '.registers' attribute, which is a list.
582586
# Add/remove some
583587
saveregs = message.registers

misc.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -764,14 +764,17 @@ def network( a ):
764764
return ip_network( unicode( a ))
765765

766766
def parse_ip_port( netloc, default=(None,None) ):
767-
"""Parse an <interface>[:<port>] with the supplied defaults, returning <host>,<port>. A Truthy host
768-
portion is required (ie. non-empty); port is optional. Returns ip as an ip_address (if
769-
possible), otherwise as a str; either form can be converted to str, if desired.
767+
"""Parse an <interface>[:<port>] with the supplied defaults, returning <host>,<port|None>. A
768+
Truthy host portion is required (ie. non-empty); port is optional. Returns ip as an ip_address
769+
(if possible), otherwise as a str; either form can be converted to str, if desired.
770770
771771
"""
772772
try:
773-
# "('hostname', port)" tuple
774-
addr,port = ast.literal_eval( netloc )
773+
# A literal "('hostname', port)" tuple or an actual tuple pair
774+
if isinstance( netloc, type_str_base ):
775+
addr,port = ast.literal_eval( netloc )
776+
else:
777+
addr,port = netloc
775778
assert isinstance( addr, type_str_base ) and isinstance( port, (int, type(None)) )
776779
try:
777780
addr = ip( addr.hostname )
@@ -787,7 +790,7 @@ def parse_ip_port( netloc, default=(None,None) ):
787790
try:
788791
parsed = urlparse( '//{}'.format( netloc ))
789792
addr = parsed.hostname
790-
port = parsed.port
793+
port = parsed.port # will be None or int
791794
try:
792795
addr = ip( parsed.hostname )
793796
except:

modbus_test.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
from .tools.waits import waitfor
3333

3434
RTU_WAIT = 2.0 # How long to wait for the simulator
35-
RTU_LATENCY = 0.05 # poll for command-line I/O response
35+
RTU_LATENCY = 0.05 # poll for command-line I/O response
3636

3737
class nonblocking_command( object ):
3838
"""Set up a non-blocking command producing output. Read the output using:
@@ -186,8 +186,8 @@ def start_simulator( simulator, *options, **kwds ):
186186

187187
assert control.address, "Failed to harvest Simulator IP address"
188188

189-
logging.normal( "Simulator started after %7.3fs on %r:%d",
190-
misc.timer() - begun, control.address[0], control.address[1] )
189+
logging.normal( "Simulator started after %7.3fs on %s",
190+
misc.timer() - begun, ':'.join( map( repr, control.address )))
191191
return command,control.address
192192

193193

@@ -196,7 +196,7 @@ def start_modbus_simulator( *options ):
196196
here.
197197
198198
"""
199-
return start_simulator(
199+
return start_simulator(
200200
os.path.join( os.path.dirname( os.path.abspath( __file__ )), 'bin', 'modbus_sim.py' ),
201201
*options
202202
)
@@ -213,12 +213,12 @@ def run_plc_modbus_polls( plc ):
213213
wfkw = dict( timeout=timeout, intervals=intervals )
214214

215215
plc.poll( 40001, rate=rate )
216-
216+
217217
success,elapsed = waitfor( lambda: plc.read( 40001 ) is not None, "40001 polled", **wfkw )
218218
assert success
219219
assert elapsed < 1.0
220220
assert plc.read( 40001 ) == 0
221-
221+
222222
assert plc.read( 1 ) == None
223223
assert plc.read( 40002 ) == None
224224
success,elapsed = waitfor( lambda: plc.read( 40002 ) is not None, "40002 polled", **wfkw )
@@ -234,11 +234,11 @@ def run_plc_modbus_polls( plc ):
234234
# number of distinct poll ranges will increase, and then decrease as we in-fill and the
235235
# inter-register range drops below the merge reach 10, allowing the polling to merge ranges.
236236
# Thus, keep track of the number of registers added, and allow
237-
#
238-
# avg.
237+
#
238+
# avg.
239239
# poll
240240
# time
241-
#
241+
#
242242
# |
243243
# |
244244
# 4s| ..
@@ -252,7 +252,7 @@ def run_plc_modbus_polls( plc ):
252252
# need to more than double the Nyquist-rate timeout
253253
wfkw['timeout'] *= 2.5
254254
wfkw['intervals'] *= 2.5
255-
255+
256256
regs = {}
257257
extent = 100 # how many each of coil/holding registers
258258
total = extent*2 # total registers in play

0 commit comments

Comments
 (0)