pymscada 0.1.11__py3-none-any.whl → 0.1.11b3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pymscada/bus_client.py CHANGED
@@ -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)
pymscada/bus_server.py CHANGED
@@ -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
pymscada/checkout.py CHANGED
@@ -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
 
pymscada/console.py CHANGED
@@ -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
pymscada/demo/ping.yaml CHANGED
@@ -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
pymscada/demo/tags.yaml CHANGED
@@ -109,157 +109,11 @@ FloatSelect:
109
109
  labels: [Floats, For, This, Tag]
110
110
  values: [0.1, 0.1, 0.2, 0.3]
111
111
  locks: [0, 1, 0, 0]
112
- cpu_temp:
113
- desc: CPU Load
114
- type: float
115
- min: 0
116
- max: 100
117
- units: '°C'
118
- dp: 1
119
- cpu_load:
120
- desc: Float Value
121
- type: float
122
- min: 0
123
- max: 100
124
- units: '%'
125
- dp: 1
126
- disk_use:
127
- desc: Float Value
128
- type: float
129
- min: 0
130
- max: 100
131
- units: '%'
132
- dp: 1
133
- sim_IntSet: # for the demo PLC sim_ tags not required
134
- desc: Simulation tag
135
- type: int
136
- sim_IntVal:
137
- desc: Simulation tag
138
- type: int
139
- sim_FloatSet:
140
- desc: Simulation tag
141
- type: float
142
- sim_FloatVal:
143
- desc: Simulation tag
144
- type: float
145
- sim_MultiSet:
146
- desc: Simulation tag
147
- type: int
148
- sim_MultiVal:
149
- desc: Simulation tag
150
- type: int
151
- sim_TimeSet:
152
- desc: Simulation tag
153
- type: int
154
- sim_TimeVal:
155
- desc: Simulation tag
156
- type: int
157
- sim_DateSet:
158
- desc: Simulation tag
159
- type: int
160
- sim_DateVal:
161
- desc: Simulation tag
162
- type: int
163
- sim_DateTimeSet:
164
- desc: Simulation tag
165
- type: int
166
- sim_DateTimeVal:
167
- desc: Simulation tag
168
- type: int
169
- Ani_Fin_20:
170
- desc: 'Logix Fin[20]'
171
- type: float
172
- dp: 2
173
- Ani_Fout_20:
174
- desc: 'Logix Fout[20]'
175
- type: float
176
- dp: 2
177
- Ani_Iin_20:
178
- desc: 'Logix Iin[20]'
179
- type: int
180
- Ani_Iout_20:
181
- desc: 'Logix Iout[20]'
182
- type: int
183
- Ani_Iout_21_0:
184
- desc: 'Logix Iout[21].0'
185
- multi:
186
- - 'Off'
187
- - 'On'
188
- Ani_Iout_21_1:
189
- desc: 'Logix Iout[21].1'
190
- multi:
191
- - 'Off'
192
- - 'On'
193
- Ani_Iin_21_0:
194
- desc: 'Logix Iin[21].0'
195
- multi:
196
- - 'Off'
197
- - 'On'
198
- Ani_Iin_21_1:
199
- desc: 'Logix Iin[21].1'
200
- multi:
201
- - 'Off'
202
- - 'On'
203
- InVar:
204
- desc: InVar
205
- type: float
206
- OutVar:
207
- desc: OutVar
208
- type: float
209
- Router_eth1_bytes_in:
210
- desc: eth1 bytes in
211
- type: int
212
- Router_eth1_bytes_out:
213
- desc: eth1 bytes out
214
- type: int
215
- Router_eth2_bytes_in:
216
- desc: eth2 bytes in
217
- type: int
218
- Router_eth2_bytes_out:
219
- desc: eth2 bytes out
220
- type: int
221
- Router_eth3_bytes_in:
222
- desc: eth3 bytes in
223
- type: int
224
- Router_eth3_bytes_out:
225
- desc: eth3 bytes out
226
- type: int
227
- Router_eth4_bytes_in:
228
- desc: eth4 bytes in
229
- type: int
230
- Router_eth4_bytes_out:
231
- desc: eth4 bytes out
232
- type: int
233
- Router_eth5_bytes_in:
234
- desc: eth5 bytes in
235
- type: int
236
- Router_eth5_bytes_out:
237
- desc: eth5 bytes out
238
- type: int
239
- Router_eth6_bytes_in:
240
- desc: eth6 bytes in
241
- type: int
242
- Router_eth6_bytes_out:
243
- desc: eth6 bytes out
244
- type: int
245
- Router_eth7_bytes_in:
246
- desc: eth7 bytes in
247
- type: int
248
- Router_eth7_bytes_out:
249
- desc: eth7 bytes out
250
- type: int
251
- Router_eth8_bytes_in:
252
- desc: eth8 bytes in
253
- type: int
254
- Router_eth8_bytes_out:
255
- desc: eth8 bytes out
256
- type: int
257
112
  localhost_ping:
258
113
  desc: Ping time to localhost
114
+ type: int
259
115
  units: ms
260
116
  google_ping:
261
117
  desc: Ping time to google
262
- units: ms
263
- electronet_ping:
264
- desc: Ping time to electronet
118
+ type: int
265
119
  units: ms