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/main.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"""Main server entry point."""
|
|
2
2
|
import argparse
|
|
3
3
|
import asyncio
|
|
4
|
+
from importlib.metadata import version
|
|
4
5
|
import logging
|
|
6
|
+
import sys
|
|
5
7
|
from pymscada.bus_server import BusServer
|
|
6
8
|
from pymscada.checkout import checkout
|
|
7
9
|
from pymscada.config import Config
|
|
@@ -11,92 +13,258 @@ from pymscada.history import History
|
|
|
11
13
|
from pymscada.iodrivers.logix_client import LogixClient
|
|
12
14
|
from pymscada.iodrivers.modbus_client import ModbusClient
|
|
13
15
|
from pymscada.iodrivers.modbus_server import ModbusServer
|
|
16
|
+
from pymscada.iodrivers.ping_client import PingClient
|
|
14
17
|
from pymscada.iodrivers.snmp_client import SnmpClient
|
|
15
|
-
from pymscada.simulate import Simulate
|
|
16
18
|
from pymscada.www_server import WwwServer
|
|
17
19
|
from pymscada.validate import validate
|
|
18
20
|
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
"""
|
|
22
|
-
parser = argparse.ArgumentParser(
|
|
23
|
-
prog='pymscada',
|
|
24
|
-
description='Connect IO, logic, applications and webpage UI.',
|
|
25
|
-
epilog='Python MobileSCADA.'
|
|
26
|
-
)
|
|
27
|
-
commands = ['bus', 'console', 'wwwserver', 'history', 'files',
|
|
28
|
-
'logixclient',
|
|
29
|
-
'modbusserver', 'modbusclient',
|
|
30
|
-
'snmpclient',
|
|
31
|
-
'simulate', 'checkout',
|
|
32
|
-
'validate']
|
|
33
|
-
parser.add_argument('module', type=str, choices=commands, metavar='action',
|
|
34
|
-
help=f'select one of: {", ".join(commands)}')
|
|
35
|
-
parser.add_argument('--config', metavar='file',
|
|
36
|
-
help='Config file, default is "[module].yaml".')
|
|
37
|
-
parser.add_argument('--tags', metavar='file',
|
|
38
|
-
help='Tags file, default is "tags.yaml".')
|
|
39
|
-
parser.add_argument('--verbose', action='store_true',
|
|
40
|
-
help="Set level to logging.INFO.")
|
|
41
|
-
parser.add_argument('--path', metavar='folder',
|
|
42
|
-
help="Working folder, used for history and validate.")
|
|
43
|
-
parser.add_argument('--overwrite', action='store_true', default=False,
|
|
44
|
-
help='checkout may overwrite files, CARE!')
|
|
45
|
-
return parser.parse_args()
|
|
22
|
+
class Module():
|
|
23
|
+
"""Default Module."""
|
|
46
24
|
|
|
25
|
+
name = None
|
|
26
|
+
help = None
|
|
27
|
+
epilog = None
|
|
28
|
+
module = None
|
|
29
|
+
config = True
|
|
30
|
+
tags = True
|
|
31
|
+
sub = None
|
|
47
32
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
33
|
+
def __init__(self, subparser: argparse._SubParsersAction):
|
|
34
|
+
"""Add arguments common to all subparsers."""
|
|
35
|
+
self.sub = subparser.add_parser(
|
|
36
|
+
self.name, help=self.help, epilog=self.epilog)
|
|
37
|
+
self.sub.set_defaults(app=self)
|
|
38
|
+
if self.config:
|
|
39
|
+
self.sub.add_argument(
|
|
40
|
+
'--config', metavar='file', default=f'{self.name}.yaml',
|
|
41
|
+
help=f"Config file, default is '{self.name}.yaml'")
|
|
42
|
+
if self.tags:
|
|
43
|
+
self.sub.add_argument(
|
|
44
|
+
'--tags', metavar='file', default='tags.yaml',
|
|
45
|
+
help="Tags file, default is 'tags.yaml'")
|
|
46
|
+
self.sub.add_argument('--verbose', action='store_true',
|
|
47
|
+
help="Set level to logging.INFO")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class _Bus(Module):
|
|
51
|
+
"""Bus Server."""
|
|
52
|
+
|
|
53
|
+
name = 'bus'
|
|
54
|
+
help = 'run the message bus'
|
|
55
|
+
tags = False
|
|
56
|
+
|
|
57
|
+
def run_once(self, options):
|
|
58
|
+
"""Create the module."""
|
|
58
59
|
config = Config(options.config)
|
|
59
|
-
module = BusServer(**config)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
self.module = BusServer(**config)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class _WwwServer(Module):
|
|
64
|
+
"""WWW Server Module."""
|
|
65
|
+
|
|
66
|
+
name = 'wwwserver'
|
|
67
|
+
help = 'serve web pages'
|
|
68
|
+
|
|
69
|
+
def run_once(self, options):
|
|
70
|
+
"""Create the module."""
|
|
63
71
|
config = Config(options.config)
|
|
64
72
|
tag_info = dict(Config(options.tags))
|
|
65
|
-
module = WwwServer(tag_info=tag_info, **config)
|
|
66
|
-
|
|
73
|
+
self.module = WwwServer(tag_info=tag_info, **config)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class _History(Module):
|
|
77
|
+
"""History Module."""
|
|
78
|
+
|
|
79
|
+
name = 'history'
|
|
80
|
+
help = 'collect and serve history'
|
|
81
|
+
|
|
82
|
+
def run_once(self, options):
|
|
83
|
+
"""Create the module."""
|
|
67
84
|
config = Config(options.config)
|
|
68
85
|
tag_info = dict(Config(options.tags))
|
|
69
|
-
module = History(tag_info=tag_info, **config)
|
|
70
|
-
|
|
86
|
+
self.module = History(tag_info=tag_info, **config)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class _Files(Module):
|
|
90
|
+
"""Bus Module."""
|
|
91
|
+
|
|
92
|
+
name = 'files'
|
|
93
|
+
help = 'receive and send files'
|
|
94
|
+
tags = False
|
|
95
|
+
|
|
96
|
+
def run_once(self, options):
|
|
97
|
+
"""Create the module."""
|
|
71
98
|
config = Config(options.config)
|
|
72
|
-
module = Files(**config)
|
|
73
|
-
|
|
99
|
+
self.module = Files(**config)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class _Console(Module):
|
|
103
|
+
"""Bus Module."""
|
|
104
|
+
|
|
105
|
+
name = 'console'
|
|
106
|
+
help = 'interactive bus console'
|
|
107
|
+
config = False
|
|
108
|
+
tags = False
|
|
109
|
+
|
|
110
|
+
def run_once(self, _options):
|
|
111
|
+
"""Create the module."""
|
|
112
|
+
self.module = Console()
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class _checkout(Module):
|
|
116
|
+
"""Bus Module."""
|
|
117
|
+
|
|
118
|
+
name = 'checkout'
|
|
119
|
+
help = 'create example config files'
|
|
120
|
+
epilog = """
|
|
121
|
+
To add to systemd `f="pymscada-bus" && cp config/$f.service
|
|
122
|
+
/lib/systemd/system && systemctl enable $f && systemctl start
|
|
123
|
+
$f`"""
|
|
124
|
+
config = False
|
|
125
|
+
tags = False
|
|
126
|
+
|
|
127
|
+
def __init__(self, subparser: argparse._SubParsersAction):
|
|
128
|
+
super().__init__(subparser)
|
|
129
|
+
self.sub.add_argument(
|
|
130
|
+
'--overwrite', action='store_true', default=False,
|
|
131
|
+
help='checkout may overwrite files, CARE!')
|
|
132
|
+
self.sub.add_argument(
|
|
133
|
+
'--diff', action='store_true', default=False,
|
|
134
|
+
help='compare default with existing')
|
|
135
|
+
|
|
136
|
+
def run_once(self, options):
|
|
137
|
+
"""Create the module."""
|
|
138
|
+
checkout(overwrite=options.overwrite, diff=options.diff)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class _validate(Module):
|
|
142
|
+
"""Bus Module."""
|
|
143
|
+
|
|
144
|
+
name = 'validate'
|
|
145
|
+
help = 'validate config files'
|
|
146
|
+
config = False
|
|
147
|
+
tags = False
|
|
148
|
+
|
|
149
|
+
def __init__(self, subparser: argparse._SubParsersAction):
|
|
150
|
+
super().__init__(subparser)
|
|
151
|
+
self.sub.add_argument(
|
|
152
|
+
'--path', metavar='file',
|
|
153
|
+
help='default is current working directory')
|
|
154
|
+
|
|
155
|
+
def run_once(self, options):
|
|
156
|
+
"""Create the module."""
|
|
157
|
+
r, e, p = validate(options.path)
|
|
158
|
+
if r:
|
|
159
|
+
print(f'Config files in {p} valid.')
|
|
160
|
+
else:
|
|
161
|
+
print(e)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class _ModbusServer(Module):
|
|
165
|
+
"""Bus Module."""
|
|
166
|
+
|
|
167
|
+
name = 'modbusserver'
|
|
168
|
+
help = 'receive modbus messages'
|
|
169
|
+
epilog = """
|
|
170
|
+
Needs `setcap CAP_NET_BIND_SERVICE=+eip /usr/bin/python3.nn` to
|
|
171
|
+
bind to port 502."""
|
|
172
|
+
tags = False
|
|
173
|
+
|
|
174
|
+
def run_once(self, options):
|
|
175
|
+
"""Create the module."""
|
|
74
176
|
config = Config(options.config)
|
|
75
|
-
module =
|
|
76
|
-
|
|
177
|
+
self.module = ModbusServer(**config)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class _ModbusClient(Module):
|
|
181
|
+
"""Bus Module."""
|
|
182
|
+
|
|
183
|
+
name = 'modbusclient'
|
|
184
|
+
help = 'poll/write to modbus devices'
|
|
185
|
+
tags = False
|
|
186
|
+
|
|
187
|
+
def run_once(self, options):
|
|
188
|
+
"""Create the module."""
|
|
77
189
|
config = Config(options.config)
|
|
78
|
-
module = ModbusClient(**config)
|
|
79
|
-
|
|
190
|
+
self.module = ModbusClient(**config)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class _LogixClient(Module):
|
|
194
|
+
"""Bus Module."""
|
|
195
|
+
|
|
196
|
+
name = 'logixclient'
|
|
197
|
+
help = 'poll/write to logix devices'
|
|
198
|
+
|
|
199
|
+
def run_once(self, options):
|
|
200
|
+
"""Create the module."""
|
|
80
201
|
config = Config(options.config)
|
|
81
|
-
module =
|
|
82
|
-
|
|
202
|
+
self.module = LogixClient(**config)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class _PingClient(Module):
|
|
206
|
+
"""Bus Module."""
|
|
207
|
+
|
|
208
|
+
name = 'ping'
|
|
209
|
+
help = 'ping a list of addresses, return time'
|
|
210
|
+
epilog = """
|
|
211
|
+
Needs `setcap CAP_NET_RAW+ep /usr/bin/python3.nn` to open SOCK_RAW
|
|
212
|
+
"""
|
|
213
|
+
tags = False
|
|
214
|
+
|
|
215
|
+
def run_once(self, options):
|
|
216
|
+
"""Create the module."""
|
|
217
|
+
if sys.platform.startswith("win"):
|
|
218
|
+
asyncio.set_event_loop_policy(
|
|
219
|
+
asyncio.WindowsSelectorEventLoopPolicy())
|
|
83
220
|
config = Config(options.config)
|
|
84
|
-
module =
|
|
85
|
-
|
|
221
|
+
self.module = PingClient(**config)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class _SnmpClient(Module):
|
|
225
|
+
"""Bus Module."""
|
|
226
|
+
|
|
227
|
+
name = 'snmpclient'
|
|
228
|
+
help = 'poll snmp oids'
|
|
229
|
+
tags = False
|
|
230
|
+
|
|
231
|
+
def run_once(self, options):
|
|
232
|
+
"""Create the module."""
|
|
86
233
|
config = Config(options.config)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
234
|
+
self.module = SnmpClient(**config)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def args(_version: str):
|
|
238
|
+
"""Read commandline arguments."""
|
|
239
|
+
parser = argparse.ArgumentParser(
|
|
240
|
+
prog='pymscada',
|
|
241
|
+
description='Connect IO, logic, applications, and webpage UI',
|
|
242
|
+
epilog=f'Python Mobile SCADA {_version}'
|
|
243
|
+
)
|
|
244
|
+
s = parser.add_subparsers(title='module')
|
|
245
|
+
_Bus(s)
|
|
246
|
+
_WwwServer(s)
|
|
247
|
+
_History(s)
|
|
248
|
+
_Files(s)
|
|
249
|
+
_Console(s)
|
|
250
|
+
_checkout(s)
|
|
251
|
+
_validate(s)
|
|
252
|
+
_ModbusServer(s)
|
|
253
|
+
_ModbusClient(s)
|
|
254
|
+
_LogixClient(s)
|
|
255
|
+
_SnmpClient(s)
|
|
256
|
+
_PingClient(s)
|
|
257
|
+
return parser.parse_args()
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
async def run():
|
|
261
|
+
"""Run bus and wwwserver."""
|
|
262
|
+
_version = version("pymscada")
|
|
263
|
+
logging.warning(f'pymscada {_version} starting')
|
|
264
|
+
options = args(_version)
|
|
265
|
+
if options.verbose:
|
|
266
|
+
logging.getLogger().setLevel(logging.INFO)
|
|
267
|
+
options.app.run_once(options)
|
|
268
|
+
if options.app.module is not None:
|
|
269
|
+
await options.app.module.start()
|
|
270
|
+
await asyncio.get_event_loop().create_future()
|
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
|