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.
- pymscada/checkout.py +38 -5
- pymscada/demo/logixclient.yaml +6 -10
- pymscada/demo/modbusclient.yaml +22 -18
- pymscada/demo/modbusserver.yaml +10 -2
- pymscada/demo/ping.yaml +12 -0
- pymscada/demo/pymscada-io-ping.service +15 -0
- pymscada/demo/snmpclient.yaml +17 -17
- pymscada/demo/tags.yaml +50 -1
- pymscada/demo/wwwserver.yaml +447 -1
- pymscada/iodrivers/logix_client.py +9 -9
- pymscada/iodrivers/logix_map.py +31 -34
- pymscada/iodrivers/modbus_client.py +20 -34
- pymscada/iodrivers/modbus_map.py +4 -1
- pymscada/iodrivers/modbus_server.py +34 -48
- pymscada/iodrivers/ping_client.py +120 -0
- pymscada/iodrivers/ping_map.py +43 -0
- pymscada/iodrivers/snmp_client.py +4 -2
- pymscada/iodrivers/snmp_map.py +1 -1
- pymscada/main.py +239 -71
- pymscada/validate.py +127 -35
- pymscada-0.1.0.dist-info/METADATA +88 -0
- {pymscada-0.0.15.dist-info → pymscada-0.1.0.dist-info}/RECORD +26 -24
- pymscada/demo/simulate.yaml +0 -21
- pymscada/simulate.py +0 -66
- pymscada-0.0.15.dist-info/METADATA +0 -270
- /pymscada/{iodrivers → tools}/snmp_client2.py +0 -0
- {pymscada-0.0.15.dist-info → pymscada-0.1.0.dist-info}/WHEEL +0 -0
- {pymscada-0.0.15.dist-info → pymscada-0.1.0.dist-info}/entry_points.txt +0 -0
- {pymscada-0.0.15.dist-info → pymscada-0.1.0.dist-info}/licenses/LICENSE +0 -0
pymscada/demo/wwwserver.yaml
CHANGED
|
@@ -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
|
-
"""
|
|
10
|
+
"""Manage interface to device."""
|
|
11
11
|
|
|
12
|
-
def __init__(self, name: str, ip:str, rate: float,
|
|
13
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
37
|
-
|
|
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:
|
pymscada/iodrivers/logix_map.py
CHANGED
|
@@ -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,
|
|
44
|
-
write_tag: str):
|
|
44
|
+
def __init__(self, tagname: str, tagdict: dict):
|
|
45
45
|
"""Initialise modbus map and Tag."""
|
|
46
|
-
dtype, dmin, dmax = DTYPES[
|
|
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.
|
|
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.
|
|
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,
|
|
99
|
-
|
|
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,
|
|
115
|
-
"""
|
|
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
|
|
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
|
|
47
|
-
"""
|
|
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: #
|
|
55
|
-
self.buffer = self.buffer[start:]
|
|
50
|
+
if buf_len < 6 + start: # enough to unpack length
|
|
56
51
|
break
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
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 =
|
|
105
|
-
self.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}'
|