pymscada 0.1.11__tar.gz → 0.1.11b3__tar.gz
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.
- {pymscada-0.1.11 → pymscada-0.1.11b3}/PKG-INFO +3 -3
- {pymscada-0.1.11 → pymscada-0.1.11b3}/pyproject.toml +5 -2
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/bus_client.py +23 -22
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/bus_server.py +28 -25
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/checkout.py +6 -10
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/console.py +11 -14
- pymscada-0.1.11b3/src/pymscada/demo/openweather.yaml +25 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/ping.yaml +0 -3
- pymscada-0.1.11b3/src/pymscada/demo/tags.yaml +119 -0
- pymscada-0.1.11b3/src/pymscada/demo/wwwserver.yaml +81 -0
- pymscada-0.1.11b3/src/pymscada/iodrivers/openweather.py +126 -0
- pymscada-0.1.11b3/src/pymscada/main.py +57 -0
- pymscada-0.1.11b3/src/pymscada/module_config.py +217 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/protocol_constants.py +29 -39
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/validate.py +29 -26
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/www_server.py +4 -4
- {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/test_assets/db.sqlite +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/test_bus_server.py +13 -13
- pymscada-0.1.11b3/tests/test_openweather.py +47 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/test_opnotes.py +10 -14
- {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/test_validate.py +2 -2
- pymscada-0.1.11/src/pymscada/demo/tags.yaml +0 -265
- pymscada-0.1.11/src/pymscada/demo/wwwserver.yaml +0 -536
- pymscada-0.1.11/src/pymscada/main.py +0 -311
- {pymscada-0.1.11 → pymscada-0.1.11b3}/LICENSE +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/README.md +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/__init__.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/__main__.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/config.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/README.md +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/__init__.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/accuweather.yaml +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/bus.yaml +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/files.yaml +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/history.yaml +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/logixclient.yaml +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/modbus_plc.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/modbusclient.yaml +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/modbusserver.yaml +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/opnotes.yaml +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/pymscada-bus.service +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/pymscada-demo-modbus_plc.service +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/pymscada-files.service +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/pymscada-history.service +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/pymscada-io-accuweather.service +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/pymscada-io-logixclient.service +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/pymscada-io-modbusclient.service +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/pymscada-io-modbusserver.service +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/pymscada-io-ping.service +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/pymscada-io-snmpclient.service +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/pymscada-opnotes.service +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/pymscada-wwwserver.service +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/snmpclient.yaml +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/files.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/history.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/iodrivers/__init__.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/iodrivers/accuweather.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/iodrivers/logix_client.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/iodrivers/logix_map.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/iodrivers/modbus_client.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/iodrivers/modbus_map.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/iodrivers/modbus_server.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/iodrivers/ping_client.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/iodrivers/ping_map.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/iodrivers/snmp_client.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/iodrivers/snmp_map.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/misc.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/opnotes.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/pdf/__init__.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/pdf/one.pdf +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/pdf/two.pdf +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/periodic.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/samplers.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/tag.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/tools/snmp_client2.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/tools/walk.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/__init__.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/bus_echo.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/iodrivers/test_logix.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/iodrivers/test_modbus.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/test_assets/busserver.yaml +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/test_assets/hist_tag_0_0.dat +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/test_assets/hist_tag_0_10_2.dat +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/test_assets/hist_tag_0_15.dat +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/test_assets/hist_tag_0_26.dat +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/test_assets/hist_tag_0_50.dat +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/test_config.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/test_history.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/test_misc.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/test_periodic.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/test_samplers.py +0 -0
- {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/test_tag.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pymscada
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.11b3
|
|
4
4
|
Summary: Shared tag value SCADA with python backup and Angular UI
|
|
5
5
|
Author-Email: Jamie Walton <jamie@walton.net.nz>
|
|
6
6
|
License: GPL-3.0-or-later
|
|
@@ -11,11 +11,11 @@ Classifier: Operating System :: OS Independent
|
|
|
11
11
|
Classifier: Environment :: Console
|
|
12
12
|
Classifier: Development Status :: 1 - Planning
|
|
13
13
|
Project-URL: Homepage, https://github.com/jamie0walton/pymscada
|
|
14
|
-
Project-URL: Bug
|
|
14
|
+
Project-URL: Bug Tracker, https://github.com/jamie0walton/pymscada/issues
|
|
15
15
|
Requires-Python: >=3.9
|
|
16
16
|
Requires-Dist: PyYAML>=6.0.1
|
|
17
17
|
Requires-Dist: aiohttp>=3.8.5
|
|
18
|
-
Requires-Dist: pymscada-html>=0.1.
|
|
18
|
+
Requires-Dist: pymscada-html>=0.1.10b2
|
|
19
19
|
Requires-Dist: cerberus>=1.3.5
|
|
20
20
|
Requires-Dist: pycomm3>=1.2.14
|
|
21
21
|
Requires-Dist: pysnmplib>=5.0.24
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pymscada"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.11b3"
|
|
4
4
|
description = "Shared tag value SCADA with python backup and Angular UI"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Jamie Walton", email = "jamie@walton.net.nz" },
|
|
@@ -8,7 +8,7 @@ authors = [
|
|
|
8
8
|
dependencies = [
|
|
9
9
|
"PyYAML>=6.0.1",
|
|
10
10
|
"aiohttp>=3.8.5",
|
|
11
|
-
"pymscada-html>=0.1.
|
|
11
|
+
"pymscada-html>=0.1.10b2",
|
|
12
12
|
"cerberus>=1.3.5",
|
|
13
13
|
"pycomm3>=1.2.14",
|
|
14
14
|
"pysnmplib>=5.0.24",
|
|
@@ -54,3 +54,6 @@ pdm = []
|
|
|
54
54
|
omit = [
|
|
55
55
|
"tests/*",
|
|
56
56
|
]
|
|
57
|
+
|
|
58
|
+
[tool.pytest.ini_options]
|
|
59
|
+
addopts = "-v -s"
|
|
@@ -42,26 +42,26 @@ class BusClient:
|
|
|
42
42
|
self.to_publish[tag.name] = tag
|
|
43
43
|
return
|
|
44
44
|
if tag.type is float:
|
|
45
|
-
data = struct.pack('!Bd', pc.
|
|
45
|
+
data = struct.pack('!Bd', pc.TYPE.FLOAT, tag.value)
|
|
46
46
|
elif tag.type is int:
|
|
47
|
-
data = struct.pack('!Bq', pc.
|
|
47
|
+
data = struct.pack('!Bq', pc.TYPE.INT, tag.value)
|
|
48
48
|
elif tag.type is bytes:
|
|
49
49
|
size = len(tag.value)
|
|
50
50
|
try:
|
|
51
|
-
data = struct.pack(f'!B{size}s', pc.
|
|
51
|
+
data = struct.pack(f'!B{size}s', pc.TYPE.BYTES, tag.value)
|
|
52
52
|
except struct.error as e:
|
|
53
53
|
logging.error(f'bus_client {tag.name} {e}')
|
|
54
54
|
elif tag.type is str:
|
|
55
55
|
size = len(tag.value)
|
|
56
|
-
data = struct.pack(f'!B{size}s', pc.
|
|
56
|
+
data = struct.pack(f'!B{size}s', pc.TYPE.STR, tag.value.encode())
|
|
57
57
|
elif tag.type in [list, dict]:
|
|
58
58
|
jsonstr = json.dumps(tag.value).encode()
|
|
59
59
|
size = len(jsonstr)
|
|
60
|
-
data = struct.pack(f'!B{size}s', pc.
|
|
60
|
+
data = struct.pack(f'!B{size}s', pc.TYPE.JSON, jsonstr)
|
|
61
61
|
else:
|
|
62
62
|
logging.warning(f'publish {tag.name} unhandled {tag.type}')
|
|
63
63
|
return
|
|
64
|
-
self.write(pc.
|
|
64
|
+
self.write(pc.COMMAND.SET, tag.id, tag.time_us, data)
|
|
65
65
|
|
|
66
66
|
def add_callback_rta(self, tagname, handler):
|
|
67
67
|
"""Collect callback handlers."""
|
|
@@ -75,10 +75,10 @@ class BusClient:
|
|
|
75
75
|
time_us = int(time.time() * 1e6)
|
|
76
76
|
jsonstr = json.dumps(request).encode()
|
|
77
77
|
size = len(jsonstr)
|
|
78
|
-
data = struct.pack(f'>B{size}s', pc.
|
|
79
|
-
self.write(pc.
|
|
78
|
+
data = struct.pack(f'>B{size}s', pc.TYPE.JSON, jsonstr)
|
|
79
|
+
self.write(pc.COMMAND.RTA, self.tag_by_name[tagname].id, time_us, data)
|
|
80
80
|
|
|
81
|
-
def write(self, command: pc.
|
|
81
|
+
def write(self, command: pc.COMMAND, tag_id: int, time_us: int,
|
|
82
82
|
data: bytes):
|
|
83
83
|
"""Write a message."""
|
|
84
84
|
if data is None:
|
|
@@ -101,14 +101,14 @@ class BusClient:
|
|
|
101
101
|
self.tag_by_name[tag.name] = tag
|
|
102
102
|
if tag.value is not None:
|
|
103
103
|
self.to_publish[tag.name] = tag
|
|
104
|
-
self.write(pc.
|
|
104
|
+
self.write(pc.COMMAND.ID, 0, 0, tag.name.encode())
|
|
105
105
|
|
|
106
106
|
async def open_connection(self):
|
|
107
107
|
"""Establish connection and callbacks."""
|
|
108
108
|
self.reader, self.writer = await asyncio.open_connection(
|
|
109
109
|
self.ip, self.port)
|
|
110
110
|
self.addr = self.writer.get_extra_info('sockname')
|
|
111
|
-
self.write(pc.
|
|
111
|
+
self.write(pc.COMMAND.LOG, 0, 0, f'{self.module} connected'.encode())
|
|
112
112
|
logging.warning(f'connected {self.addr} {self.port}')
|
|
113
113
|
for tag in Tag.get_all_tags().values():
|
|
114
114
|
self.add_tag(tag)
|
|
@@ -157,14 +157,15 @@ class BusClient:
|
|
|
157
157
|
|
|
158
158
|
def process(self, cmd, tag_id, time_us, value):
|
|
159
159
|
"""Process bus message, updating the local tag value."""
|
|
160
|
-
if cmd == pc.
|
|
161
|
-
logging.warning(f'Bus server error {tag_id}
|
|
160
|
+
if cmd == pc.COMMAND.ERR:
|
|
161
|
+
logging.warning(f'Bus server error {tag_id} '
|
|
162
|
+
f'{pc.COMMAND.text(cmd)} {value}')
|
|
162
163
|
return
|
|
163
|
-
if cmd == pc.
|
|
164
|
+
if cmd == pc.COMMAND.ID:
|
|
164
165
|
tag = self.tag_by_name[value.decode()]
|
|
165
166
|
tag.id = tag_id
|
|
166
167
|
self.tag_by_id[tag_id] = tag
|
|
167
|
-
self.write(pc.
|
|
168
|
+
self.write(pc.COMMAND.SUB, tag.id, 0, b'')
|
|
168
169
|
if tag.name in self.tag_by_name:
|
|
169
170
|
self.tag_by_id[tag.id] = tag
|
|
170
171
|
if tag.name in self.to_publish:
|
|
@@ -172,7 +173,7 @@ class BusClient:
|
|
|
172
173
|
del self.to_publish[tag.name]
|
|
173
174
|
return
|
|
174
175
|
tag = self.tag_by_id[tag_id]
|
|
175
|
-
if cmd == pc.
|
|
176
|
+
if cmd == pc.COMMAND.SET:
|
|
176
177
|
if value is None:
|
|
177
178
|
try:
|
|
178
179
|
if self.tag_info is None:
|
|
@@ -186,17 +187,17 @@ class BusClient:
|
|
|
186
187
|
pass
|
|
187
188
|
return
|
|
188
189
|
data_type = struct.unpack_from('!B', value, offset=0)[0]
|
|
189
|
-
if data_type == pc.
|
|
190
|
+
if data_type == pc.TYPE.FLOAT:
|
|
190
191
|
data = struct.unpack_from('!d', value, offset=1)[0]
|
|
191
|
-
elif data_type == pc.
|
|
192
|
+
elif data_type == pc.TYPE.INT:
|
|
192
193
|
data = struct.unpack_from('!q', value, offset=1)[0]
|
|
193
|
-
elif data_type == pc.
|
|
194
|
+
elif data_type == pc.TYPE.BYTES:
|
|
194
195
|
data = struct.unpack_from(f'!{len(value) - 1}s', value,
|
|
195
196
|
offset=1)[0]
|
|
196
|
-
elif data_type == pc.
|
|
197
|
+
elif data_type == pc.TYPE.STR:
|
|
197
198
|
data = struct.unpack_from(f'!{len(value) - 1}s', value,
|
|
198
199
|
offset=1)[0].decode()
|
|
199
|
-
elif data_type == pc.
|
|
200
|
+
elif data_type == pc.TYPE.JSON:
|
|
200
201
|
data = json.loads(struct.unpack_from(f'!{len(value) - 1}s',
|
|
201
202
|
value, offset=1
|
|
202
203
|
)[0].decode())
|
|
@@ -204,7 +205,7 @@ class BusClient:
|
|
|
204
205
|
logging.warning(f'process error {tag.name} {tag.type} {value}')
|
|
205
206
|
return
|
|
206
207
|
tag.value = data, time_us, id(self)
|
|
207
|
-
elif cmd == pc.
|
|
208
|
+
elif cmd == pc.COMMAND.RTA:
|
|
208
209
|
data = struct.unpack_from(f'!{len(value) - 1}s', value, offset=1
|
|
209
210
|
)[0].decode()
|
|
210
211
|
data = json.loads(data)
|
|
@@ -85,12 +85,12 @@ class BusConnection():
|
|
|
85
85
|
del self.addr
|
|
86
86
|
del self.pending
|
|
87
87
|
|
|
88
|
-
def write(self, command: pc.
|
|
88
|
+
def write(self, command: pc.COMMAND, tag_id: int, time_us: int,
|
|
89
89
|
data: bytes):
|
|
90
90
|
"""Write a message."""
|
|
91
91
|
if data is None:
|
|
92
92
|
data = b''
|
|
93
|
-
logging.info(f'write {
|
|
93
|
+
logging.info(f'write {command.text} {tag_id}')
|
|
94
94
|
for i in range(0, len(data) + 1, pc.MAX_LEN):
|
|
95
95
|
snip = data[i:i+pc.MAX_LEN]
|
|
96
96
|
size = len(snip)
|
|
@@ -168,78 +168,78 @@ class BusServer:
|
|
|
168
168
|
if tag.from_bus == bus_id:
|
|
169
169
|
return
|
|
170
170
|
try:
|
|
171
|
-
self.connections[bus_id].write(pc.
|
|
171
|
+
self.connections[bus_id].write(pc.COMMAND.SET, tag.id, tag.time_us,
|
|
172
172
|
tag.value)
|
|
173
173
|
except KeyError:
|
|
174
174
|
tag.del_callback(self.publish, bus_id)
|
|
175
175
|
|
|
176
|
-
def process(self, bus_id, cmd, tag_id, time_us, data):
|
|
176
|
+
def process(self, bus_id, cmd: int, tag_id, time_us, data):
|
|
177
177
|
"""Process bus message, updating the local tag value."""
|
|
178
|
-
logging.info(f'write {pc.
|
|
178
|
+
logging.info(f'write {pc.COMMAND.text(cmd)} {tag_id} '
|
|
179
179
|
f'{"None" if data is None else data[:20]}')
|
|
180
|
-
if cmd == pc.
|
|
180
|
+
if cmd == pc.COMMAND.SET:
|
|
181
181
|
try:
|
|
182
182
|
tag = BusTags._tag_by_id[tag_id]
|
|
183
183
|
tag.update(data, time_us, bus_id)
|
|
184
184
|
except KeyError:
|
|
185
185
|
self.connections[bus_id].write(
|
|
186
|
-
pc.
|
|
186
|
+
pc.COMMAND.ERR, tag_id, time_us,
|
|
187
187
|
f"SET KeyError {tag_id}".encode())
|
|
188
|
-
elif cmd == pc.
|
|
188
|
+
elif cmd == pc.COMMAND.RTA:
|
|
189
189
|
try:
|
|
190
190
|
tag = BusTags._tag_by_id[tag_id]
|
|
191
191
|
except KeyError:
|
|
192
192
|
self.connections[bus_id].write(
|
|
193
|
-
pc.
|
|
193
|
+
pc.COMMAND.ERR, tag_id, time_us,
|
|
194
194
|
f"RTA KeyError {tag_id}".encode())
|
|
195
195
|
try:
|
|
196
196
|
self.connections[tag.from_bus].write(
|
|
197
|
-
pc.
|
|
197
|
+
pc.COMMAND.RTA, tag_id, tag.time_us, data)
|
|
198
198
|
except KeyError:
|
|
199
199
|
logging.warning(f'likely busclient for {tag.name} is gone')
|
|
200
200
|
except Exception as e:
|
|
201
201
|
self.connections[bus_id].write(
|
|
202
|
-
pc.
|
|
202
|
+
pc.COMMAND.ERR, tag_id, time_us,
|
|
203
203
|
f"RTA {tag_id} {e}".encode())
|
|
204
204
|
"""Reply comes from another BusClient, not the Server."""
|
|
205
|
-
elif cmd == pc.
|
|
205
|
+
elif cmd == pc.COMMAND.SUB:
|
|
206
206
|
try:
|
|
207
207
|
tag = BusTags._tag_by_id[tag_id]
|
|
208
208
|
except KeyError:
|
|
209
209
|
self.connections[bus_id].write(
|
|
210
|
-
pc.
|
|
210
|
+
pc.COMMAND.ERR, tag_id, time_us,
|
|
211
211
|
f"SUBscribe KeyError {tag_id}".encode())
|
|
212
|
-
self.connections[bus_id].write(pc.
|
|
212
|
+
self.connections[bus_id].write(pc.COMMAND.SET, tag_id, tag.time_us,
|
|
213
213
|
tag.value)
|
|
214
214
|
tag.add_callback(self.publish, bus_id)
|
|
215
|
-
elif cmd == pc.
|
|
215
|
+
elif cmd == pc.COMMAND.ID:
|
|
216
216
|
try:
|
|
217
217
|
tag = BusTags._tag_by_name[data]
|
|
218
218
|
except KeyError:
|
|
219
219
|
self.connections[bus_id].write(
|
|
220
|
-
pc.
|
|
220
|
+
pc.COMMAND.ERR, tag_id, time_us,
|
|
221
221
|
f"ID {data} undefined".encode())
|
|
222
222
|
tag = BusTag(data)
|
|
223
|
-
self.connections[bus_id].write(pc.
|
|
223
|
+
self.connections[bus_id].write(pc.COMMAND.ID, tag.id, tag.time_us,
|
|
224
224
|
tag.name)
|
|
225
|
-
elif cmd == pc.
|
|
225
|
+
elif cmd == pc.COMMAND.GET:
|
|
226
226
|
try:
|
|
227
227
|
tag = BusTags._tag_by_id[tag_id]
|
|
228
228
|
except KeyError:
|
|
229
229
|
self.connections[bus_id].write(
|
|
230
|
-
pc.
|
|
230
|
+
pc.COMMAND.ERR, tag_id, time_us,
|
|
231
231
|
f"GET KeyError for {tag_id}".encode())
|
|
232
|
-
self.connections[bus_id].write(pc.
|
|
232
|
+
self.connections[bus_id].write(pc.COMMAND.SET, tag.id, tag.time_us,
|
|
233
233
|
tag.value)
|
|
234
|
-
elif cmd == pc.
|
|
234
|
+
elif cmd == pc.COMMAND.UNSUB:
|
|
235
235
|
try:
|
|
236
236
|
tag = BusTags._tag_by_id[tag_id]
|
|
237
237
|
except KeyError:
|
|
238
238
|
self.connections[bus_id].write(
|
|
239
|
-
pc.
|
|
239
|
+
pc.COMMAND.ERR, tag_id, time_us,
|
|
240
240
|
f"UNSubscribe KeyError for {tag_id}".encode())
|
|
241
241
|
tag.del_callback(self.publish, bus_id)
|
|
242
|
-
elif cmd == pc.
|
|
242
|
+
elif cmd == pc.COMMAND.LIST:
|
|
243
243
|
tagname_list = []
|
|
244
244
|
if len(data) == 0:
|
|
245
245
|
for _, tag in BusTags._tag_by_id.items():
|
|
@@ -260,8 +260,8 @@ class BusServer:
|
|
|
260
260
|
if data in tag.name:
|
|
261
261
|
tagname_list.append(tag.name)
|
|
262
262
|
self.connections[bus_id].write(
|
|
263
|
-
pc.
|
|
264
|
-
elif cmd == pc.
|
|
263
|
+
pc.COMMAND.LIST, 0, time_us, b' '.join(tagname_list))
|
|
264
|
+
elif cmd == pc.COMMAND.LOG:
|
|
265
265
|
if len(data) > 300:
|
|
266
266
|
logging.warning(f'process: log message too long from {bus_id}')
|
|
267
267
|
else:
|
|
@@ -273,6 +273,9 @@ class BusServer:
|
|
|
273
273
|
"""Process read messages, delete broken connections."""
|
|
274
274
|
bus_id, cmd, tag_id, time_us, data = command
|
|
275
275
|
if cmd is None:
|
|
276
|
+
# Clean up tag subscriptions before deleting it
|
|
277
|
+
for tag in BusTags._tag_by_id.values():
|
|
278
|
+
tag.del_callback(self.publish, bus_id)
|
|
276
279
|
self.connections[bus_id].delete()
|
|
277
280
|
del self.connections[bus_id]
|
|
278
281
|
return
|
|
@@ -57,21 +57,17 @@ def make_config(overwrite: bool):
|
|
|
57
57
|
else:
|
|
58
58
|
continue
|
|
59
59
|
print(f'{rt} {target}')
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
target.write_bytes(rd_bytes)
|
|
65
|
-
else:
|
|
66
|
-
target.write_bytes(config_file.read_bytes())
|
|
60
|
+
rd_bytes = config_file.read_bytes()
|
|
61
|
+
for k, v in PATH.items():
|
|
62
|
+
rd_bytes = rd_bytes.replace(k.encode(), str(v).encode())
|
|
63
|
+
target.write_bytes(rd_bytes)
|
|
67
64
|
|
|
68
65
|
|
|
69
66
|
def read_with_subst(file: Path):
|
|
70
67
|
"""Read the file and replace DIR markers."""
|
|
71
68
|
rd = file.read_bytes().decode()
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
rd = rd.replace(k, str(v))
|
|
69
|
+
for k, v in PATH.items():
|
|
70
|
+
rd = rd.replace(k, str(v))
|
|
75
71
|
lines = rd.splitlines()
|
|
76
72
|
return lines
|
|
77
73
|
|
|
@@ -13,7 +13,6 @@ except ModuleNotFoundError:
|
|
|
13
13
|
|
|
14
14
|
class EC:
|
|
15
15
|
"""Escape codes."""
|
|
16
|
-
|
|
17
16
|
backspace = b'\x7f'
|
|
18
17
|
enter = b'\r'
|
|
19
18
|
tab = b'\t'
|
|
@@ -59,17 +58,14 @@ class KeypressProtocol(asyncio.Protocol):
|
|
|
59
58
|
def data_received(self, data):
|
|
60
59
|
"""Got keypress, update edit line, send to writer."""
|
|
61
60
|
if len(data) == 1:
|
|
62
|
-
if data == EC.backspace:
|
|
61
|
+
if data == EC.backspace and self.cursor > 0:
|
|
63
62
|
self.line = self.line[:self.cursor-1] + self.line[self.cursor:]
|
|
64
|
-
|
|
65
|
-
self.cursor -= 1
|
|
63
|
+
self.cursor -= 1
|
|
66
64
|
elif data == EC.enter:
|
|
67
|
-
self.
|
|
68
|
-
|
|
69
|
-
if self.line != self.lines[-1]:
|
|
70
|
-
self.lines.append(self.line)
|
|
71
|
-
else:
|
|
65
|
+
if self.line and (not self.lines or
|
|
66
|
+
self.line != self.lines[-1]):
|
|
72
67
|
self.lines.append(self.line)
|
|
68
|
+
self.stash = None
|
|
73
69
|
self.edit_line(None, 0)
|
|
74
70
|
self.process_command(self.line)
|
|
75
71
|
self.line = None
|
|
@@ -129,11 +125,12 @@ class ConsoleWriter:
|
|
|
129
125
|
|
|
130
126
|
def write(self, data: bytes):
|
|
131
127
|
"""Stream writer, primarily for logging."""
|
|
128
|
+
cursor_str = b''
|
|
129
|
+
if self.edit is not None and self.cursor > 0:
|
|
130
|
+
cursor_str = b'\x1b[' + str(self.cursor).encode() + b'C'
|
|
132
131
|
ln = EC.cr_clr + data + b'\r\n'
|
|
133
132
|
if self.edit is not None:
|
|
134
|
-
ln += EC.cr_clr + self.edit + EC.mv_left
|
|
135
|
-
if self.cursor > 0:
|
|
136
|
-
ln += b'\x1b[' + str(self.cursor).encode() + b'C'
|
|
133
|
+
ln += EC.cr_clr + self.edit + EC.mv_left + cursor_str
|
|
137
134
|
sys.stdout.buffer.write(ln)
|
|
138
135
|
sys.stdout.flush()
|
|
139
136
|
|
|
@@ -173,8 +170,8 @@ class Console:
|
|
|
173
170
|
# all to add '\r\n' to the logging output
|
|
174
171
|
logger = logging.getLogger()
|
|
175
172
|
handler = CustomHandler()
|
|
176
|
-
handler.setFormatter(logging.Formatter(
|
|
177
|
-
|
|
173
|
+
handler.setFormatter(logging.Formatter('%(levelname)s:console: '
|
|
174
|
+
'%(message)s'))
|
|
178
175
|
logger.handlers.clear()
|
|
179
176
|
logger.addHandler(handler)
|
|
180
177
|
self.busclient = BusClient(bus_ip, bus_port, module='Console')
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
bus_ip: 127.0.0.1
|
|
2
|
+
bus_port: 1325
|
|
3
|
+
proxy:
|
|
4
|
+
api:
|
|
5
|
+
api_key: ${OPENWEATHER_API_KEY}
|
|
6
|
+
units: metric
|
|
7
|
+
locations:
|
|
8
|
+
Murupara:
|
|
9
|
+
lat: -38.45747036367239
|
|
10
|
+
lon: 176.70484322806843
|
|
11
|
+
times: [3]
|
|
12
|
+
parameters:
|
|
13
|
+
- Temp
|
|
14
|
+
- WindSpeed
|
|
15
|
+
- WindDir
|
|
16
|
+
- Rain
|
|
17
|
+
tags:
|
|
18
|
+
- Murupara_Temp
|
|
19
|
+
- Murupara_WindSpeed
|
|
20
|
+
- Murupara_WindDir
|
|
21
|
+
- Murupara_Rain
|
|
22
|
+
- Murupara_Temp_03
|
|
23
|
+
- Murupara_WindSpeed_03
|
|
24
|
+
- Murupara_WindDir_03
|
|
25
|
+
- Murupara_Rain_03
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
__bus__:
|
|
2
|
+
desc: Bus Server Info
|
|
3
|
+
type: str
|
|
4
|
+
__files__:
|
|
5
|
+
desc: File Server
|
|
6
|
+
type: dict
|
|
7
|
+
__history__:
|
|
8
|
+
desc: History
|
|
9
|
+
type: bytes
|
|
10
|
+
__opnotes__:
|
|
11
|
+
desc: Operator Notes
|
|
12
|
+
type: dict
|
|
13
|
+
__alarms__:
|
|
14
|
+
desc: Alarms
|
|
15
|
+
type: dict
|
|
16
|
+
IntSet:
|
|
17
|
+
desc: Integer Setpoint
|
|
18
|
+
type: int
|
|
19
|
+
min: -100
|
|
20
|
+
max: 100
|
|
21
|
+
init: 1
|
|
22
|
+
IntVal:
|
|
23
|
+
desc: Integer Value
|
|
24
|
+
type: int
|
|
25
|
+
min: -100
|
|
26
|
+
max: 100
|
|
27
|
+
init: 1
|
|
28
|
+
FloatSet:
|
|
29
|
+
desc: Float Setpoint
|
|
30
|
+
type: float
|
|
31
|
+
min: -1.e+100
|
|
32
|
+
max: 1.e+100
|
|
33
|
+
units: 'm'
|
|
34
|
+
dp: 1
|
|
35
|
+
init: 2
|
|
36
|
+
FloatVal:
|
|
37
|
+
desc: Float Value
|
|
38
|
+
type: float
|
|
39
|
+
min: -1.e+100
|
|
40
|
+
max: 1.e+100
|
|
41
|
+
units: 'm'
|
|
42
|
+
dp: 1
|
|
43
|
+
init: 2
|
|
44
|
+
MultiSet:
|
|
45
|
+
desc: Multi Setpoint
|
|
46
|
+
multi:
|
|
47
|
+
- zero
|
|
48
|
+
- one
|
|
49
|
+
- two
|
|
50
|
+
- three
|
|
51
|
+
init: 0
|
|
52
|
+
MultiVal:
|
|
53
|
+
desc: Multi Value
|
|
54
|
+
multi:
|
|
55
|
+
- zero
|
|
56
|
+
- one
|
|
57
|
+
- two
|
|
58
|
+
- three
|
|
59
|
+
init: 0
|
|
60
|
+
StringSet:
|
|
61
|
+
desc: String Setpoint
|
|
62
|
+
type: str
|
|
63
|
+
init: a string
|
|
64
|
+
StringVal:
|
|
65
|
+
desc: String Value
|
|
66
|
+
type: str
|
|
67
|
+
init: a string
|
|
68
|
+
TimeSet:
|
|
69
|
+
desc: Time Setpoint
|
|
70
|
+
type: int
|
|
71
|
+
init: 1234567890
|
|
72
|
+
format: time
|
|
73
|
+
TimeVal:
|
|
74
|
+
desc: Time Value
|
|
75
|
+
type: int
|
|
76
|
+
init: 1234567890
|
|
77
|
+
format: time
|
|
78
|
+
DateSet:
|
|
79
|
+
desc: Date Setpoint
|
|
80
|
+
type: int
|
|
81
|
+
init: 1234567890
|
|
82
|
+
format: date
|
|
83
|
+
DateVal:
|
|
84
|
+
desc: Date Value
|
|
85
|
+
type: int
|
|
86
|
+
init: 1234567890
|
|
87
|
+
format: date
|
|
88
|
+
DateTimeSet:
|
|
89
|
+
desc: Date Time Setpoint
|
|
90
|
+
type: int
|
|
91
|
+
init: 1234567890
|
|
92
|
+
format: datetime
|
|
93
|
+
DateTimeVal:
|
|
94
|
+
desc: Date Time Value
|
|
95
|
+
type: int
|
|
96
|
+
init: 1234567890
|
|
97
|
+
format: datetime
|
|
98
|
+
MultiSelect:
|
|
99
|
+
desc: Select Dict Multi
|
|
100
|
+
type: dict
|
|
101
|
+
init:
|
|
102
|
+
labels: [A, B, C, D, E, F, G, H]
|
|
103
|
+
values: [0, 1, 2, 3, 3, 4, 4, 3]
|
|
104
|
+
locks: [1, 0, 0, 0, 0, 0, 0, 1]
|
|
105
|
+
FloatSelect:
|
|
106
|
+
desc: Select Dict Float
|
|
107
|
+
type: dict
|
|
108
|
+
init:
|
|
109
|
+
labels: [Floats, For, This, Tag]
|
|
110
|
+
values: [0.1, 0.1, 0.2, 0.3]
|
|
111
|
+
locks: [0, 1, 0, 0]
|
|
112
|
+
localhost_ping:
|
|
113
|
+
desc: Ping time to localhost
|
|
114
|
+
type: int
|
|
115
|
+
units: ms
|
|
116
|
+
google_ping:
|
|
117
|
+
desc: Ping time to google
|
|
118
|
+
type: int
|
|
119
|
+
units: ms
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
bus_ip: 127.0.0.1
|
|
2
|
+
bus_port: 1324
|
|
3
|
+
ip: 0.0.0.0
|
|
4
|
+
port: 8324
|
|
5
|
+
get_path:
|
|
6
|
+
serve_path: __HOME__
|
|
7
|
+
pages:
|
|
8
|
+
- name: Notes
|
|
9
|
+
parent:
|
|
10
|
+
items:
|
|
11
|
+
- type: opnotes
|
|
12
|
+
site: [Site 1, Site 2]
|
|
13
|
+
by: [B1, B2]
|
|
14
|
+
- name: Default Main
|
|
15
|
+
parent:
|
|
16
|
+
items:
|
|
17
|
+
- {desc: Default tags, type: h1}
|
|
18
|
+
- {tagname: IntSet, type: setpoint}
|
|
19
|
+
- {tagname: IntVal, type: value}
|
|
20
|
+
- {tagname: FloatSet, type: setpoint}
|
|
21
|
+
- {tagname: FloatVal, type: value}
|
|
22
|
+
- {tagname: MultiSet, type: setpoint}
|
|
23
|
+
- {tagname: MultiVal, type: value}
|
|
24
|
+
- {tagname: StringSet, type: setpoint}
|
|
25
|
+
- {tagname: StringVal, type: value}
|
|
26
|
+
- {tagname: TimeSet, type: setpoint}
|
|
27
|
+
- {tagname: TimeVal, type: value}
|
|
28
|
+
- {tagname: DateSet, type: setpoint}
|
|
29
|
+
- {tagname: DateVal, type: value}
|
|
30
|
+
- {tagname: DateTimeSet, type: setpoint}
|
|
31
|
+
- {tagname: DateTimeVal, type: value}
|
|
32
|
+
- type: selectdict
|
|
33
|
+
tagname: MultiSelect
|
|
34
|
+
opts:
|
|
35
|
+
type: multi
|
|
36
|
+
multi:
|
|
37
|
+
- Zero
|
|
38
|
+
- One
|
|
39
|
+
- Two
|
|
40
|
+
- Three
|
|
41
|
+
- Four
|
|
42
|
+
- Five
|
|
43
|
+
- {tagname: FloatSelect, type: selectdict, opts: {type: float, dp: 2}}
|
|
44
|
+
- name: Files
|
|
45
|
+
parent:
|
|
46
|
+
items:
|
|
47
|
+
- type: files
|
|
48
|
+
- name: Ping Values
|
|
49
|
+
parent:
|
|
50
|
+
items:
|
|
51
|
+
- {desc: Default tags, type: h1}
|
|
52
|
+
- {tagname: localhost_ping, type: value}
|
|
53
|
+
- {tagname: google_ping, type: value}
|
|
54
|
+
- {tagname: electronet_ping, type: value}
|
|
55
|
+
- name: Ping Trend
|
|
56
|
+
parent:
|
|
57
|
+
items:
|
|
58
|
+
- type: uplot # Do all times in seconds, which uplot uses.
|
|
59
|
+
ms:
|
|
60
|
+
desc: Ping Trend
|
|
61
|
+
age: 172800
|
|
62
|
+
legend_pos: left
|
|
63
|
+
time_pos: left
|
|
64
|
+
time_res: m
|
|
65
|
+
axes:
|
|
66
|
+
- scale: x
|
|
67
|
+
range: [-86400, 0] # 86400 172800 1209600
|
|
68
|
+
- scale: mS
|
|
69
|
+
range: [0.0, 1.0]
|
|
70
|
+
dp: 1
|
|
71
|
+
series:
|
|
72
|
+
- tagname: localhost_ping
|
|
73
|
+
label: localhost
|
|
74
|
+
scale: mS
|
|
75
|
+
color: black
|
|
76
|
+
dp: 1
|
|
77
|
+
- tagname: google_ping
|
|
78
|
+
label: google
|
|
79
|
+
scale: mS
|
|
80
|
+
color: red
|
|
81
|
+
dp: 1
|