pymscada 0.1.10__tar.gz → 0.1.11b2__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.
Potentially problematic release.
This version of pymscada might be problematic. Click here for more details.
- {pymscada-0.1.10 → pymscada-0.1.11b2}/PKG-INFO +4 -2
- {pymscada-0.1.10 → pymscada-0.1.11b2}/pyproject.toml +9 -2
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/bus_client.py +23 -22
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/bus_server.py +28 -25
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/console.py +11 -14
- pymscada-0.1.11b2/src/pymscada/demo/openweather.yaml +25 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/demo/tags.yaml +3 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/demo/wwwserver.yaml +0 -263
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/files.py +12 -17
- pymscada-0.1.11b2/src/pymscada/iodrivers/openweather.py +126 -0
- pymscada-0.1.11b2/src/pymscada/main.py +57 -0
- pymscada-0.1.11b2/src/pymscada/module_config.py +208 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/protocol_constants.py +29 -39
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/validate.py +29 -26
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/www_server.py +10 -11
- {pymscada-0.1.10 → pymscada-0.1.11b2}/tests/test_assets/db.sqlite +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/tests/test_bus_server.py +13 -13
- pymscada-0.1.11b2/tests/test_openweather.py +47 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/tests/test_opnotes.py +10 -14
- {pymscada-0.1.10 → pymscada-0.1.11b2}/tests/test_validate.py +2 -2
- pymscada-0.1.10/src/pymscada/main.py +0 -311
- {pymscada-0.1.10 → pymscada-0.1.11b2}/LICENSE +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/README.md +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/__init__.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/__main__.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/checkout.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/config.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/demo/README.md +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/demo/__init__.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/demo/accuweather.yaml +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/demo/bus.yaml +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/demo/files.yaml +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/demo/history.yaml +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/demo/logixclient.yaml +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/demo/modbus_plc.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/demo/modbusclient.yaml +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/demo/modbusserver.yaml +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/demo/opnotes.yaml +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/demo/ping.yaml +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/demo/pymscada-bus.service +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/demo/pymscada-demo-modbus_plc.service +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/demo/pymscada-files.service +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/demo/pymscada-history.service +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/demo/pymscada-io-accuweather.service +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/demo/pymscada-io-logixclient.service +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/demo/pymscada-io-modbusclient.service +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/demo/pymscada-io-modbusserver.service +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/demo/pymscada-io-ping.service +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/demo/pymscada-io-snmpclient.service +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/demo/pymscada-opnotes.service +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/demo/pymscada-wwwserver.service +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/demo/snmpclient.yaml +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/history.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/iodrivers/__init__.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/iodrivers/accuweather.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/iodrivers/logix_client.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/iodrivers/logix_map.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/iodrivers/modbus_client.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/iodrivers/modbus_map.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/iodrivers/modbus_server.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/iodrivers/ping_client.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/iodrivers/ping_map.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/iodrivers/snmp_client.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/iodrivers/snmp_map.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/misc.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/opnotes.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/pdf/__init__.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/pdf/one.pdf +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/pdf/two.pdf +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/periodic.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/samplers.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/tag.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/tools/snmp_client2.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/src/pymscada/tools/walk.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/tests/__init__.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/tests/bus_echo.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/tests/iodrivers/test_logix.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/tests/iodrivers/test_modbus.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/tests/test_assets/busserver.yaml +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/tests/test_assets/hist_tag_0_0.dat +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/tests/test_assets/hist_tag_0_10_2.dat +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/tests/test_assets/hist_tag_0_15.dat +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/tests/test_assets/hist_tag_0_26.dat +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/tests/test_assets/hist_tag_0_50.dat +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/tests/test_config.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/tests/test_history.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/tests/test_misc.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/tests/test_periodic.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/tests/test_samplers.py +0 -0
- {pymscada-0.1.10 → pymscada-0.1.11b2}/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.11b2
|
|
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
|
|
@@ -10,10 +10,12 @@ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
11
11
|
Classifier: Environment :: Console
|
|
12
12
|
Classifier: Development Status :: 1 - Planning
|
|
13
|
+
Project-URL: Homepage, https://github.com/jamie0walton/pymscada
|
|
14
|
+
Project-URL: Bug Tracker, https://github.com/jamie0walton/pymscada/issues
|
|
13
15
|
Requires-Python: >=3.9
|
|
14
16
|
Requires-Dist: PyYAML>=6.0.1
|
|
15
17
|
Requires-Dist: aiohttp>=3.8.5
|
|
16
|
-
Requires-Dist: pymscada-html>=0.1.
|
|
18
|
+
Requires-Dist: pymscada-html>=0.1.10b2
|
|
17
19
|
Requires-Dist: cerberus>=1.3.5
|
|
18
20
|
Requires-Dist: pycomm3>=1.2.14
|
|
19
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.11b2"
|
|
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",
|
|
@@ -30,6 +30,10 @@ text = "GPL-3.0-or-later"
|
|
|
30
30
|
[project.scripts]
|
|
31
31
|
pymscada = "pymscada.__main__:cmd_line"
|
|
32
32
|
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://github.com/jamie0walton/pymscada"
|
|
35
|
+
"Bug Tracker" = "https://github.com/jamie0walton/pymscada/issues"
|
|
36
|
+
|
|
33
37
|
[build-system]
|
|
34
38
|
requires = [
|
|
35
39
|
"pdm-backend",
|
|
@@ -50,3 +54,6 @@ pdm = []
|
|
|
50
54
|
omit = [
|
|
51
55
|
"tests/*",
|
|
52
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
|
|
@@ -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
|
|
@@ -256,10 +256,13 @@ Router_eth8_bytes_out:
|
|
|
256
256
|
type: int
|
|
257
257
|
localhost_ping:
|
|
258
258
|
desc: Ping time to localhost
|
|
259
|
+
type: int
|
|
259
260
|
units: ms
|
|
260
261
|
google_ping:
|
|
261
262
|
desc: Ping time to google
|
|
263
|
+
type: int
|
|
262
264
|
units: ms
|
|
263
265
|
electronet_ping:
|
|
264
266
|
desc: Ping time to electronet
|
|
267
|
+
type: int
|
|
265
268
|
units: ms
|
|
@@ -88,269 +88,6 @@ pages:
|
|
|
88
88
|
parent: Dropdown
|
|
89
89
|
items:
|
|
90
90
|
- type: files
|
|
91
|
-
- name: Temperature
|
|
92
|
-
parent: Weather
|
|
93
|
-
items:
|
|
94
|
-
- type: uplot # Do all times in seconds, which uplot uses.
|
|
95
|
-
ms:
|
|
96
|
-
desc: Temperature
|
|
97
|
-
age: 172800
|
|
98
|
-
legend_pos: left
|
|
99
|
-
time_pos: left
|
|
100
|
-
time_res: m
|
|
101
|
-
axes:
|
|
102
|
-
- scale: x
|
|
103
|
-
range: [-604800, 86400] # 86400 172800 1209600
|
|
104
|
-
- scale: 'C'
|
|
105
|
-
range: [0.0, 35.0]
|
|
106
|
-
dp: 1
|
|
107
|
-
series:
|
|
108
|
-
- tagname: temperature
|
|
109
|
-
label: Current Temperature
|
|
110
|
-
scale: 'C'
|
|
111
|
-
color: black
|
|
112
|
-
width: 2
|
|
113
|
-
dp: 1
|
|
114
|
-
- tagname: temperature_01
|
|
115
|
-
label: 1h Temperature
|
|
116
|
-
scale: 'C'
|
|
117
|
-
color: darkgray
|
|
118
|
-
width: 1.5
|
|
119
|
-
dp: 1
|
|
120
|
-
- tagname: temperature_04
|
|
121
|
-
label: 4h Temperature
|
|
122
|
-
scale: 'C'
|
|
123
|
-
color: green
|
|
124
|
-
width: 1
|
|
125
|
-
dp: 1
|
|
126
|
-
- tagname: temperature_12
|
|
127
|
-
label: 12h Temperature
|
|
128
|
-
scale: 'C'
|
|
129
|
-
color: orange
|
|
130
|
-
width: 0.75
|
|
131
|
-
dp: 1
|
|
132
|
-
- tagname: temperature_24
|
|
133
|
-
label: 24h Temperature
|
|
134
|
-
scale: 'C'
|
|
135
|
-
color: red
|
|
136
|
-
width: 0.5
|
|
137
|
-
dp: 1
|
|
138
|
-
- name: Wind Speed
|
|
139
|
-
parent: Weather
|
|
140
|
-
items:
|
|
141
|
-
- type: uplot # Do all times in seconds, which uplot uses.
|
|
142
|
-
ms:
|
|
143
|
-
desc: Wind Speed
|
|
144
|
-
age: 172800
|
|
145
|
-
legend_pos: left
|
|
146
|
-
time_pos: left
|
|
147
|
-
time_res: m
|
|
148
|
-
axes:
|
|
149
|
-
- scale: x
|
|
150
|
-
range: [-604800, 86400] # 86400 172800 1209600
|
|
151
|
-
- scale: 'm/s'
|
|
152
|
-
range: [0.0, 20.0]
|
|
153
|
-
dp: 1
|
|
154
|
-
series:
|
|
155
|
-
- tagname: windSpeed
|
|
156
|
-
label: Current Wind Speed
|
|
157
|
-
scale: 'm/s'
|
|
158
|
-
color: black
|
|
159
|
-
width: 2
|
|
160
|
-
dp: 1
|
|
161
|
-
- tagname: windSpeed_01
|
|
162
|
-
label: 1h Wind Speed
|
|
163
|
-
scale: 'm/s'
|
|
164
|
-
color: darkgray
|
|
165
|
-
width: 1.5
|
|
166
|
-
dp: 1
|
|
167
|
-
- tagname: windSpeed_04
|
|
168
|
-
label: 4h Wind Speed
|
|
169
|
-
scale: 'm/s'
|
|
170
|
-
color: green
|
|
171
|
-
width: 1
|
|
172
|
-
dp: 1
|
|
173
|
-
- tagname: windSpeed_12
|
|
174
|
-
label: 12h Wind Speed
|
|
175
|
-
scale: 'm/s'
|
|
176
|
-
color: orange
|
|
177
|
-
width: 0.75
|
|
178
|
-
dp: 1
|
|
179
|
-
- tagname: windSpeed_24
|
|
180
|
-
label: 24h Wind Speed
|
|
181
|
-
scale: 'm/s'
|
|
182
|
-
color: red
|
|
183
|
-
width: 0.5
|
|
184
|
-
dp: 1
|
|
185
|
-
- name: Wind Direction
|
|
186
|
-
parent: Weather
|
|
187
|
-
items:
|
|
188
|
-
- type: uplot # Do all times in seconds, which uplot uses.
|
|
189
|
-
ms:
|
|
190
|
-
desc: Wind Direction
|
|
191
|
-
age: 172800
|
|
192
|
-
legend_pos: left
|
|
193
|
-
time_pos: left
|
|
194
|
-
time_res: m
|
|
195
|
-
axes:
|
|
196
|
-
- scale: x
|
|
197
|
-
range: [-604800, 86400] # 86400 172800 1209600
|
|
198
|
-
- scale: 'deg'
|
|
199
|
-
range: [0.0, 360.0]
|
|
200
|
-
dp: 1
|
|
201
|
-
series:
|
|
202
|
-
- tagname: windDirection
|
|
203
|
-
label: Current Wind Direction
|
|
204
|
-
scale: 'deg'
|
|
205
|
-
color: black
|
|
206
|
-
width: 2
|
|
207
|
-
dp: 1
|
|
208
|
-
- tagname: windDirection_01
|
|
209
|
-
label: 1h Wind Direction
|
|
210
|
-
scale: 'deg'
|
|
211
|
-
color: darkgray
|
|
212
|
-
width: 1.5
|
|
213
|
-
dp: 1
|
|
214
|
-
- tagname: windDirection_04
|
|
215
|
-
label: 4h Wind Direction
|
|
216
|
-
scale: 'deg'
|
|
217
|
-
color: green
|
|
218
|
-
width: 1
|
|
219
|
-
dp: 1
|
|
220
|
-
- tagname: windDirection_12
|
|
221
|
-
label: 12h Wind Direction
|
|
222
|
-
scale: 'deg'
|
|
223
|
-
color: orange
|
|
224
|
-
width: 0.75
|
|
225
|
-
dp: 1
|
|
226
|
-
- tagname: windDirection_24
|
|
227
|
-
label: 24h Wind Direction
|
|
228
|
-
scale: 'deg'
|
|
229
|
-
color: red
|
|
230
|
-
width: 0.5
|
|
231
|
-
dp: 1
|
|
232
|
-
- name: Rain Accumulation
|
|
233
|
-
parent: Weather
|
|
234
|
-
items:
|
|
235
|
-
- type: uplot # Do all times in seconds, which uplot uses.
|
|
236
|
-
ms:
|
|
237
|
-
desc: Rain Accumulation
|
|
238
|
-
age: 172800
|
|
239
|
-
legend_pos: left
|
|
240
|
-
time_pos: left
|
|
241
|
-
time_res: m
|
|
242
|
-
axes:
|
|
243
|
-
- scale: x
|
|
244
|
-
range: [-604800, 86400] # 86400 172800 1209600
|
|
245
|
-
- scale: 'mm'
|
|
246
|
-
range: [0.0, 10.0]
|
|
247
|
-
dp: 1
|
|
248
|
-
series:
|
|
249
|
-
- tagname: rainAccumulation
|
|
250
|
-
label: Current Rain Accumulation
|
|
251
|
-
scale: 'mm'
|
|
252
|
-
color: black
|
|
253
|
-
width: 2
|
|
254
|
-
dp: 1
|
|
255
|
-
- tagname: rainAccumulation_01
|
|
256
|
-
label: 1h Rain Accumulation
|
|
257
|
-
scale: 'mm'
|
|
258
|
-
color: darkgray
|
|
259
|
-
width: 1.5
|
|
260
|
-
dp: 1
|
|
261
|
-
- tagname: rainAccumulation_04
|
|
262
|
-
label: 4h Rain Accumulation
|
|
263
|
-
scale: 'mm'
|
|
264
|
-
color: green
|
|
265
|
-
width: 1
|
|
266
|
-
dp: 1
|
|
267
|
-
- tagname: rainAccumulation_12
|
|
268
|
-
label: 12h Rain Accumulation
|
|
269
|
-
scale: 'mm'
|
|
270
|
-
color: orange
|
|
271
|
-
width: 0.75
|
|
272
|
-
dp: 1
|
|
273
|
-
- tagname: rainAccumulation_24
|
|
274
|
-
label: 24h Rain Accumulation
|
|
275
|
-
scale: 'mm'
|
|
276
|
-
color: red
|
|
277
|
-
width: 0.5
|
|
278
|
-
dp: 1
|
|
279
|
-
- name: Humidity
|
|
280
|
-
parent: Weather
|
|
281
|
-
items:
|
|
282
|
-
- type: uplot # Do all times in seconds, which uplot uses.
|
|
283
|
-
ms:
|
|
284
|
-
desc: Humidity
|
|
285
|
-
age: 172800
|
|
286
|
-
legend_pos: left
|
|
287
|
-
time_pos: left
|
|
288
|
-
time_res: m
|
|
289
|
-
axes:
|
|
290
|
-
- scale: x
|
|
291
|
-
range: [-604800, 86400] # 86400 172800 1209600
|
|
292
|
-
- scale: '%'
|
|
293
|
-
range: [0.0, 100.0]
|
|
294
|
-
dp: 1
|
|
295
|
-
series:
|
|
296
|
-
- tagname: humidity
|
|
297
|
-
label: Current Humidity
|
|
298
|
-
scale: '%'
|
|
299
|
-
color: black
|
|
300
|
-
width: 2
|
|
301
|
-
dp: 1
|
|
302
|
-
- tagname: humidity_01
|
|
303
|
-
label: 1h Humidity
|
|
304
|
-
scale: '%'
|
|
305
|
-
color: darkgray
|
|
306
|
-
width: 1.5
|
|
307
|
-
dp: 1
|
|
308
|
-
- tagname: humidity_04
|
|
309
|
-
label: 4h Humidity
|
|
310
|
-
scale: '%'
|
|
311
|
-
color: green
|
|
312
|
-
width: 1
|
|
313
|
-
dp: 1
|
|
314
|
-
- tagname: humidity_12
|
|
315
|
-
label: 12h Humidity
|
|
316
|
-
scale: '%'
|
|
317
|
-
color: orange
|
|
318
|
-
width: 0.75
|
|
319
|
-
dp: 1
|
|
320
|
-
- tagname: humidity_24
|
|
321
|
-
label: 24h Humidity
|
|
322
|
-
scale: '%'
|
|
323
|
-
color: red
|
|
324
|
-
width: 0.5
|
|
325
|
-
dp: 1
|
|
326
|
-
- name: Values
|
|
327
|
-
parent: Weather
|
|
328
|
-
items:
|
|
329
|
-
- {tagname: temperature, type: value}
|
|
330
|
-
- {tagname: temperature_01, type: value}
|
|
331
|
-
- {tagname: temperature_04, type: value}
|
|
332
|
-
- {tagname: temperature_12, type: value}
|
|
333
|
-
- {tagname: temperature_24, type: value}
|
|
334
|
-
- {tagname: windSpeed, type: value}
|
|
335
|
-
- {tagname: windSpeed_01, type: value}
|
|
336
|
-
- {tagname: windSpeed_04, type: value}
|
|
337
|
-
- {tagname: windSpeed_12, type: value}
|
|
338
|
-
- {tagname: windSpeed_24, type: value}
|
|
339
|
-
- {tagname: windDirection, type: value}
|
|
340
|
-
- {tagname: windDirection_01, type: value}
|
|
341
|
-
- {tagname: windDirection_04, type: value}
|
|
342
|
-
- {tagname: windDirection_12, type: value}
|
|
343
|
-
- {tagname: windDirection_24, type: value}
|
|
344
|
-
- {tagname: rainAccumulation, type: value}
|
|
345
|
-
- {tagname: rainAccumulation_01, type: value}
|
|
346
|
-
- {tagname: rainAccumulation_04, type: value}
|
|
347
|
-
- {tagname: rainAccumulation_12, type: value}
|
|
348
|
-
- {tagname: rainAccumulation_24, type: value}
|
|
349
|
-
- {tagname: humidity, type: value}
|
|
350
|
-
- {tagname: humidity_01, type: value}
|
|
351
|
-
- {tagname: humidity_04, type: value}
|
|
352
|
-
- {tagname: humidity_12, type: value}
|
|
353
|
-
- {tagname: humidity_24, type: value}
|
|
354
91
|
- name: Logix
|
|
355
92
|
items:
|
|
356
93
|
- {tagname: Ani_Fin_20, type: setpoint}
|