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.
Files changed (92) hide show
  1. {pymscada-0.1.11 → pymscada-0.1.11b3}/PKG-INFO +3 -3
  2. {pymscada-0.1.11 → pymscada-0.1.11b3}/pyproject.toml +5 -2
  3. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/bus_client.py +23 -22
  4. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/bus_server.py +28 -25
  5. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/checkout.py +6 -10
  6. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/console.py +11 -14
  7. pymscada-0.1.11b3/src/pymscada/demo/openweather.yaml +25 -0
  8. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/ping.yaml +0 -3
  9. pymscada-0.1.11b3/src/pymscada/demo/tags.yaml +119 -0
  10. pymscada-0.1.11b3/src/pymscada/demo/wwwserver.yaml +81 -0
  11. pymscada-0.1.11b3/src/pymscada/iodrivers/openweather.py +126 -0
  12. pymscada-0.1.11b3/src/pymscada/main.py +57 -0
  13. pymscada-0.1.11b3/src/pymscada/module_config.py +217 -0
  14. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/protocol_constants.py +29 -39
  15. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/validate.py +29 -26
  16. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/www_server.py +4 -4
  17. {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/test_assets/db.sqlite +0 -0
  18. {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/test_bus_server.py +13 -13
  19. pymscada-0.1.11b3/tests/test_openweather.py +47 -0
  20. {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/test_opnotes.py +10 -14
  21. {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/test_validate.py +2 -2
  22. pymscada-0.1.11/src/pymscada/demo/tags.yaml +0 -265
  23. pymscada-0.1.11/src/pymscada/demo/wwwserver.yaml +0 -536
  24. pymscada-0.1.11/src/pymscada/main.py +0 -311
  25. {pymscada-0.1.11 → pymscada-0.1.11b3}/LICENSE +0 -0
  26. {pymscada-0.1.11 → pymscada-0.1.11b3}/README.md +0 -0
  27. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/__init__.py +0 -0
  28. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/__main__.py +0 -0
  29. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/config.py +0 -0
  30. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/README.md +0 -0
  31. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/__init__.py +0 -0
  32. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/accuweather.yaml +0 -0
  33. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/bus.yaml +0 -0
  34. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/files.yaml +0 -0
  35. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/history.yaml +0 -0
  36. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/logixclient.yaml +0 -0
  37. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/modbus_plc.py +0 -0
  38. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/modbusclient.yaml +0 -0
  39. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/modbusserver.yaml +0 -0
  40. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/opnotes.yaml +0 -0
  41. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/pymscada-bus.service +0 -0
  42. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/pymscada-demo-modbus_plc.service +0 -0
  43. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/pymscada-files.service +0 -0
  44. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/pymscada-history.service +0 -0
  45. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/pymscada-io-accuweather.service +0 -0
  46. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/pymscada-io-logixclient.service +0 -0
  47. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/pymscada-io-modbusclient.service +0 -0
  48. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/pymscada-io-modbusserver.service +0 -0
  49. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/pymscada-io-ping.service +0 -0
  50. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/pymscada-io-snmpclient.service +0 -0
  51. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/pymscada-opnotes.service +0 -0
  52. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/pymscada-wwwserver.service +0 -0
  53. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/demo/snmpclient.yaml +0 -0
  54. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/files.py +0 -0
  55. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/history.py +0 -0
  56. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/iodrivers/__init__.py +0 -0
  57. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/iodrivers/accuweather.py +0 -0
  58. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/iodrivers/logix_client.py +0 -0
  59. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/iodrivers/logix_map.py +0 -0
  60. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/iodrivers/modbus_client.py +0 -0
  61. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/iodrivers/modbus_map.py +0 -0
  62. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/iodrivers/modbus_server.py +0 -0
  63. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/iodrivers/ping_client.py +0 -0
  64. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/iodrivers/ping_map.py +0 -0
  65. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/iodrivers/snmp_client.py +0 -0
  66. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/iodrivers/snmp_map.py +0 -0
  67. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/misc.py +0 -0
  68. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/opnotes.py +0 -0
  69. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/pdf/__init__.py +0 -0
  70. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/pdf/one.pdf +0 -0
  71. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/pdf/two.pdf +0 -0
  72. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/periodic.py +0 -0
  73. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/samplers.py +0 -0
  74. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/tag.py +0 -0
  75. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/tools/snmp_client2.py +0 -0
  76. {pymscada-0.1.11 → pymscada-0.1.11b3}/src/pymscada/tools/walk.py +0 -0
  77. {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/__init__.py +0 -0
  78. {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/bus_echo.py +0 -0
  79. {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/iodrivers/test_logix.py +0 -0
  80. {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/iodrivers/test_modbus.py +0 -0
  81. {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/test_assets/busserver.yaml +0 -0
  82. {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/test_assets/hist_tag_0_0.dat +0 -0
  83. {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/test_assets/hist_tag_0_10_2.dat +0 -0
  84. {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/test_assets/hist_tag_0_15.dat +0 -0
  85. {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/test_assets/hist_tag_0_26.dat +0 -0
  86. {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/test_assets/hist_tag_0_50.dat +0 -0
  87. {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/test_config.py +0 -0
  88. {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/test_history.py +0 -0
  89. {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/test_misc.py +0 -0
  90. {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/test_periodic.py +0 -0
  91. {pymscada-0.1.11 → pymscada-0.1.11b3}/tests/test_samplers.py +0 -0
  92. {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.11
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 tracker, https://github.com/jamie0walton/pymscada/issues
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.2
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.11"
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.2",
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.TYPE_FLOAT, tag.value)
45
+ data = struct.pack('!Bd', pc.TYPE.FLOAT, tag.value)
46
46
  elif tag.type is int:
47
- data = struct.pack('!Bq', pc.TYPE_INT, tag.value)
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.TYPE_BYTES, tag.value)
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.TYPE_STR, tag.value.encode())
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.TYPE_JSON, jsonstr)
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.CMD_SET, tag.id, tag.time_us, data)
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.TYPE_JSON, jsonstr)
79
- self.write(pc.CMD_RTA, self.tag_by_name[tagname].id, time_us, data)
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.COMMANDS, tag_id: int, time_us: int,
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.CMD_ID, 0, 0, tag.name.encode())
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.CMD_LOG, 0, 0, f'{self.module} connected'.encode())
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.CMD_ERR:
161
- logging.warning(f'Bus server error {tag_id} {value}')
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.CMD_ID:
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.CMD_SUB, tag.id, 0, b'')
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.CMD_SET:
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.TYPE_FLOAT:
190
+ if data_type == pc.TYPE.FLOAT:
190
191
  data = struct.unpack_from('!d', value, offset=1)[0]
191
- elif data_type == pc.TYPE_INT:
192
+ elif data_type == pc.TYPE.INT:
192
193
  data = struct.unpack_from('!q', value, offset=1)[0]
193
- elif data_type == pc.TYPE_BYTES:
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.TYPE_STR:
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.TYPE_JSON:
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.CMD_RTA:
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.COMMANDS, tag_id: int, time_us: int,
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 {pc.CMD_TEXT[command]} {tag_id}')
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.CMD_SET, tag.id, tag.time_us,
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.CMD_TEXT[cmd]} {tag_id} '
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.CMD_SET:
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.CMD_ERR, tag_id, time_us,
186
+ pc.COMMAND.ERR, tag_id, time_us,
187
187
  f"SET KeyError {tag_id}".encode())
188
- elif cmd == pc.CMD_RTA:
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.CMD_ERR, tag_id, time_us,
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.CMD_RTA, tag_id, tag.time_us, data)
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.CMD_ERR, tag_id, time_us,
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.CMD_SUB:
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.CMD_ERR, tag_id, time_us,
210
+ pc.COMMAND.ERR, tag_id, time_us,
211
211
  f"SUBscribe KeyError {tag_id}".encode())
212
- self.connections[bus_id].write(pc.CMD_SET, tag_id, tag.time_us,
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.CMD_ID:
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.CMD_ERR, tag_id, time_us,
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.CMD_ID, tag.id, tag.time_us,
223
+ self.connections[bus_id].write(pc.COMMAND.ID, tag.id, tag.time_us,
224
224
  tag.name)
225
- elif cmd == pc.CMD_GET:
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.CMD_ERR, tag_id, time_us,
230
+ pc.COMMAND.ERR, tag_id, time_us,
231
231
  f"GET KeyError for {tag_id}".encode())
232
- self.connections[bus_id].write(pc.CMD_SET, tag.id, tag.time_us,
232
+ self.connections[bus_id].write(pc.COMMAND.SET, tag.id, tag.time_us,
233
233
  tag.value)
234
- elif cmd == pc.CMD_UNSUB:
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.CMD_ERR, tag_id, time_us,
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.CMD_LIST:
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.CMD_LIST, 0, time_us, b' '.join(tagname_list))
264
- elif cmd == pc.CMD_LOG:
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
- if str(target).endswith('service'):
61
- rd_bytes = config_file.read_bytes()
62
- for k, v in PATH.items():
63
- rd_bytes = rd_bytes.replace(k.encode(),str(v).encode())
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
- if str(file).endswith('service'):
73
- for k, v in PATH.items():
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
- if self.cursor > 0:
65
- self.cursor -= 1
63
+ self.cursor -= 1
66
64
  elif data == EC.enter:
67
- self.stash = None
68
- if self.lines:
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
- '%(levelname)s:%(name)s:%(message)s'))
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
@@ -4,9 +4,6 @@ tags:
4
4
  localhost_ping:
5
5
  type: ping
6
6
  addr: '127.0.0.1'
7
- electronet_ping:
8
- type: ping
9
- addr: electronet.co.nz
10
7
  google_ping:
11
8
  type: ping
12
9
  addr: www.google.com
@@ -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