pymscada 0.0.15__py3-none-any.whl → 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pymscada might be problematic. Click here for more details.

@@ -84,4 +84,450 @@ pages:
84
84
  - name: Files
85
85
  parent: Dropdown
86
86
  items:
87
- - {tagname: __files__, type: files}
87
+ - {tagname: __files__, type: files}
88
+ - name: Temperature
89
+ parent: Weather
90
+ items:
91
+ - type: uplot # Do all times in seconds, which uplot uses.
92
+ ms:
93
+ desc: Temperature
94
+ age: 172800
95
+ legend_pos: left
96
+ time_pos: left
97
+ time_res: m
98
+ axes:
99
+ - scale: x
100
+ range: [-604800, 86400] # 86400 172800 1209600
101
+ - scale: 'C'
102
+ range: [0.0, 35.0]
103
+ dp: 1
104
+ series:
105
+ - tagname: temperature
106
+ label: Current Temperature
107
+ scale: 'C'
108
+ color: black
109
+ width: 2
110
+ dp: 1
111
+ - tagname: temperature_01
112
+ label: 1h Temperature
113
+ scale: 'C'
114
+ color: darkgray
115
+ width: 1.5
116
+ dp: 1
117
+ - tagname: temperature_04
118
+ label: 4h Temperature
119
+ scale: 'C'
120
+ color: green
121
+ width: 1
122
+ dp: 1
123
+ - tagname: temperature_12
124
+ label: 12h Temperature
125
+ scale: 'C'
126
+ color: orange
127
+ width: 0.75
128
+ dp: 1
129
+ - tagname: temperature_24
130
+ label: 24h Temperature
131
+ scale: 'C'
132
+ color: red
133
+ width: 0.5
134
+ dp: 1
135
+ - name: Wind Speed
136
+ parent: Weather
137
+ items:
138
+ - type: uplot # Do all times in seconds, which uplot uses.
139
+ ms:
140
+ desc: Wind Speed
141
+ age: 172800
142
+ legend_pos: left
143
+ time_pos: left
144
+ time_res: m
145
+ axes:
146
+ - scale: x
147
+ range: [-604800, 86400] # 86400 172800 1209600
148
+ - scale: 'm/s'
149
+ range: [0.0, 20.0]
150
+ dp: 1
151
+ series:
152
+ - tagname: windSpeed
153
+ label: Current Wind Speed
154
+ scale: 'm/s'
155
+ color: black
156
+ width: 2
157
+ dp: 1
158
+ - tagname: windSpeed_01
159
+ label: 1h Wind Speed
160
+ scale: 'm/s'
161
+ color: darkgray
162
+ width: 1.5
163
+ dp: 1
164
+ - tagname: windSpeed_04
165
+ label: 4h Wind Speed
166
+ scale: 'm/s'
167
+ color: green
168
+ width: 1
169
+ dp: 1
170
+ - tagname: windSpeed_12
171
+ label: 12h Wind Speed
172
+ scale: 'm/s'
173
+ color: orange
174
+ width: 0.75
175
+ dp: 1
176
+ - tagname: windSpeed_24
177
+ label: 24h Wind Speed
178
+ scale: 'm/s'
179
+ color: red
180
+ width: 0.5
181
+ dp: 1
182
+ - name: Wind Direction
183
+ parent: Weather
184
+ items:
185
+ - type: uplot # Do all times in seconds, which uplot uses.
186
+ ms:
187
+ desc: Wind Direction
188
+ age: 172800
189
+ legend_pos: left
190
+ time_pos: left
191
+ time_res: m
192
+ axes:
193
+ - scale: x
194
+ range: [-604800, 86400] # 86400 172800 1209600
195
+ - scale: 'deg'
196
+ range: [0.0, 360.0]
197
+ dp: 1
198
+ series:
199
+ - tagname: windDirection
200
+ label: Current Wind Direction
201
+ scale: 'deg'
202
+ color: black
203
+ width: 2
204
+ dp: 1
205
+ - tagname: windDirection_01
206
+ label: 1h Wind Direction
207
+ scale: 'deg'
208
+ color: darkgray
209
+ width: 1.5
210
+ dp: 1
211
+ - tagname: windDirection_04
212
+ label: 4h Wind Direction
213
+ scale: 'deg'
214
+ color: green
215
+ width: 1
216
+ dp: 1
217
+ - tagname: windDirection_12
218
+ label: 12h Wind Direction
219
+ scale: 'deg'
220
+ color: orange
221
+ width: 0.75
222
+ dp: 1
223
+ - tagname: windDirection_24
224
+ label: 24h Wind Direction
225
+ scale: 'deg'
226
+ color: red
227
+ width: 0.5
228
+ dp: 1
229
+ - name: Rain Accumulation
230
+ parent: Weather
231
+ items:
232
+ - type: uplot # Do all times in seconds, which uplot uses.
233
+ ms:
234
+ desc: Rain Accumulation
235
+ age: 172800
236
+ legend_pos: left
237
+ time_pos: left
238
+ time_res: m
239
+ axes:
240
+ - scale: x
241
+ range: [-604800, 86400] # 86400 172800 1209600
242
+ - scale: 'mm'
243
+ range: [0.0, 10.0]
244
+ dp: 1
245
+ series:
246
+ - tagname: rainAccumulation
247
+ label: Current Rain Accumulation
248
+ scale: 'mm'
249
+ color: black
250
+ width: 2
251
+ dp: 1
252
+ - tagname: rainAccumulation_01
253
+ label: 1h Rain Accumulation
254
+ scale: 'mm'
255
+ color: darkgray
256
+ width: 1.5
257
+ dp: 1
258
+ - tagname: rainAccumulation_04
259
+ label: 4h Rain Accumulation
260
+ scale: 'mm'
261
+ color: green
262
+ width: 1
263
+ dp: 1
264
+ - tagname: rainAccumulation_12
265
+ label: 12h Rain Accumulation
266
+ scale: 'mm'
267
+ color: orange
268
+ width: 0.75
269
+ dp: 1
270
+ - tagname: rainAccumulation_24
271
+ label: 24h Rain Accumulation
272
+ scale: 'mm'
273
+ color: red
274
+ width: 0.5
275
+ dp: 1
276
+ - name: Humidity
277
+ parent: Weather
278
+ items:
279
+ - type: uplot # Do all times in seconds, which uplot uses.
280
+ ms:
281
+ desc: Humidity
282
+ age: 172800
283
+ legend_pos: left
284
+ time_pos: left
285
+ time_res: m
286
+ axes:
287
+ - scale: x
288
+ range: [-604800, 86400] # 86400 172800 1209600
289
+ - scale: '%'
290
+ range: [0.0, 100.0]
291
+ dp: 1
292
+ series:
293
+ - tagname: humidity
294
+ label: Current Humidity
295
+ scale: '%'
296
+ color: black
297
+ width: 2
298
+ dp: 1
299
+ - tagname: humidity_01
300
+ label: 1h Humidity
301
+ scale: '%'
302
+ color: darkgray
303
+ width: 1.5
304
+ dp: 1
305
+ - tagname: humidity_04
306
+ label: 4h Humidity
307
+ scale: '%'
308
+ color: green
309
+ width: 1
310
+ dp: 1
311
+ - tagname: humidity_12
312
+ label: 12h Humidity
313
+ scale: '%'
314
+ color: orange
315
+ width: 0.75
316
+ dp: 1
317
+ - tagname: humidity_24
318
+ label: 24h Humidity
319
+ scale: '%'
320
+ color: red
321
+ width: 0.5
322
+ dp: 1
323
+ - name: Values
324
+ parent: Weather
325
+ items:
326
+ - {tagname: temperature, type: value}
327
+ - {tagname: temperature_01, type: value}
328
+ - {tagname: temperature_04, type: value}
329
+ - {tagname: temperature_12, type: value}
330
+ - {tagname: temperature_24, type: value}
331
+ - {tagname: windSpeed, type: value}
332
+ - {tagname: windSpeed_01, type: value}
333
+ - {tagname: windSpeed_04, type: value}
334
+ - {tagname: windSpeed_12, type: value}
335
+ - {tagname: windSpeed_24, type: value}
336
+ - {tagname: windDirection, type: value}
337
+ - {tagname: windDirection_01, type: value}
338
+ - {tagname: windDirection_04, type: value}
339
+ - {tagname: windDirection_12, type: value}
340
+ - {tagname: windDirection_24, type: value}
341
+ - {tagname: rainAccumulation, type: value}
342
+ - {tagname: rainAccumulation_01, type: value}
343
+ - {tagname: rainAccumulation_04, type: value}
344
+ - {tagname: rainAccumulation_12, type: value}
345
+ - {tagname: rainAccumulation_24, type: value}
346
+ - {tagname: humidity, type: value}
347
+ - {tagname: humidity_01, type: value}
348
+ - {tagname: humidity_04, type: value}
349
+ - {tagname: humidity_12, type: value}
350
+ - {tagname: humidity_24, type: value}
351
+ - name: Logix
352
+ items:
353
+ - {tagname: Ani_Fin_20, type: setpoint}
354
+ - {tagname: Ani_Fout_20, type: value}
355
+ - {tagname: Ani_Iin_20, type: setpoint}
356
+ - {tagname: Ani_Iout_20, type: value}
357
+ - {tagname: InVar, type: setpoint}
358
+ - {tagname: OutVar, type: value}
359
+ - {tagname: Ani_Iin_21_0, type: setpoint}
360
+ - {tagname: Ani_Iout_21_0, type: value}
361
+ - {tagname: Ani_Iin_21_1, type: setpoint}
362
+ - {tagname: Ani_Iout_21_1, type: value}
363
+ - name: Values
364
+ parent: SNMP
365
+ items:
366
+ - {tagname: Router_eth1_bytes_in, type: value}
367
+ - {tagname: Router_eth1_bytes_out, type: value}
368
+ - {tagname: Router_eth2_bytes_in, type: value}
369
+ - {tagname: Router_eth2_bytes_out, type: value}
370
+ - {tagname: Router_eth3_bytes_in, type: value}
371
+ - {tagname: Router_eth3_bytes_out, type: value}
372
+ - {tagname: Router_eth4_bytes_in, type: value}
373
+ - {tagname: Router_eth4_bytes_out, type: value}
374
+ - {tagname: Router_eth5_bytes_in, type: value}
375
+ - {tagname: Router_eth5_bytes_out, type: value}
376
+ - {tagname: Router_eth6_bytes_in, type: value}
377
+ - {tagname: Router_eth6_bytes_out, type: value}
378
+ - {tagname: Router_eth7_bytes_in, type: value}
379
+ - {tagname: Router_eth7_bytes_out, type: value}
380
+ - {tagname: Router_eth8_bytes_in, type: value}
381
+ - {tagname: Router_eth8_bytes_out, type: value}
382
+ - name: Trend
383
+ parent: SNMP
384
+ items:
385
+ - type: uplot # Do all times in seconds, which uplot uses.
386
+ ms:
387
+ desc: Bytes
388
+ age: 172800
389
+ legend_pos: left
390
+ time_pos: left
391
+ time_res: m
392
+ axes:
393
+ - scale: x
394
+ range: [-86400, 0]
395
+ - scale: 'bytes'
396
+ range: [0, 100000]
397
+ dp: 0
398
+ series:
399
+ - tagname: Router_eth1_bytes_in
400
+ label: eth1 in
401
+ scale: 'bytes'
402
+ color: violet
403
+ width: 1
404
+ dp: 0
405
+ - tagname: Router_eth1_bytes_out
406
+ label: eth1 out
407
+ scale: 'bytes'
408
+ color: violet
409
+ width: 0.5
410
+ dp: 0
411
+ - tagname: Router_eth2_bytes_in
412
+ label: eth2 in
413
+ scale: 'bytes'
414
+ color: blue
415
+ width: 1
416
+ dp: 0
417
+ - tagname: Router_eth2_bytes_out
418
+ label: eth2 out
419
+ scale: 'bytes'
420
+ color: blue
421
+ width: 0.5
422
+ dp: 0
423
+ - tagname: Router_eth3_bytes_in
424
+ label: eth3 in
425
+ scale: 'bytes'
426
+ color: green
427
+ width: 1
428
+ dp: 0
429
+ - tagname: Router_eth3_bytes_out
430
+ label: eth3 out
431
+ scale: 'bytes'
432
+ color: green
433
+ width: 0.5
434
+ dp: 0
435
+ - tagname: Router_eth4_bytes_in
436
+ label: eth4 in
437
+ scale: 'bytes'
438
+ color: gray
439
+ width: 1
440
+ dp: 0
441
+ - tagname: Router_eth4_bytes_out
442
+ label: eth4 out
443
+ scale: 'bytes'
444
+ color: gray
445
+ width: 0.5
446
+ dp: 0
447
+ - tagname: Router_eth5_bytes_in
448
+ label: eth5 in
449
+ scale: 'bytes'
450
+ color: goldenrod
451
+ width: 1
452
+ dp: 0
453
+ - tagname: Router_eth5_bytes_out
454
+ label: eth5 out
455
+ scale: 'bytes'
456
+ color: goldenrod
457
+ width: 0.5
458
+ dp: 0
459
+ - tagname: Router_eth6_bytes_in
460
+ label: eth6 in
461
+ scale: 'bytes'
462
+ color: brown
463
+ width: 1
464
+ dp: 0
465
+ - tagname: Router_eth6_bytes_out
466
+ label: eth6 out
467
+ scale: 'bytes'
468
+ color: brown
469
+ width: 0.5
470
+ dp: 0
471
+ - tagname: Router_eth7_bytes_in
472
+ label: eth7 in
473
+ scale: 'bytes'
474
+ color: orange
475
+ width: 1
476
+ dp: 0
477
+ - tagname: Router_eth7_bytes_out
478
+ label: eth7 out
479
+ scale: 'bytes'
480
+ color: orange
481
+ width: 0.5
482
+ dp: 0
483
+ - tagname: Router_eth8_bytes_in
484
+ label: eth8 in
485
+ scale: 'bytes'
486
+ color: aqua
487
+ width: 1
488
+ dp: 0
489
+ - tagname: Router_eth8_bytes_out
490
+ label: eth8 out
491
+ scale: 'bytes'
492
+ color: aqua
493
+ width: 0.5
494
+ dp: 0
495
+ - name: Ping Values
496
+ parent: Ping
497
+ items:
498
+ - {desc: Default tags, type: h1}
499
+ - {tagname: localhost_ping, type: value}
500
+ - {tagname: google_ping, type: value}
501
+ - {tagname: electronet_ping, type: value}
502
+ - name: Ping Trend
503
+ parent: Ping
504
+ items:
505
+ - type: uplot # Do all times in seconds, which uplot uses.
506
+ ms:
507
+ desc: Ping Trend
508
+ age: 172800
509
+ legend_pos: left
510
+ time_pos: left
511
+ time_res: m
512
+ axes:
513
+ - scale: x
514
+ range: [-86400, 0] # 86400 172800 1209600
515
+ - scale: mS
516
+ range: [0.0, 1.0]
517
+ dp: 1
518
+ series:
519
+ - tagname: localhost_ping
520
+ label: localhost
521
+ scale: mS
522
+ color: black
523
+ dp: 1
524
+ - tagname: electronet_ping
525
+ label: electronet
526
+ scale: mS
527
+ color: blue
528
+ dp: 1
529
+ - tagname: google_ping
530
+ label: google
531
+ scale: mS
532
+ color: red
533
+ dp: 1
@@ -7,23 +7,22 @@ from pymscada.iodrivers.logix_map import LogixMaps
7
7
 
8
8
 
9
9
  class LogixClientConnector:
10
- """Poll Logix device, write on change in write range."""
10
+ """Manage interface to device."""
11
11
 
12
- def __init__(self, name: str, ip:str, rate: float, read: list,
13
- writeok: list, mapping: LogixMaps):
12
+ def __init__(self, name: str, ip: str, rate: float, poll: list,
13
+ mapping: LogixMaps):
14
14
  """Set up polling client."""
15
15
  self.plc_name = name
16
16
  self.ip = ip
17
17
  self.read_tags = []
18
- for r in read:
18
+ for r in poll:
19
19
  tag = r['addr']
20
20
  if r['type'].endswith('[]'):
21
21
  count = r['end'] - r['start'] + 1
22
22
  tag = f"{r['addr']}[{r['start']}]{{{count}}}"
23
23
  self.read_tags.append(tag)
24
24
  self.mapping = mapping
25
- self.mapping.add_write_callback(name, writeok,
26
- self.write_tag_update)
25
+ self.mapping.add_write_callback(name, self.write_tag_update)
27
26
  self.periodic = Periodic(self.poll, rate)
28
27
  self.plc = LogixDriver(ip)
29
28
 
@@ -33,14 +32,14 @@ class LogixClientConnector:
33
32
  logging.warning(f'write failed {self.plc_name} {addr} to {value}')
34
33
  return
35
34
  logging.info(f'writing {addr} {value}')
36
- res = self.plc.write((addr, value))
37
- pass
35
+ result = self.plc.write((addr, value))
36
+ logging.info(result)
38
37
 
39
38
  async def poll(self):
40
39
  """Poll data, reopen connection if dead."""
41
40
  if not self.plc.connected and not self.plc.open():
42
41
  return
43
- polled_tags = None
42
+ # polled_tags = None
44
43
  polled_tags = self.plc.read(*self.read_tags)
45
44
  self.mapping.polled_data(self.plc_name, polled_tags)
46
45
 
@@ -50,6 +49,7 @@ class LogixClientConnector:
50
49
 
51
50
 
52
51
  class LogixClient:
52
+ """Manage interface between bus and individual devices."""
53
53
 
54
54
  def __init__(self, bus_ip: str = '127.0.0.1', bus_port: int = 1324,
55
55
  rtus: dict = {}, tags: dict = {}) -> None:
@@ -13,6 +13,7 @@ DTYPES = {
13
13
 
14
14
 
15
15
  def tag_split(plc_tag: str):
16
+ """Split the address into rtu, variable, element and bit."""
16
17
  separator = plc_tag.find(':')
17
18
  arr_start_loc = plc_tag.find('[')
18
19
  arr_end_loc = plc_tag.find(']')
@@ -40,27 +41,40 @@ def tag_split(plc_tag: str):
40
41
  class LogixMap:
41
42
  """Do value updates for each tag."""
42
43
 
43
- def __init__(self, tagname: str, src_type: str, read_tag: str,
44
- write_tag: str):
44
+ def __init__(self, tagname: str, tagdict: dict):
45
45
  """Initialise modbus map and Tag."""
46
- dtype, dmin, dmax = DTYPES[src_type][0:3]
46
+ dtype, dmin, dmax = DTYPES[tagdict['type']][0:3]
47
47
  self.tag = Tag(tagname, dtype)
48
48
  self.map_bus = id(self)
49
- self.read_plc, self.read_var, self.read_elm, self.read_bit = \
50
- tag_split(read_tag)
51
- self.plc_read_tag = read_tag
52
- self.write_plc, self.write_var, self.write_elm, self.write_bit = \
53
- tag_split(write_tag)
54
- self.plc_write_tag = write_tag
55
- self.callback = None
56
49
  if dmin is not None:
57
50
  self.tag.value_min = dmin
58
51
  if dmax is not None:
59
52
  self.tag.value_max = dmax
53
+ if 'read' in tagdict:
54
+ self.plc_read_tag = tagdict['read']
55
+ self.read_plc, self.read_var, self.read_elm, self.read_bit = \
56
+ tag_split(self.plc_read_tag)
57
+ else:
58
+ self.plc_read_tag = None
59
+ self.read_plc = None
60
+ self.read_var = None
61
+ self.read_elm = None
62
+ self.read_bit = None
63
+ if 'write' in tagdict:
64
+ self.plc_write_tag = tagdict['write']
65
+ self.write_plc, self.write_var, self.write_elm, self.write_bit = \
66
+ tag_split(self.plc_write_tag)
67
+ else:
68
+ self.plc_write_tag = None
69
+ self.write_plc = None
70
+ self.write_var = None
71
+ self.write_elm = None
72
+ self.write_bit = None
73
+ self.write_callback = None
60
74
 
61
75
  def set_callback(self, callback):
62
76
  """Add tag callback interface."""
63
- self.callback = callback
77
+ self.write_callback = callback
64
78
  self.tag.add_callback(self.tag_value_changed, bus_id=self.map_bus)
65
79
 
66
80
  def set_tag_value(self, value, time_us):
@@ -83,7 +97,7 @@ class LogixMap:
83
97
  addr = f'{self.write_var}[{self.write_elm}]'
84
98
  else:
85
99
  addr = f'{self.write_var}[{self.write_elm}].{self.write_bit}'
86
- self.callback(addr, tag.value)
100
+ self.write_callback(addr, tag.value)
87
101
 
88
102
 
89
103
  class LogixMaps:
@@ -95,14 +109,8 @@ class LogixMaps:
95
109
  self.tag_map: dict[str, LogixMap] = {}
96
110
  # use the plc_name then variable name to access a list of maps.
97
111
  self.read_var_map: dict[str, dict[str, list[LogixMap]]] = {}
98
- for tagname, v in tags.items():
99
- if 'addr' in v:
100
- read_addr = v['addr']
101
- write_addr = v['addr']
102
- else:
103
- read_addr = v['read']
104
- write_addr = v['write']
105
- map = LogixMap(tagname, v['type'], read_addr, write_addr)
112
+ for tagname, tagdict in tags.items():
113
+ map = LogixMap(tagname, tagdict)
106
114
  if map.read_plc not in self.read_var_map:
107
115
  self.read_var_map[map.read_plc] = {}
108
116
  if map.read_var not in self.read_var_map[map.read_plc]:
@@ -111,21 +119,10 @@ class LogixMaps:
111
119
  self.read_var_map[map.read_plc][map.read_var].append(map)
112
120
  self.tag_map[map.tag.name] = map
113
121
 
114
- def add_write_callback(self, plcname, writeok, callback):
115
- """Connection advises device links."""
116
- # Create a set of all possible valid addresses
117
- write_set = set()
118
- for w in writeok:
119
- if '[' in w['type']:
120
- for i in range(w['start'], w['end'] + 1):
121
- write_set.add((w['addr'], i))
122
- else:
123
- write_set.add((w['addr'], None))
124
- # where the mapped tag uses a valid address, add callback to
125
- # the connection writer
122
+ def add_write_callback(self, plcname, callback):
123
+ """Register connector with map for write tags."""
126
124
  for map in self.tag_map.values():
127
- if map.write_plc == plcname and \
128
- (map.write_var, map.write_elm) in write_set:
125
+ if map.write_plc == plcname:
129
126
  map.set_callback(callback)
130
127
 
131
128
  def polled_data(self, plcname, polls):
@@ -1,6 +1,5 @@
1
1
  """Modbus Client."""
2
2
  import asyncio
3
- from itertools import chain
4
3
  import logging
5
4
  from struct import pack, unpack_from
6
5
  from pymscada.bus_client import BusClient
@@ -43,53 +42,40 @@ class ModbusClientProtocol(asyncio.Protocol):
43
42
  transport.set_write_buffer_limits(high=0)
44
43
  self.transport = transport
45
44
 
46
- def data_received(self, recv):
47
- """Received TCP data, see if there is a full modbus packet."""
48
- # logging.info("data_received")
49
- # logging.info(f'tcp echo server received: {recv}')
45
+ def unpack_mb(self):
46
+ """Return complete modbus packets and trim the buffer."""
50
47
  start = 0
51
- self.buffer += recv
52
48
  while True:
53
49
  buf_len = len(self.buffer)
54
- if buf_len < 6 + start: # assumes possible mbap_len of 0
55
- self.buffer = self.buffer[start:]
50
+ if buf_len < 6 + start: # enough to unpack length
56
51
  break
57
- (_mbap_tr, _mbap_pr, mbap_len) = unpack_from(
58
- ">3H", self.buffer, start
59
- )
60
- if buf_len < 6 + mbap_len:
61
- self.buffer = self.buffer[start:]
52
+ mbap_tr, mbap_pr, mbap_len = unpack_from(">3H", self.buffer, start)
53
+ if buf_len < start + 6 + mbap_len: # there is a complete message
62
54
  break
63
55
  end = start + 6 + mbap_len
64
- # got a complete message, set start to end for buffer prune
65
- self.process(self.buffer[start:end])
56
+ yield self.buffer[start:end]
66
57
  start = end
58
+ self.buffer = self.buffer[end:]
59
+
60
+ def data_received(self, recv):
61
+ """Received TCP data, see if there is a full modbus packet."""
62
+ self.buffer += recv
63
+ for msg in self.unpack_mb():
64
+ self.process(msg)
67
65
 
68
66
  def datagram_received(self, recv, _addr):
69
67
  """Received a UDP packet, discard any partial packets."""
70
68
  # logging.info("datagram_received")
71
- start = 0
72
- buffer = recv
73
- while True:
74
- buf_len = len(buffer)
75
- if buf_len < 6 + start: # assumes possible mbap_len of 0
76
- buffer = buffer[start:]
77
- break
78
- (_mbap_tr, _mbap_pr, mbap_len) = unpack_from(">3H", buffer, start)
79
- if buf_len < 6 + mbap_len:
80
- buffer = buffer[start:]
81
- break
82
- end = start + 6 + mbap_len
83
- # got a complete message, set start to end for buffer prune
84
- self.process(buffer[start:end])
85
- start = end
69
+ self.buffer = recv
70
+ for msg in self.unpack_mb():
71
+ self.process(msg)
86
72
 
87
73
 
88
74
  class ModbusClientConnector:
89
75
  """Poll Modbus device, write on change in write range."""
90
76
 
91
77
  def __init__(self, name: str, ip: str, port: int, rate: int, tcp_udp: str,
92
- read: list, writeok: list, mapping: ModbusMaps):
78
+ poll: list, mapping: ModbusMaps):
93
79
  """
94
80
  Set up polling client.
95
81
 
@@ -101,13 +87,13 @@ class ModbusClientConnector:
101
87
  self.tcp_udp = tcp_udp
102
88
  self.transport = None
103
89
  self.protocol = None
104
- self.read = read
105
- self.writeok = writeok
90
+ self.read = poll
91
+ self.writeok = None
106
92
  self.periodic = Periodic(self.poll, rate)
107
93
  self.mapping = mapping
108
94
  self.sent = {}
109
95
  tables = {}
110
- for file_range in chain(read, writeok):
96
+ for file_range in poll: # chain(read, writeok):
111
97
  unit = file_range['unit']
112
98
  file = file_range['file']
113
99
  table = f'{name}:{unit}:{file}'