pymscada 0.0.14__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 +24 -23
- 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 +74 -62
- 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.14.dist-info → pymscada-0.1.0.dist-info}/RECORD +28 -26
- pymscada/demo/simulate.yaml +0 -21
- pymscada/simulate.py +0 -66
- pymscada-0.0.14.dist-info/METADATA +0 -251
- /pymscada/demo/{pymscada-modbusclient.service → pymscada-io-modbusclient.service} +0 -0
- /pymscada/demo/{pymscada-modbusserver.service → pymscada-io-modbusserver.service} +0 -0
- /pymscada/{iodrivers → tools}/snmp_client2.py +0 -0
- {pymscada-0.0.14.dist-info → pymscada-0.1.0.dist-info}/WHEEL +0 -0
- {pymscada-0.0.14.dist-info → pymscada-0.1.0.dist-info}/entry_points.txt +0 -0
- {pymscada-0.0.14.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
|
@@ -12,52 +12,75 @@ DTYPES = {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
def tag_split(plc_tag: str):
|
|
16
|
+
"""Split the address into rtu, variable, element and bit."""
|
|
17
|
+
separator = plc_tag.find(':')
|
|
18
|
+
arr_start_loc = plc_tag.find('[')
|
|
19
|
+
arr_end_loc = plc_tag.find(']')
|
|
20
|
+
bit_loc = plc_tag.find('.')
|
|
21
|
+
plc = plc_tag[:separator]
|
|
22
|
+
if arr_start_loc == -1 and bit_loc == -1:
|
|
23
|
+
var = plc_tag[separator + 1:]
|
|
24
|
+
elm = None
|
|
25
|
+
bit = None
|
|
26
|
+
elif arr_start_loc == -1:
|
|
27
|
+
var = plc_tag[separator + 1:bit_loc]
|
|
28
|
+
elm = None
|
|
29
|
+
bit = int(plc_tag[bit_loc + 1:])
|
|
30
|
+
elif bit_loc == -1:
|
|
31
|
+
var = plc_tag[separator + 1:arr_start_loc]
|
|
32
|
+
elm = int(plc_tag[arr_start_loc + 1:arr_end_loc])
|
|
33
|
+
bit = None
|
|
34
|
+
else:
|
|
35
|
+
var = plc_tag[separator + 1:arr_start_loc]
|
|
36
|
+
elm = int(plc_tag[arr_start_loc + 1:arr_end_loc])
|
|
37
|
+
bit = int(plc_tag[bit_loc + 1:])
|
|
38
|
+
return plc, var, elm, bit
|
|
39
|
+
|
|
40
|
+
|
|
15
41
|
class LogixMap:
|
|
16
42
|
"""Do value updates for each tag."""
|
|
17
43
|
|
|
18
|
-
def __init__(self, tagname: str,
|
|
44
|
+
def __init__(self, tagname: str, tagdict: dict):
|
|
19
45
|
"""Initialise modbus map and Tag."""
|
|
20
|
-
dtype, dmin, dmax = DTYPES[
|
|
46
|
+
dtype, dmin, dmax = DTYPES[tagdict['type']][0:3]
|
|
21
47
|
self.tag = Tag(tagname, dtype)
|
|
22
48
|
self.map_bus = id(self)
|
|
23
|
-
separator = plc_tag.find(':')
|
|
24
|
-
arr_start_loc = plc_tag.find('[')
|
|
25
|
-
arr_end_loc = plc_tag.find(']')
|
|
26
|
-
bit_loc = plc_tag.find('.')
|
|
27
|
-
self.plc = plc_tag[:separator]
|
|
28
|
-
if arr_start_loc == -1 and bit_loc == -1:
|
|
29
|
-
self.var = plc_tag[separator + 1:]
|
|
30
|
-
self.elm = None
|
|
31
|
-
self.bit = None
|
|
32
|
-
elif arr_start_loc == -1:
|
|
33
|
-
self.var = plc_tag[separator + 1:bit_loc]
|
|
34
|
-
self.elm = None
|
|
35
|
-
self.bit = int(plc_tag[bit_loc + 1:])
|
|
36
|
-
elif bit_loc == -1:
|
|
37
|
-
self.var = plc_tag[separator + 1:arr_start_loc]
|
|
38
|
-
self.elm = int(plc_tag[arr_start_loc + 1:arr_end_loc])
|
|
39
|
-
self.bit = None
|
|
40
|
-
else:
|
|
41
|
-
self.var = plc_tag[separator + 1:arr_start_loc]
|
|
42
|
-
self.elm = int(plc_tag[arr_start_loc + 1:arr_end_loc])
|
|
43
|
-
self.bit = int(plc_tag[bit_loc + 1:])
|
|
44
|
-
self.plc_tag = plc_tag
|
|
45
|
-
self.callback = None
|
|
46
49
|
if dmin is not None:
|
|
47
50
|
self.tag.value_min = dmin
|
|
48
51
|
if dmax is not None:
|
|
49
52
|
self.tag.value_max = dmax
|
|
50
|
-
|
|
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
|
|
51
74
|
|
|
52
75
|
def set_callback(self, callback):
|
|
53
76
|
"""Add tag callback interface."""
|
|
54
|
-
self.
|
|
77
|
+
self.write_callback = callback
|
|
55
78
|
self.tag.add_callback(self.tag_value_changed, bus_id=self.map_bus)
|
|
56
79
|
|
|
57
80
|
def set_tag_value(self, value, time_us):
|
|
58
81
|
"""Pass update from IO driver to tag value."""
|
|
59
|
-
if self.
|
|
60
|
-
if value & 1 << self.
|
|
82
|
+
if self.read_bit is not None:
|
|
83
|
+
if value & 1 << self.read_bit:
|
|
61
84
|
value = 1
|
|
62
85
|
else:
|
|
63
86
|
value = 0
|
|
@@ -66,15 +89,15 @@ class LogixMap:
|
|
|
66
89
|
|
|
67
90
|
def tag_value_changed(self, tag: Tag):
|
|
68
91
|
"""Pass update from tag value to IO driver."""
|
|
69
|
-
if self.
|
|
70
|
-
addr = self.
|
|
71
|
-
elif self.
|
|
72
|
-
addr = f'{self.
|
|
73
|
-
elif self.
|
|
74
|
-
addr = f'{self.
|
|
92
|
+
if self.write_elm is None and self.write_bit is None:
|
|
93
|
+
addr = self.write_var
|
|
94
|
+
elif self.write_elm is None:
|
|
95
|
+
addr = f'{self.write_var}.{self.write_bit}'
|
|
96
|
+
elif self.write_bit is None:
|
|
97
|
+
addr = f'{self.write_var}[{self.write_elm}]'
|
|
75
98
|
else:
|
|
76
|
-
addr = f'{self.
|
|
77
|
-
self.
|
|
99
|
+
addr = f'{self.write_var}[{self.write_elm}].{self.write_bit}'
|
|
100
|
+
self.write_callback(addr, tag.value)
|
|
78
101
|
|
|
79
102
|
|
|
80
103
|
class LogixMaps:
|
|
@@ -85,32 +108,21 @@ class LogixMaps:
|
|
|
85
108
|
# use the tagname to access the map.
|
|
86
109
|
self.tag_map: dict[str, LogixMap] = {}
|
|
87
110
|
# use the plc_name then variable name to access a list of maps.
|
|
88
|
-
self.
|
|
89
|
-
for tagname,
|
|
90
|
-
|
|
91
|
-
map
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if map.var not in self.var_map[map.plc]:
|
|
111
|
+
self.read_var_map: dict[str, dict[str, list[LogixMap]]] = {}
|
|
112
|
+
for tagname, tagdict in tags.items():
|
|
113
|
+
map = LogixMap(tagname, tagdict)
|
|
114
|
+
if map.read_plc not in self.read_var_map:
|
|
115
|
+
self.read_var_map[map.read_plc] = {}
|
|
116
|
+
if map.read_var not in self.read_var_map[map.read_plc]:
|
|
95
117
|
# make a list so multiple bits can map to a word
|
|
96
|
-
self.
|
|
97
|
-
self.
|
|
118
|
+
self.read_var_map[map.read_plc][map.read_var] = []
|
|
119
|
+
self.read_var_map[map.read_plc][map.read_var].append(map)
|
|
98
120
|
self.tag_map[map.tag.name] = map
|
|
99
121
|
|
|
100
|
-
def add_write_callback(self, plcname,
|
|
101
|
-
"""
|
|
102
|
-
# Create a set of all possible valid addresses
|
|
103
|
-
write_set = set()
|
|
104
|
-
for w in writeok:
|
|
105
|
-
if '[' in w['type']:
|
|
106
|
-
for i in range(w['start'], w['end'] + 1):
|
|
107
|
-
write_set.add((w['addr'], i))
|
|
108
|
-
else:
|
|
109
|
-
write_set.add((w['addr'], None))
|
|
110
|
-
# where the mapped tag uses a valid address, add callback to
|
|
111
|
-
# the connection writer
|
|
122
|
+
def add_write_callback(self, plcname, callback):
|
|
123
|
+
"""Register connector with map for write tags."""
|
|
112
124
|
for map in self.tag_map.values():
|
|
113
|
-
if map.
|
|
125
|
+
if map.write_plc == plcname:
|
|
114
126
|
map.set_callback(callback)
|
|
115
127
|
|
|
116
128
|
def polled_data(self, plcname, polls):
|
|
@@ -121,12 +133,12 @@ class LogixMaps:
|
|
|
121
133
|
logging.error(poll.error)
|
|
122
134
|
arr_start_loc = poll.tag.find('[')
|
|
123
135
|
if arr_start_loc == -1:
|
|
124
|
-
for map in self.
|
|
136
|
+
for map in self.read_var_map[plcname][poll.tag]:
|
|
125
137
|
map.set_tag_value(poll.value, time_us)
|
|
126
138
|
else:
|
|
127
139
|
var = poll.tag[:arr_start_loc]
|
|
128
140
|
elm = int(poll.tag[arr_start_loc + 1: -1])
|
|
129
|
-
for map in self.
|
|
130
|
-
elm_offset = map.
|
|
141
|
+
for map in self.read_var_map[plcname][var]:
|
|
142
|
+
elm_offset = map.read_elm - elm
|
|
131
143
|
if elm_offset > 0 and elm_offset < len(poll.value):
|
|
132
144
|
map.set_tag_value(poll.value[elm_offset], time_us)
|