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/validate.py
CHANGED
|
@@ -51,7 +51,7 @@ TAG_SCHEMA = {
|
|
|
51
51
|
'keysrules': {
|
|
52
52
|
'type': 'string',
|
|
53
53
|
# tag name discovery, save for later checking
|
|
54
|
-
'ms_tagname':
|
|
54
|
+
'ms_tagname': 'save'
|
|
55
55
|
},
|
|
56
56
|
'valuesrules': {
|
|
57
57
|
'type': 'dict',
|
|
@@ -67,7 +67,7 @@ TAG_SCHEMA = {
|
|
|
67
67
|
BUS_SCHEMA = {
|
|
68
68
|
'type': 'dict',
|
|
69
69
|
'schema': {
|
|
70
|
-
'ip': {'type': 'string', 'ms_ip':
|
|
70
|
+
'ip': {'type': 'string', 'ms_ip': 'ipv4 none'},
|
|
71
71
|
'port': {'type': 'integer', 'min': 1024, 'max': 65536}
|
|
72
72
|
}
|
|
73
73
|
}
|
|
@@ -88,12 +88,12 @@ VALUESETFILES_LIST = {
|
|
|
88
88
|
'allowed': ['value', 'setpoint', 'files'],
|
|
89
89
|
},
|
|
90
90
|
# tagname must have been found in parsing tags.yaml
|
|
91
|
-
'tagname': {'type': 'string', 'ms_tagname':
|
|
91
|
+
'tagname': {'type': 'string', 'ms_tagname': 'exists'}
|
|
92
92
|
}
|
|
93
93
|
SELECTDICT_LIST = {
|
|
94
94
|
'type': {'type': 'string', 'allowed': ['selectdict']},
|
|
95
95
|
# tagname must have been found in parsing tags.yaml
|
|
96
|
-
'tagname': {'type': 'string', 'ms_tagname':
|
|
96
|
+
'tagname': {'type': 'string', 'ms_tagname': 'exists'},
|
|
97
97
|
'opts': {
|
|
98
98
|
'type': 'dict',
|
|
99
99
|
# 'schema': {
|
|
@@ -138,7 +138,7 @@ UPLOT_LIST = {
|
|
|
138
138
|
'type': 'dict',
|
|
139
139
|
'schema': {
|
|
140
140
|
# tagname must have been found in parsing tags.yaml
|
|
141
|
-
'tagname': {'type': 'string', 'ms_tagname':
|
|
141
|
+
'tagname': {'type': 'string', 'ms_tagname': 'exists'},
|
|
142
142
|
'label': {'type': 'string', 'required': False},
|
|
143
143
|
'scale': {'type': 'string', 'required': False},
|
|
144
144
|
'color': {'type': 'string', 'required': False},
|
|
@@ -170,9 +170,9 @@ LIST_WWWSERVER = {
|
|
|
170
170
|
WWWSERVER_SCHEMA = {
|
|
171
171
|
'type': 'dict',
|
|
172
172
|
'schema': {
|
|
173
|
-
'bus_ip': {'type': 'string', 'ms_ip':
|
|
173
|
+
'bus_ip': {'type': 'string', 'ms_ip': 'none ipv4'},
|
|
174
174
|
'bus_port': {'type': 'integer', 'min': 1024, 'max': 65536},
|
|
175
|
-
'ip': {'type': 'string', 'ms_ip':
|
|
175
|
+
'ip': {'type': 'string', 'ms_ip': 'none ipv4'},
|
|
176
176
|
'port': {'type': 'integer', 'min': 1024, 'max': 65536},
|
|
177
177
|
'get_path': {'nullable': True},
|
|
178
178
|
'paths': {'type': 'list', 'allowed': ['history', 'config', 'pdf']},
|
|
@@ -186,7 +186,7 @@ WWWSERVER_SCHEMA = {
|
|
|
186
186
|
HISTORY_SCHEMA = {
|
|
187
187
|
'type': 'dict',
|
|
188
188
|
'schema': {
|
|
189
|
-
'bus_ip': {'type': 'string', 'ms_ip':
|
|
189
|
+
'bus_ip': {'type': 'string', 'ms_ip': 'none ipv4'},
|
|
190
190
|
'bus_port': {'type': 'integer', 'min': 1024, 'max': 65536},
|
|
191
191
|
'path': {'type': 'string'},
|
|
192
192
|
}
|
|
@@ -195,7 +195,7 @@ HISTORY_SCHEMA = {
|
|
|
195
195
|
MODBUSSERVER_SCHEMA = {
|
|
196
196
|
'type': 'dict',
|
|
197
197
|
'schema': {
|
|
198
|
-
'bus_ip': {'type': 'string', 'ms_ip':
|
|
198
|
+
'bus_ip': {'type': 'string', 'ms_ip': 'none ipv4', 'nullable': True},
|
|
199
199
|
'bus_port': {'type': 'integer', 'min': 1024, 'max': 65536,
|
|
200
200
|
'nullable': True},
|
|
201
201
|
'path': {'type': 'string'},
|
|
@@ -219,7 +219,7 @@ MODBUSSERVER_SCHEMA = {
|
|
|
219
219
|
'type': 'dict',
|
|
220
220
|
'keysrules': {
|
|
221
221
|
'type': 'string',
|
|
222
|
-
'ms_tagname':
|
|
222
|
+
'ms_tagname': 'none'
|
|
223
223
|
},
|
|
224
224
|
'valuesrules': {
|
|
225
225
|
'type': 'dict',
|
|
@@ -235,7 +235,7 @@ MODBUSSERVER_SCHEMA = {
|
|
|
235
235
|
MODBUSCLIENT_SCHEMA = {
|
|
236
236
|
'type': 'dict',
|
|
237
237
|
'schema': {
|
|
238
|
-
'bus_ip': {'type': 'string', 'ms_ip':
|
|
238
|
+
'bus_ip': {'type': 'string', 'ms_ip': 'ipv4'},
|
|
239
239
|
'bus_port': {'type': 'integer', 'min': 1024, 'max': 65536,
|
|
240
240
|
'nullable': True},
|
|
241
241
|
'path': {'type': 'string'},
|
|
@@ -249,11 +249,48 @@ MODBUSCLIENT_SCHEMA = {
|
|
|
249
249
|
'port': {},
|
|
250
250
|
'tcp_udp': {'type': 'string', 'allowed': ['tcp', 'udp']},
|
|
251
251
|
'rate': {},
|
|
252
|
-
'
|
|
252
|
+
'poll': {
|
|
253
253
|
'type': 'list',
|
|
254
254
|
'schema': {}
|
|
255
|
-
}
|
|
256
|
-
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
'tags': {
|
|
260
|
+
'type': 'dict',
|
|
261
|
+
'keysrules': {
|
|
262
|
+
'type': 'string',
|
|
263
|
+
'ms_tagname': 'exists'
|
|
264
|
+
},
|
|
265
|
+
'valuesrules': {
|
|
266
|
+
'type': 'dict',
|
|
267
|
+
'schema': {
|
|
268
|
+
'type': {},
|
|
269
|
+
'read': {},
|
|
270
|
+
'write': {}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
SNMPCLIENT_SCHEMA = {
|
|
278
|
+
'type': 'dict',
|
|
279
|
+
'schema': {
|
|
280
|
+
'bus_ip': {'type': 'string', 'ms_ip': 'ipv4'},
|
|
281
|
+
'bus_port': {'type': 'integer', 'min': 1024, 'max': 65536,
|
|
282
|
+
'nullable': True},
|
|
283
|
+
'path': {'type': 'string'},
|
|
284
|
+
'rtus': {
|
|
285
|
+
'type': 'list',
|
|
286
|
+
'schema': {
|
|
287
|
+
'type': 'dict',
|
|
288
|
+
'schema': {
|
|
289
|
+
'name': {},
|
|
290
|
+
'ip': {},
|
|
291
|
+
'community': {},
|
|
292
|
+
'rate': {},
|
|
293
|
+
'poll': {
|
|
257
294
|
'type': 'list',
|
|
258
295
|
'schema': {}
|
|
259
296
|
}
|
|
@@ -264,44 +301,91 @@ MODBUSCLIENT_SCHEMA = {
|
|
|
264
301
|
'type': 'dict',
|
|
265
302
|
'keysrules': {
|
|
266
303
|
'type': 'string',
|
|
267
|
-
'ms_tagname':
|
|
304
|
+
'ms_tagname': 'exists'
|
|
268
305
|
},
|
|
269
306
|
'valuesrules': {
|
|
270
307
|
'type': 'dict',
|
|
271
308
|
'schema': {
|
|
272
309
|
'type': {},
|
|
273
|
-
'
|
|
274
|
-
}
|
|
310
|
+
'read': {}
|
|
311
|
+
}
|
|
275
312
|
}
|
|
276
|
-
}
|
|
313
|
+
}
|
|
277
314
|
}
|
|
278
315
|
}
|
|
279
316
|
|
|
317
|
+
LOGIXCLIENT_SCHEMA = {
|
|
318
|
+
'type': 'dict',
|
|
319
|
+
'schema': {
|
|
320
|
+
'bus_ip': {'type': 'string', 'ms_ip': 'ipv4'},
|
|
321
|
+
'bus_port': {'type': 'integer', 'min': 1024, 'max': 65536,
|
|
322
|
+
'nullable': True},
|
|
323
|
+
'path': {'type': 'string'},
|
|
324
|
+
'rtus': {
|
|
325
|
+
'type': 'list',
|
|
326
|
+
'schema': {
|
|
327
|
+
'type': 'dict',
|
|
328
|
+
'schema': {
|
|
329
|
+
'name': {},
|
|
330
|
+
'ip': {},
|
|
331
|
+
'rate': {},
|
|
332
|
+
'poll': {
|
|
333
|
+
'type': 'list',
|
|
334
|
+
'schema': {}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
'tags': {
|
|
340
|
+
'type': 'dict',
|
|
341
|
+
'keysrules': {
|
|
342
|
+
'type': 'string',
|
|
343
|
+
'ms_tagname': 'exists'
|
|
344
|
+
},
|
|
345
|
+
'valuesrules': {
|
|
346
|
+
'type': 'dict',
|
|
347
|
+
'schema': {
|
|
348
|
+
'type': {},
|
|
349
|
+
'read': {},
|
|
350
|
+
'write': {}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
|
|
280
358
|
class MsValidator(Validator):
|
|
281
|
-
"""
|
|
359
|
+
"""Additional application checks."""
|
|
360
|
+
|
|
282
361
|
ms_tagnames = {}
|
|
362
|
+
ms_notagcheck = {}
|
|
283
363
|
|
|
284
364
|
def _validate_ms_tagname(self, constraint, field, value):
|
|
285
|
-
"""
|
|
365
|
+
"""
|
|
366
|
+
Test tagname exists, capture when true.
|
|
286
367
|
|
|
287
368
|
The rule's arguments are validated against this schema:
|
|
288
|
-
{'type': '
|
|
369
|
+
{'type': 'string'}
|
|
289
370
|
"""
|
|
290
371
|
if '.' in field:
|
|
291
372
|
self._error(field, "'.' invalid in tag definition.")
|
|
292
|
-
if constraint:
|
|
373
|
+
if constraint == 'save':
|
|
293
374
|
if field in self.ms_tagnames:
|
|
294
375
|
self._error(field, 'attempt to redefine')
|
|
295
376
|
else:
|
|
296
377
|
self.ms_tagnames[field] = {'type': None}
|
|
297
|
-
|
|
378
|
+
elif constraint == 'exists':
|
|
298
379
|
if value not in self.ms_tagnames:
|
|
299
380
|
self._error(field, 'tag was not defined in tags.yaml')
|
|
300
|
-
|
|
301
|
-
|
|
381
|
+
elif constraint == 'none':
|
|
382
|
+
pass
|
|
383
|
+
else:
|
|
384
|
+
pass
|
|
302
385
|
|
|
303
386
|
def _validate_ms_tagtype(self, constraint, field, value):
|
|
304
|
-
"""
|
|
387
|
+
"""
|
|
388
|
+
Test tagname type, capture when true.
|
|
305
389
|
|
|
306
390
|
The rule's arguments are validated against this schema:
|
|
307
391
|
{'type': 'boolean'}
|
|
@@ -318,18 +402,22 @@ class MsValidator(Validator):
|
|
|
318
402
|
pass
|
|
319
403
|
|
|
320
404
|
def _validate_ms_ip(self, constraint, field, value):
|
|
321
|
-
"""
|
|
405
|
+
"""
|
|
406
|
+
Test session.inet_aton works for the address.
|
|
322
407
|
|
|
323
408
|
The rule's arguments are validated against this schema:
|
|
324
|
-
{'type': '
|
|
409
|
+
{'type': 'string'}
|
|
325
410
|
"""
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
411
|
+
if value is None and 'none' in constraint:
|
|
412
|
+
pass
|
|
413
|
+
elif 'ipv4' in constraint:
|
|
414
|
+
try:
|
|
415
|
+
inet_aton(value)
|
|
416
|
+
except (OSError, TypeError):
|
|
417
|
+
self._error(field, 'ip address fails socket.inet_aton')
|
|
330
418
|
|
|
331
419
|
|
|
332
|
-
def validate(path: str=None):
|
|
420
|
+
def validate(path: str = None):
|
|
333
421
|
"""Validate."""
|
|
334
422
|
s = {
|
|
335
423
|
'tags': TAG_SCHEMA,
|
|
@@ -338,8 +426,10 @@ def validate(path: str=None):
|
|
|
338
426
|
'history': HISTORY_SCHEMA,
|
|
339
427
|
'modbusserver': MODBUSSERVER_SCHEMA,
|
|
340
428
|
'modbusclient': MODBUSCLIENT_SCHEMA,
|
|
429
|
+
'snmpclient': SNMPCLIENT_SCHEMA,
|
|
430
|
+
'logixclient': LOGIXCLIENT_SCHEMA,
|
|
341
431
|
}
|
|
342
|
-
prefix = ''
|
|
432
|
+
prefix = './'
|
|
343
433
|
if path is not None:
|
|
344
434
|
prefix = path + '/'
|
|
345
435
|
c = {
|
|
@@ -349,8 +439,10 @@ def validate(path: str=None):
|
|
|
349
439
|
'history': dict(Config(f'{prefix}history.yaml')),
|
|
350
440
|
'modbusserver': dict(Config(f'{prefix}modbusserver.yaml')),
|
|
351
441
|
'modbusclient': dict(Config(f'{prefix}modbusclient.yaml')),
|
|
442
|
+
'snmpclient': dict(Config(f'{prefix}snmpclient.yaml')),
|
|
443
|
+
'logixclient': dict(Config(f'{prefix}logixclient.yaml')),
|
|
352
444
|
}
|
|
353
445
|
v = MsValidator(s)
|
|
354
446
|
res = v.validate(c)
|
|
355
447
|
wdy = dump(v.errors) # , default_flow_style=False)
|
|
356
|
-
return res, wdy
|
|
448
|
+
return res, wdy, prefix
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: pymscada
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Shared tag value SCADA with python backup and Angular UI
|
|
5
|
+
Author-Email: Jamie Walton <jamie@walton.net.nz>
|
|
6
|
+
License: GPL-3.0-or-later
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: JavaScript
|
|
9
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Development Status :: 1 - Planning
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Requires-Dist: PyYAML>=6.0.1
|
|
15
|
+
Requires-Dist: aiohttp>=3.8.5
|
|
16
|
+
Requires-Dist: pymscada-html==0.1.0
|
|
17
|
+
Requires-Dist: cerberus>=1.3.5
|
|
18
|
+
Requires-Dist: pycomm3>=1.2.14
|
|
19
|
+
Requires-Dist: pysnmplib>=5.0.24
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# pymscada
|
|
23
|
+
#### [Docs](https://github.com/jamie0walton/pymscada/blob/main/docs/README.md)
|
|
24
|
+
|
|
25
|
+
#### [@Github](https://github.com/jamie0walton/pymscada/blob/main/README.md)
|
|
26
|
+
|
|
27
|
+
## Python Mobile SCADA
|
|
28
|
+
|
|
29
|
+
```pymscada``` read / write to Modbus and Logix PLCs. Read SNMP OIDs.
|
|
30
|
+
Collect history values and provide the ability to set values and trends
|
|
31
|
+
and issue commands.
|
|
32
|
+
|
|
33
|
+
User interface is via a web client embedded in this package. Examples included
|
|
34
|
+
for securing with Apache as a proxy.
|
|
35
|
+
|
|
36
|
+
Configuration with text yaml files, including the web page which are
|
|
37
|
+
procedurally built.
|
|
38
|
+
|
|
39
|
+
# See also
|
|
40
|
+
|
|
41
|
+
- The angular project [angmscada](https://github.com/jamie0walton/angmscada)
|
|
42
|
+
- Python container for the compiled angular pages [pymscada-html](https://github.com/jamie0walton/pymscada-html)
|
|
43
|
+
|
|
44
|
+
# Licence
|
|
45
|
+
|
|
46
|
+
```pymscada``` is distributed under the GPLv3 [license](./LICENSE).
|
|
47
|
+
|
|
48
|
+
# Use
|
|
49
|
+
Checkout the example files.
|
|
50
|
+
```bash
|
|
51
|
+
mscada@raspberrypi:~/test $ pymscada checkout
|
|
52
|
+
making 'history' folder
|
|
53
|
+
making pdf dir
|
|
54
|
+
making config dir
|
|
55
|
+
Creating /home/mscada/test/config/modbusclient.yaml
|
|
56
|
+
Creating /home/mscada/test/config/pymscada-history.service
|
|
57
|
+
Creating /home/mscada/test/config/wwwserver.yaml
|
|
58
|
+
Creating /home/mscada/test/config/pymscada-demo-modbus_plc.service
|
|
59
|
+
Creating /home/mscada/test/config/files.yaml
|
|
60
|
+
Creating /home/mscada/test/config/pymscada-modbusserver.service
|
|
61
|
+
Creating /home/mscada/test/config/pymscada-wwwserver.service
|
|
62
|
+
Creating /home/mscada/test/config/simulate.yaml
|
|
63
|
+
Creating /home/mscada/test/config/tags.yaml
|
|
64
|
+
Creating /home/mscada/test/config/history.yaml
|
|
65
|
+
Creating /home/mscada/test/config/pymscada-files.service
|
|
66
|
+
Creating /home/mscada/test/config/bus.yaml
|
|
67
|
+
Creating /home/mscada/test/config/modbusserver.yaml
|
|
68
|
+
Creating /home/mscada/test/config/modbus_plc.py
|
|
69
|
+
Creating /home/mscada/test/config/pymscada-modbusclient.service
|
|
70
|
+
Creating /home/mscada/test/config/pymscada-bus.service
|
|
71
|
+
Creating /home/mscada/test/config/README.md
|
|
72
|
+
mscada@raspberrypi:~/test $ pymscada validate
|
|
73
|
+
WARNING:root:pymscada 0.1.0 starting
|
|
74
|
+
Config files in ./ valid.
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Runs on a Raspberry Pi and includes preconfigured systemd files to
|
|
78
|
+
automate running the services. Mostly works on Windows, works better
|
|
79
|
+
on linux.
|
|
80
|
+
|
|
81
|
+
Modules can be run from the command line, although you need
|
|
82
|
+
a terminal for each running module (better with systemd).
|
|
83
|
+
```bash
|
|
84
|
+
pymscada bus --config bus.yaml
|
|
85
|
+
pymscada wwwserver --config wwwserver.yaml --tags tags.yaml
|
|
86
|
+
pymscada history --config history.yaml --tags tags.yaml
|
|
87
|
+
python weather.py
|
|
88
|
+
```
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
pymscada-0.0.
|
|
2
|
-
pymscada-0.0.
|
|
3
|
-
pymscada-0.0.
|
|
4
|
-
pymscada-0.0.
|
|
1
|
+
pymscada-0.1.0.dist-info/METADATA,sha256=4k0xZCUfrBdcOG1vnI9reNEvrOg8UKqG6MaN5vp_Oqs,3212
|
|
2
|
+
pymscada-0.1.0.dist-info/WHEEL,sha256=N2J68yzZqJh3mI_Wg92rwhw0rtJDFpZj9bwQIMJgaVg,90
|
|
3
|
+
pymscada-0.1.0.dist-info/entry_points.txt,sha256=AcZZ7HFj8k1ztP6ge-5bdRinYF8glW2s6lFEQG3esN4,57
|
|
4
|
+
pymscada-0.1.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
5
5
|
pymscada/__init__.py,sha256=D_4aEDWkW6xMVQane3CbehBKPxT3FCaDY_QpFwglCe8,653
|
|
6
6
|
pymscada/__main__.py,sha256=WcyVlrYOoDdktJhOoyubTOycMwpayksFdxwelRU5xpQ,272
|
|
7
7
|
pymscada/bus_client.py,sha256=A6EI3OafyOBKd8UVFXS4K39SpmwBoFE-kSIwldVeDKE,8695
|
|
8
8
|
pymscada/bus_server.py,sha256=-Bn4rfdE4OWXE0av459sROnCwAqV0VKFWulF0m1abHE,11255
|
|
9
|
-
pymscada/checkout.py,sha256=
|
|
9
|
+
pymscada/checkout.py,sha256=ISXhwRJbZBRu0cMeVHwgHzMgk9YslhnPtjs7-0lgjRo,3351
|
|
10
10
|
pymscada/config.py,sha256=vwGxieaJBYXiHNQEOYVDFaPuGmnUlCnbNm_W9bugKlc,1851
|
|
11
11
|
pymscada/console.py,sha256=sw1k6eXY53S8qMR-kx8pHOyhXHHt88e4RTcXmC7AQlw,946
|
|
12
12
|
pymscada/demo/README.md,sha256=iNcVbCTkq-d4agLV-979lNRaqf_hbJCn3OFzY-6qfU8,880
|
|
@@ -14,10 +14,11 @@ pymscada/demo/__init__.py,sha256=WsDDgkWnZBJbt2-cJCdc2NvRAv_T4a7WOC1Q0k_l0gI,29
|
|
|
14
14
|
pymscada/demo/bus.yaml,sha256=zde5JDo2Yv5s7NvJ569gAEoTDvsvgBwRPxfrYhsxj3w,26
|
|
15
15
|
pymscada/demo/files.yaml,sha256=Uizvo-LEhHCRePGNLdvl9m_Z40qOlT7aZDLE4zTFSMc,169
|
|
16
16
|
pymscada/demo/history.yaml,sha256=mn_Xf4h_bK6vwZVQ0Iz9BzJpwWok2gEgSKbDgEM8AOQ,46
|
|
17
|
-
pymscada/demo/logixclient.yaml,sha256=
|
|
17
|
+
pymscada/demo/logixclient.yaml,sha256=G_NlJhBYwT1a9ceHDgO6fCNKFmBM2pVO_t9Xa1NqlRY,912
|
|
18
18
|
pymscada/demo/modbus_plc.py,sha256=3zZHHbyrdxyryEHBeNIw-fpcDGcS1MaJiqEwQDr6zWI,2397
|
|
19
|
-
pymscada/demo/modbusclient.yaml,sha256=
|
|
20
|
-
pymscada/demo/modbusserver.yaml,sha256=
|
|
19
|
+
pymscada/demo/modbusclient.yaml,sha256=geeCsUJZkkEj7jjXR_Yk6R5zA5Ta9IczrHsARz7ZgXY,1099
|
|
20
|
+
pymscada/demo/modbusserver.yaml,sha256=67_mED6jXgtnzlDIky9Cg4j-nXur06iz9ve3JUwSyG8,1133
|
|
21
|
+
pymscada/demo/ping.yaml,sha256=r_VBGTLU5r4cZi9bIGL3M4eNw70KnoBptOUoNrSbnFY,210
|
|
21
22
|
pymscada/demo/pymscada-bus.service,sha256=rRTFwHaS8XWd9YAIB3cET4QvASaIO9emmxFiUAbl14g,257
|
|
22
23
|
pymscada/demo/pymscada-demo-modbus_plc.service,sha256=jmgk_peoxwKVXe-LbyK2VluMS1JMmoTud4JZHi9Tgec,316
|
|
23
24
|
pymscada/demo/pymscada-files.service,sha256=yGTxYnmQ_QBjIzItFatw_uIg_cG11vadLDaR0C9pPEk,322
|
|
@@ -25,24 +26,25 @@ pymscada/demo/pymscada-history.service,sha256=kEU_RsrRSmEUE-nR23n2q2yZxjALm_nCrJ
|
|
|
25
26
|
pymscada/demo/pymscada-io-logixclient.service,sha256=gvnCJgUeqnIgnuN-Wf1XhB0FJxVYyLksmMO7JC0wT-Y,344
|
|
26
27
|
pymscada/demo/pymscada-io-modbusclient.service,sha256=4tenKcrfRi0iMdv8-k2gtMQA4OPTM59zAyKpovwemxM,344
|
|
27
28
|
pymscada/demo/pymscada-io-modbusserver.service,sha256=FqCMD3EJKoiq6EbYnoijRLX5UeUWbZrNzDs50eQj7iE,344
|
|
29
|
+
pymscada/demo/pymscada-io-ping.service,sha256=d1n32srVKGd8qo8JWeBYEEznCRZWRWaBQLOYzdqEXWg,327
|
|
28
30
|
pymscada/demo/pymscada-io-snmpclient.service,sha256=wrA2kDR3bgO30lP_lNJrIsVXNQiXmWKnphoqUj3QTRI,339
|
|
29
31
|
pymscada/demo/pymscada-wwwserver.service,sha256=uDnqzfvAdAnTrqOCqDm1PN7SmeMSuOdmAhorHPJdEVI,366
|
|
30
|
-
pymscada/demo/
|
|
31
|
-
pymscada/demo/
|
|
32
|
-
pymscada/demo/
|
|
33
|
-
pymscada/demo/wwwserver.yaml,sha256=KDbe3mmslcK1L0ZnuvtOhhuJG-Hyw9OztMz813pChYw,1920
|
|
32
|
+
pymscada/demo/snmpclient.yaml,sha256=z8iACrFvMftYUtqGrRjPZYZTpn7aOXI-Kp675NAM8cU,2013
|
|
33
|
+
pymscada/demo/tags.yaml,sha256=GH90X3QRBANUhvd2E9OuyIoiZD25OBihHHlDBw1uzlw,4231
|
|
34
|
+
pymscada/demo/wwwserver.yaml,sha256=jyboShIbwkwgMAcFLkpn88VMvGfoc7a44jNrI_B6OqY,12493
|
|
34
35
|
pymscada/files.py,sha256=kEkiD7j5k69EK4jfMHEp-lbTnulYyrtUjkQDa5xmGPc,1784
|
|
35
36
|
pymscada/history.py,sha256=yAUfjzo4O3w9-hGEAbtvz9UseMTwRVz7NqS832RtvIs,9455
|
|
36
37
|
pymscada/iodrivers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
|
-
pymscada/iodrivers/logix_client.py,sha256=
|
|
38
|
-
pymscada/iodrivers/logix_map.py,sha256=
|
|
39
|
-
pymscada/iodrivers/modbus_client.py,sha256=
|
|
40
|
-
pymscada/iodrivers/modbus_map.py,sha256=
|
|
41
|
-
pymscada/iodrivers/modbus_server.py,sha256=
|
|
42
|
-
pymscada/iodrivers/
|
|
43
|
-
pymscada/iodrivers/
|
|
44
|
-
pymscada/iodrivers/
|
|
45
|
-
pymscada/
|
|
38
|
+
pymscada/iodrivers/logix_client.py,sha256=wDhl0pCqs_k7Hd1MtbOuBrEx9emOwDrLJqBNussH1qU,2806
|
|
39
|
+
pymscada/iodrivers/logix_map.py,sha256=ljjBAMJcw199v1V5u0Yfl38U6zbZzba5mdY4I3ZvdIM,5401
|
|
40
|
+
pymscada/iodrivers/modbus_client.py,sha256=mo7VRLWi4W2J5Ft3M6Y5jSbK5vbd6wn4R2dncbiL4nU,9529
|
|
41
|
+
pymscada/iodrivers/modbus_map.py,sha256=af2J3CGSeYQ4mSy8rNsERp9z7fRgRUYk3it5Mrc_IQA,7255
|
|
42
|
+
pymscada/iodrivers/modbus_server.py,sha256=nnUjd6iCny_Abylk06J8dq4slNAyNPQC2Rw2ZWEElKM,6507
|
|
43
|
+
pymscada/iodrivers/ping_client.py,sha256=dIR4SuKeNCu2FyhUNfTbkv-El3qPFMb0zUG1GJSD-80,4166
|
|
44
|
+
pymscada/iodrivers/ping_map.py,sha256=EbOteqfEYKIOMqPymROJ4now2If-ekEj6jnM5hthoSA,1403
|
|
45
|
+
pymscada/iodrivers/snmp_client.py,sha256=e-ea5BQnp-PxTetM9d61Mm-GkQrNZkUUlNp1Km9ZasI,2601
|
|
46
|
+
pymscada/iodrivers/snmp_map.py,sha256=sDdIR5ZPAETpozDfBt_XQiZ-f4t99UCPlzj7BxFxQyM,2369
|
|
47
|
+
pymscada/main.py,sha256=LxFUuijrIuNLOGhNy_5yNIq4pvNz4npsUwHaDtvrdCQ,7191
|
|
46
48
|
pymscada/misc.py,sha256=0Cj6OFhQonyhyk9x0BG5MiS-6EPk_w6zvavt8o_Hlf0,622
|
|
47
49
|
pymscada/pdf/__init__.py,sha256=WsDDgkWnZBJbt2-cJCdc2NvRAv_T4a7WOC1Q0k_l0gI,29
|
|
48
50
|
pymscada/pdf/one.pdf,sha256=eoJ45DrAjVZrwmwdA_EAz1fwmT44eRnt_tkc2pmMrKY,1488
|
|
@@ -50,9 +52,9 @@ pymscada/pdf/two.pdf,sha256=TAuW5yLU1_wfmTH_I5ezHwY0pxhCVuZh3ixu0kwmJwE,1516
|
|
|
50
52
|
pymscada/periodic.py,sha256=MLlL93VLvFqBBgjO1Us1t0aLHTZ5BFdW0B__G02T1nQ,1235
|
|
51
53
|
pymscada/protocol_constants.py,sha256=ndFeuzhWjKTr_ahvmGJc6Cs5pYkHM8CRgNNDe0Tqecs,1997
|
|
52
54
|
pymscada/samplers.py,sha256=t0IscgsCm5YByioOZ6aOKMO_guDFS_wxnJSiOGKI4Nw,2583
|
|
53
|
-
pymscada/simulate.py,sha256=39PfIc_jJC4ekuSEqlVb8SlxvIjwgyTkCL5fSlwmanc,2404
|
|
54
55
|
pymscada/tag.py,sha256=Q2EgacTnsxnGLM_zoHYsVWdwmd1faqfbxw9byI5fCgY,9463
|
|
56
|
+
pymscada/tools/snmp_client2.py,sha256=pdn5dYyEv4q-ubA0zQ8X-3tQDYxGC7f7Xexa7QPaL40,1675
|
|
55
57
|
pymscada/tools/walk.py,sha256=OgpprUbKLhEWMvJGfU1ckUt_PFEpwZVOD8HucCgzmOc,1625
|
|
56
|
-
pymscada/validate.py,sha256=
|
|
58
|
+
pymscada/validate.py,sha256=VPpAVEwfgori5OREEwWlbPoPxz5Tfqr6dw-O5pINHyI,13125
|
|
57
59
|
pymscada/www_server.py,sha256=BW6-ctE5BgyFDiyUJsOpe3c06DRp8k83MCGjwPh5fco,11473
|
|
58
|
-
pymscada-0.0.
|
|
60
|
+
pymscada-0.1.0.dist-info/RECORD,,
|
pymscada/demo/simulate.yaml
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
bus_ip: 127.0.0.1
|
|
2
|
-
bus_port: 1324
|
|
3
|
-
process:
|
|
4
|
-
one:
|
|
5
|
-
type: sine
|
|
6
|
-
offset: 50
|
|
7
|
-
amplitude: 40
|
|
8
|
-
frequency: 0.0005
|
|
9
|
-
sub:
|
|
10
|
-
offset: FloatSet
|
|
11
|
-
pub:
|
|
12
|
-
result: FloatVal
|
|
13
|
-
two:
|
|
14
|
-
type: sawtooth
|
|
15
|
-
offset: 10
|
|
16
|
-
amplitude: 80
|
|
17
|
-
frequency: 0.00005
|
|
18
|
-
sub:
|
|
19
|
-
offset: IntSet
|
|
20
|
-
pub:
|
|
21
|
-
result: IntVal
|
pymscada/simulate.py
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
"""Simulate logic."""
|
|
2
|
-
import asyncio
|
|
3
|
-
import logging
|
|
4
|
-
import math
|
|
5
|
-
from pymscada.bus_client import BusClient
|
|
6
|
-
from pymscada.misc import find_nodes
|
|
7
|
-
from pymscada.tag import Tag, TYPES
|
|
8
|
-
from time import monotonic
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class Simulate():
|
|
12
|
-
"""Modify tag values to simulate a real process."""
|
|
13
|
-
|
|
14
|
-
def __init__(self, bus_ip: str = '127.0.0.1', bus_port: int = 1324,
|
|
15
|
-
tag_info: dict = {}, process: dict = {}) -> None:
|
|
16
|
-
"""
|
|
17
|
-
Connect to bus on bus_ip:bus_port, modify tag values per process.
|
|
18
|
-
|
|
19
|
-
Event loop must be running.
|
|
20
|
-
"""
|
|
21
|
-
self.busclient = BusClient(bus_ip, bus_port, tag_info)
|
|
22
|
-
self.process = process
|
|
23
|
-
self.tags = {}
|
|
24
|
-
for x in ['sub', 'pub']: # resist the temptation to
|
|
25
|
-
for t in find_nodes(x, self.process):
|
|
26
|
-
for tagname in t[x].values():
|
|
27
|
-
self.tags[tagname] = Tag(
|
|
28
|
-
tagname, TYPES[tag_info[tagname]['type']])
|
|
29
|
-
|
|
30
|
-
def step(self):
|
|
31
|
-
"""Do simulation step."""
|
|
32
|
-
for e in self.process.values():
|
|
33
|
-
time = monotonic()
|
|
34
|
-
tagname = e['pub']['result']
|
|
35
|
-
if e['type'] == 'sine':
|
|
36
|
-
result = e['offset'] + e['amplitude'] * \
|
|
37
|
-
math.sin(e['frequency'] * time)
|
|
38
|
-
elif e['type'] == 'sawtooth':
|
|
39
|
-
period = 1 / e['frequency']
|
|
40
|
-
result = int(e['offset'] + e['amplitude'] * (time %
|
|
41
|
-
period) / period)
|
|
42
|
-
else:
|
|
43
|
-
return
|
|
44
|
-
if self.tags[tagname].value is None or \
|
|
45
|
-
abs(self.tags[tagname].value - result) > 0.01:
|
|
46
|
-
self.tags[tagname].value = result
|
|
47
|
-
logging.warning(f'simulate {tagname} {result}')
|
|
48
|
-
|
|
49
|
-
async def periodic(self):
|
|
50
|
-
"""Run simulation step every 5 seconds."""
|
|
51
|
-
while True:
|
|
52
|
-
self.next_run += 1.0
|
|
53
|
-
self.step()
|
|
54
|
-
self.last_ran = monotonic()
|
|
55
|
-
sleep_time = self.next_run - self.last_ran
|
|
56
|
-
if sleep_time < 0:
|
|
57
|
-
self.next_run = self.last_ran
|
|
58
|
-
logging.warning(f'Health count skipped at {self.last_ran}')
|
|
59
|
-
else:
|
|
60
|
-
await asyncio.sleep(sleep_time)
|
|
61
|
-
|
|
62
|
-
async def start(self):
|
|
63
|
-
"""Provide the simulation process."""
|
|
64
|
-
await self.busclient.start()
|
|
65
|
-
self.next_run = monotonic()
|
|
66
|
-
await asyncio.create_task(self.periodic())
|