pymscada 0.1.1a2__tar.gz → 0.1.2__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.

Files changed (82) hide show
  1. {pymscada-0.1.1a2 → pymscada-0.1.2}/PKG-INFO +1 -1
  2. {pymscada-0.1.1a2 → pymscada-0.1.2}/pyproject.toml +1 -1
  3. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/console.py +60 -44
  4. {pymscada-0.1.1a2 → pymscada-0.1.2}/LICENSE +0 -0
  5. {pymscada-0.1.1a2 → pymscada-0.1.2}/README.md +0 -0
  6. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/__init__.py +0 -0
  7. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/__main__.py +0 -0
  8. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/bus_client.py +0 -0
  9. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/bus_server.py +0 -0
  10. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/checkout.py +0 -0
  11. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/config.py +0 -0
  12. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/demo/README.md +0 -0
  13. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/demo/__init__.py +0 -0
  14. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/demo/bus.yaml +0 -0
  15. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/demo/files.yaml +0 -0
  16. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/demo/history.yaml +0 -0
  17. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/demo/logixclient.yaml +0 -0
  18. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/demo/modbus_plc.py +0 -0
  19. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/demo/modbusclient.yaml +0 -0
  20. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/demo/modbusserver.yaml +0 -0
  21. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/demo/opnotes.yaml +0 -0
  22. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/demo/ping.yaml +0 -0
  23. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/demo/pymscada-bus.service +0 -0
  24. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/demo/pymscada-demo-modbus_plc.service +0 -0
  25. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/demo/pymscada-files.service +0 -0
  26. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/demo/pymscada-history.service +0 -0
  27. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/demo/pymscada-io-logixclient.service +0 -0
  28. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/demo/pymscada-io-modbusclient.service +0 -0
  29. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/demo/pymscada-io-modbusserver.service +0 -0
  30. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/demo/pymscada-io-ping.service +0 -0
  31. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/demo/pymscada-io-snmpclient.service +0 -0
  32. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/demo/pymscada-opnotes.service +0 -0
  33. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/demo/pymscada-wwwserver.service +0 -0
  34. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/demo/snmpclient.yaml +0 -0
  35. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/demo/tags.yaml +0 -0
  36. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/demo/wwwserver.yaml +0 -0
  37. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/files.py +0 -0
  38. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/history.py +0 -0
  39. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/iodrivers/__init__.py +0 -0
  40. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/iodrivers/logix_client.py +0 -0
  41. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/iodrivers/logix_map.py +0 -0
  42. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/iodrivers/modbus_client.py +0 -0
  43. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/iodrivers/modbus_map.py +0 -0
  44. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/iodrivers/modbus_server.py +0 -0
  45. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/iodrivers/ping_client.py +0 -0
  46. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/iodrivers/ping_map.py +0 -0
  47. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/iodrivers/snmp_client.py +0 -0
  48. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/iodrivers/snmp_map.py +0 -0
  49. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/main.py +0 -0
  50. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/misc.py +0 -0
  51. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/opnotes.py +0 -0
  52. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/pdf/__init__.py +0 -0
  53. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/pdf/one.pdf +0 -0
  54. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/pdf/two.pdf +0 -0
  55. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/periodic.py +0 -0
  56. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/protocol_constants.py +0 -0
  57. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/samplers.py +0 -0
  58. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/tag.py +0 -0
  59. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/tools/snmp_client2.py +0 -0
  60. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/tools/walk.py +0 -0
  61. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/validate.py +0 -0
  62. {pymscada-0.1.1a2 → pymscada-0.1.2}/src/pymscada/www_server.py +0 -0
  63. {pymscada-0.1.1a2 → pymscada-0.1.2}/tests/__init__.py +0 -0
  64. {pymscada-0.1.1a2 → pymscada-0.1.2}/tests/bus_echo.py +0 -0
  65. {pymscada-0.1.1a2 → pymscada-0.1.2}/tests/iodrivers/test_logix.py +0 -0
  66. {pymscada-0.1.1a2 → pymscada-0.1.2}/tests/iodrivers/test_modbus.py +0 -0
  67. {pymscada-0.1.1a2 → pymscada-0.1.2}/tests/test_assets/busserver.yaml +0 -0
  68. {pymscada-0.1.1a2 → pymscada-0.1.2}/tests/test_assets/db.sqlite +0 -0
  69. {pymscada-0.1.1a2 → pymscada-0.1.2}/tests/test_assets/hist_tag_0_0.dat +0 -0
  70. {pymscada-0.1.1a2 → pymscada-0.1.2}/tests/test_assets/hist_tag_0_10_2.dat +0 -0
  71. {pymscada-0.1.1a2 → pymscada-0.1.2}/tests/test_assets/hist_tag_0_15.dat +0 -0
  72. {pymscada-0.1.1a2 → pymscada-0.1.2}/tests/test_assets/hist_tag_0_26.dat +0 -0
  73. {pymscada-0.1.1a2 → pymscada-0.1.2}/tests/test_assets/hist_tag_0_50.dat +0 -0
  74. {pymscada-0.1.1a2 → pymscada-0.1.2}/tests/test_bus_server.py +0 -0
  75. {pymscada-0.1.1a2 → pymscada-0.1.2}/tests/test_config.py +0 -0
  76. {pymscada-0.1.1a2 → pymscada-0.1.2}/tests/test_history.py +0 -0
  77. {pymscada-0.1.1a2 → pymscada-0.1.2}/tests/test_misc.py +0 -0
  78. {pymscada-0.1.1a2 → pymscada-0.1.2}/tests/test_opnotes.py +0 -0
  79. {pymscada-0.1.1a2 → pymscada-0.1.2}/tests/test_periodic.py +0 -0
  80. {pymscada-0.1.1a2 → pymscada-0.1.2}/tests/test_samplers.py +0 -0
  81. {pymscada-0.1.1a2 → pymscada-0.1.2}/tests/test_tag.py +0 -0
  82. {pymscada-0.1.1a2 → pymscada-0.1.2}/tests/test_validate.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pymscada
3
- Version: 0.1.1a2
3
+ Version: 0.1.2
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pymscada"
3
- version = "0.1.1a2"
3
+ version = "0.1.2"
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" },
@@ -2,10 +2,13 @@
2
2
  import asyncio
3
3
  import logging
4
4
  import sys
5
- import termios
6
- import tty
7
5
  from pymscada.bus_client import BusClient
8
6
  from pymscada.tag import Tag, tag_for_web
7
+ try:
8
+ import termios
9
+ import tty
10
+ except ModuleNotFoundError:
11
+ logging.warning("no termios, don't use console")
9
12
 
10
13
 
11
14
  class EC:
@@ -22,12 +25,24 @@ class EC:
22
25
  home = b'\x1b[H'
23
26
  cr_clr = b'\x1b[2K\x1b[G' # clear line and move start
24
27
  clear_line = b'\x1b[2K'
28
+ mv_start = b'\x1b[G'
25
29
  mv_left = b'\x1b[1000D'
26
- move_cursor_up = b'\x1b[1A'
27
- insert_line = b'\x1b[1L'
28
30
 
29
31
 
30
- class KeypressReaderProtocol(asyncio.Protocol):
32
+ class CustomHandler(logging.StreamHandler):
33
+ """Control the cursor position."""
34
+
35
+ def emit(self, record):
36
+ """Write to the console adding a carriage return."""
37
+ try:
38
+ msg = self.format(record)
39
+ self.stream.write(msg + '\r\n')
40
+ self.stream.flush()
41
+ except Exception:
42
+ self.handleError(record)
43
+
44
+
45
+ class KeypressProtocol(asyncio.Protocol):
31
46
  """Handle key presses, one at a time."""
32
47
 
33
48
  def __init__(self, edit_line, process_command):
@@ -39,6 +54,7 @@ class KeypressReaderProtocol(asyncio.Protocol):
39
54
  self.line = None # not editing a line (yet)
40
55
  self.cursor = 0 # cursor position in line
41
56
  self.stash = None # nothing stashed
57
+ self.connection_lost_future = asyncio.Future()
42
58
 
43
59
  def data_received(self, data):
44
60
  """Got keypress, update edit line, send to writer."""
@@ -98,26 +114,9 @@ class KeypressReaderProtocol(asyncio.Protocol):
98
114
  self.cursor = len(self.line)
99
115
  self.edit_line(self.line, self.cursor)
100
116
 
101
-
102
- class ConsoleReader:
103
- """Read key presses for console."""
104
-
105
- def __init__(self):
106
- """Save terminal state and init stdin."""
107
- self.fd = sys.stdin.fileno()
108
- self.old_attr = termios.tcgetattr(self.fd)
109
- tty.setraw(self.fd)
110
-
111
- async def start_connection(self, edit_line, process):
112
- """Connect protocol."""
113
- self.transport, self.protocol = \
114
- await asyncio.get_event_loop().connect_read_pipe(
115
- lambda: KeypressReaderProtocol(edit_line, process),
116
- sys.stdin)
117
-
118
- def __del__(self):
119
- """Reset the terminal."""
120
- termios.tcsetattr(self.fd, termios.TCSADRAIN, self.old_attr)
117
+ def connection_lost(self, exc):
118
+ """Let parent know protocol transport has disconnected."""
119
+ self.connection_lost_future.set_result(True)
121
120
 
122
121
 
123
122
  class ConsoleWriter:
@@ -127,8 +126,6 @@ class ConsoleWriter:
127
126
  """Init."""
128
127
  self.edit = None
129
128
  self.cursor = 0
130
- self.fd = sys.stdout.fileno()
131
- self.old_attr = termios.tcgetattr(self.fd)
132
129
 
133
130
  def write(self, data: bytes):
134
131
  """Stream writer, primarily for logging."""
@@ -154,10 +151,6 @@ class ConsoleWriter:
154
151
  sys.stdout.buffer.write(ln)
155
152
  sys.stdout.flush()
156
153
 
157
- def __del__(self):
158
- """Reset the terminal."""
159
- termios.tcsetattr(self.fd, termios.TCSADRAIN, self.old_attr)
160
-
161
154
 
162
155
  class Console:
163
156
  """Provide a text console to interact with a Bus."""
@@ -169,15 +162,26 @@ class Console:
169
162
 
170
163
  Event loop must be running.
171
164
  """
172
- self.reader = ConsoleReader()
165
+ self.fdin = sys.stdin.fileno()
166
+ self.fdout = sys.stdout.fileno()
167
+ self.fdin_attr = termios.tcgetattr(self.fdin)
168
+ self.fdout_attr = termios.tcgetattr(self.fdout)
169
+ tty.setraw(self.fdin)
173
170
  self.writer = ConsoleWriter()
174
- logging.basicConfig(stream=self.writer)
171
+ self.protocol = None
172
+ self.transport = None
173
+ # all to add '\r\n' to the logging output
174
+ logger = logging.getLogger()
175
+ handler = CustomHandler()
176
+ handler.setFormatter(logging.Formatter(
177
+ '%(levelname)s:%(name)s:%(message)s'))
178
+ logger.handlers.clear()
179
+ logger.addHandler(handler)
175
180
  self.busclient = BusClient(bus_ip, bus_port, module='Console')
176
181
  self.tags: dict[str, Tag] = {}
177
182
  for tagname, tag in tag_info.items():
178
183
  tag_for_web(tagname, tag)
179
184
  self.tags[tagname] = Tag(tagname, tag['type'])
180
- self.quit = asyncio.Event()
181
185
 
182
186
  def write_tag(self, tag: Tag):
183
187
  """Append or insert tag value through writer."""
@@ -186,20 +190,24 @@ class Console:
186
190
 
187
191
  def process(self, command: bytes):
188
192
  """Execute command."""
193
+ if command is None:
194
+ return
189
195
  cmd, var, val = (command.split(b' ') + [None] * 3)[:3]
196
+ tagnames = self.tags.keys()
190
197
  if var is not None:
191
- var = var.decode()
192
- if var is None:
193
- tagnames = self.tags.keys()
194
- else:
195
- tagnames = [x for x in self.tags.keys() if var in x]
198
+ tagname = var.decode()
199
+ tagnames = [x for x in self.tags.keys() if tagname in x]
196
200
  if cmd in [b'q', b'quit']:
197
- self.quit.set() # does not close connection tidily
201
+ self.transport.close()
198
202
  elif cmd in [b'g', b'get']:
199
203
  for tagname in tagnames:
200
204
  self.write_tag(self.tags[tagname])
201
- elif cmd == b'set':
202
- pass
205
+ elif cmd == b'set' and tagname in self.tags:
206
+ try:
207
+ typed_val = self.tags[tagname].type(val.decode())
208
+ self.tags[tagname].value = typed_val
209
+ except ValueError as e:
210
+ logging.warning(f'error setting {tagname}: {e}')
203
211
  elif cmd in [b's', b'sub']:
204
212
  for tagname in tagnames:
205
213
  self.tags[tagname].add_callback(self.write_tag)
@@ -226,5 +234,13 @@ class Console:
226
234
  async def start(self):
227
235
  """Start polling, does not return until finished."""
228
236
  await self.busclient.start()
229
- await self.reader.start_connection(self.writer.edit_line, self.process)
230
- await self.quit.wait() # Idle wait until user quits the console
237
+ try:
238
+ self.protocol = \
239
+ KeypressProtocol(self.writer.edit_line, self.process)
240
+ self.transport, _ = \
241
+ await asyncio.get_event_loop().connect_read_pipe(
242
+ lambda: self.protocol, sys.stdin)
243
+ await self.protocol.connection_lost_future
244
+ finally:
245
+ termios.tcsetattr(self.fdout, termios.TCSADRAIN, self.fdout_attr)
246
+ termios.tcsetattr(self.fdin, termios.TCSADRAIN, self.fdin_attr)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes