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.
@@ -2,13 +2,13 @@ bus_ip: 127.0.0.1
2
2
  bus_port: 1324
3
3
  ip: 0.0.0.0
4
4
  port: 8324
5
- get_path: __HOME__/angmscada/dist/angmscada
6
- serve_path: __HOME__/pymscada
5
+ get_path:
6
+ serve_path: __HOME__
7
7
  pages:
8
8
  - name: Notes
9
9
  parent:
10
10
  items:
11
- - type: opnote
11
+ - type: opnotes
12
12
  site: [Site 1, Site 2]
13
13
  by: [B1, B2]
14
14
  - name: Default Main
@@ -41,469 +41,19 @@ pages:
41
41
  - Four
42
42
  - Five
43
43
  - {tagname: FloatSelect, type: selectdict, opts: {type: float, dp: 2}}
44
- - name: Trends
45
- parent: Dropdown
46
- items:
47
- - type: uplot # Do all times in seconds, which uplot uses.
48
- ms:
49
- desc: Sample Trend
50
- age: 172800
51
- legend_pos: left
52
- time_pos: left
53
- time_res: m
54
- axes:
55
- - scale: x
56
- range: [-86400, 0] # 86400 172800 1209600
57
- - scale: '°C'
58
- range: [0.0, 100.0]
59
- dp: 1
60
- - scale: '%'
61
- range: [0.0, 100.0]
62
- dp: 1
63
- # side: 1
64
- # bands: # TODO
65
- # - series: [I_Transpower_Limit_Hi, I_Transpower_Limit_Lo]
66
- # fill: [red, 0.2]
67
- # dir: -1
68
- series:
69
- - tagname: cpu_load
70
- label: CPU Load
71
- scale: '%'
72
- color: black
73
- width: 1.5
74
- dp: 1
75
- - tagname: disk_use
76
- label: Disk Use
77
- scale: '%'
78
- color: blue
79
- width: 1
80
- dp: 1
81
- - tagname: cpu_temp
82
- label: CPU Temp
83
- scale: '°C'
84
- color: red
85
- width: 1
86
- dp: 1
87
44
  - name: Files
88
- parent: Dropdown
45
+ parent:
89
46
  items:
90
47
  - 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
- - name: Logix
355
- items:
356
- - {tagname: Ani_Fin_20, type: setpoint}
357
- - {tagname: Ani_Fout_20, type: value}
358
- - {tagname: Ani_Iin_20, type: setpoint}
359
- - {tagname: Ani_Iout_20, type: value}
360
- - {tagname: InVar, type: setpoint}
361
- - {tagname: OutVar, type: value}
362
- - {tagname: Ani_Iin_21_0, type: setpoint}
363
- - {tagname: Ani_Iout_21_0, type: value}
364
- - {tagname: Ani_Iin_21_1, type: setpoint}
365
- - {tagname: Ani_Iout_21_1, type: value}
366
- - name: Values
367
- parent: SNMP
368
- items:
369
- - {tagname: Router_eth1_bytes_in, type: value}
370
- - {tagname: Router_eth1_bytes_out, type: value}
371
- - {tagname: Router_eth2_bytes_in, type: value}
372
- - {tagname: Router_eth2_bytes_out, type: value}
373
- - {tagname: Router_eth3_bytes_in, type: value}
374
- - {tagname: Router_eth3_bytes_out, type: value}
375
- - {tagname: Router_eth4_bytes_in, type: value}
376
- - {tagname: Router_eth4_bytes_out, type: value}
377
- - {tagname: Router_eth5_bytes_in, type: value}
378
- - {tagname: Router_eth5_bytes_out, type: value}
379
- - {tagname: Router_eth6_bytes_in, type: value}
380
- - {tagname: Router_eth6_bytes_out, type: value}
381
- - {tagname: Router_eth7_bytes_in, type: value}
382
- - {tagname: Router_eth7_bytes_out, type: value}
383
- - {tagname: Router_eth8_bytes_in, type: value}
384
- - {tagname: Router_eth8_bytes_out, type: value}
385
- - name: Trend
386
- parent: SNMP
387
- items:
388
- - type: uplot # Do all times in seconds, which uplot uses.
389
- ms:
390
- desc: Bytes
391
- age: 172800
392
- legend_pos: left
393
- time_pos: left
394
- time_res: m
395
- axes:
396
- - scale: x
397
- range: [-86400, 0]
398
- - scale: 'bytes'
399
- range: [0, 100000]
400
- dp: 0
401
- series:
402
- - tagname: Router_eth1_bytes_in
403
- label: eth1 in
404
- scale: 'bytes'
405
- color: violet
406
- width: 1
407
- dp: 0
408
- - tagname: Router_eth1_bytes_out
409
- label: eth1 out
410
- scale: 'bytes'
411
- color: violet
412
- width: 0.5
413
- dp: 0
414
- - tagname: Router_eth2_bytes_in
415
- label: eth2 in
416
- scale: 'bytes'
417
- color: blue
418
- width: 1
419
- dp: 0
420
- - tagname: Router_eth2_bytes_out
421
- label: eth2 out
422
- scale: 'bytes'
423
- color: blue
424
- width: 0.5
425
- dp: 0
426
- - tagname: Router_eth3_bytes_in
427
- label: eth3 in
428
- scale: 'bytes'
429
- color: green
430
- width: 1
431
- dp: 0
432
- - tagname: Router_eth3_bytes_out
433
- label: eth3 out
434
- scale: 'bytes'
435
- color: green
436
- width: 0.5
437
- dp: 0
438
- - tagname: Router_eth4_bytes_in
439
- label: eth4 in
440
- scale: 'bytes'
441
- color: gray
442
- width: 1
443
- dp: 0
444
- - tagname: Router_eth4_bytes_out
445
- label: eth4 out
446
- scale: 'bytes'
447
- color: gray
448
- width: 0.5
449
- dp: 0
450
- - tagname: Router_eth5_bytes_in
451
- label: eth5 in
452
- scale: 'bytes'
453
- color: goldenrod
454
- width: 1
455
- dp: 0
456
- - tagname: Router_eth5_bytes_out
457
- label: eth5 out
458
- scale: 'bytes'
459
- color: goldenrod
460
- width: 0.5
461
- dp: 0
462
- - tagname: Router_eth6_bytes_in
463
- label: eth6 in
464
- scale: 'bytes'
465
- color: brown
466
- width: 1
467
- dp: 0
468
- - tagname: Router_eth6_bytes_out
469
- label: eth6 out
470
- scale: 'bytes'
471
- color: brown
472
- width: 0.5
473
- dp: 0
474
- - tagname: Router_eth7_bytes_in
475
- label: eth7 in
476
- scale: 'bytes'
477
- color: orange
478
- width: 1
479
- dp: 0
480
- - tagname: Router_eth7_bytes_out
481
- label: eth7 out
482
- scale: 'bytes'
483
- color: orange
484
- width: 0.5
485
- dp: 0
486
- - tagname: Router_eth8_bytes_in
487
- label: eth8 in
488
- scale: 'bytes'
489
- color: aqua
490
- width: 1
491
- dp: 0
492
- - tagname: Router_eth8_bytes_out
493
- label: eth8 out
494
- scale: 'bytes'
495
- color: aqua
496
- width: 0.5
497
- dp: 0
498
48
  - name: Ping Values
499
- parent: Ping
49
+ parent:
500
50
  items:
501
51
  - {desc: Default tags, type: h1}
502
52
  - {tagname: localhost_ping, type: value}
503
53
  - {tagname: google_ping, type: value}
504
54
  - {tagname: electronet_ping, type: value}
505
55
  - name: Ping Trend
506
- parent: Ping
56
+ parent:
507
57
  items:
508
58
  - type: uplot # Do all times in seconds, which uplot uses.
509
59
  ms:
@@ -524,11 +74,6 @@ pages:
524
74
  scale: mS
525
75
  color: black
526
76
  dp: 1
527
- - tagname: electronet_ping
528
- label: electronet
529
- scale: mS
530
- color: blue
531
- dp: 1
532
77
  - tagname: google_ping
533
78
  label: google
534
79
  scale: mS
@@ -0,0 +1,126 @@
1
+ """Poll OpenWeather current and forecast APIs."""
2
+ import asyncio
3
+ import aiohttp
4
+ import logging
5
+ from time import time
6
+ from pymscada.bus_client import BusClient
7
+ from pymscada.periodic import Periodic
8
+ from pymscada.tag import Tag
9
+
10
+ class OpenWeatherClient:
11
+ """Get weather data from OpenWeather Current and Forecast APIs."""
12
+
13
+ def __init__(self, bus_ip: str = '127.0.0.1', bus_port: int = 1324,
14
+ proxy: str = None, api: dict = {}, tags: dict = {}) -> None:
15
+ """
16
+ Connect to bus on bus_ip:bus_port.
17
+
18
+ api dict should contain:
19
+ - api_key: OpenWeatherMap API key
20
+ - locations: dict of location names and coordinates
21
+ - times: list of hours ahead to fetch forecast data for
22
+ - units: optional units (standard, metric, imperial)
23
+ """
24
+ self.busclient = None
25
+ if bus_ip is not None:
26
+ self.busclient = BusClient(bus_ip, bus_port, module='OpenWeather')
27
+ self.proxy = proxy
28
+ self.map_bus = id(self)
29
+ self.tags = {tagname: Tag(tagname, float) for tagname in tags}
30
+ self.api_key = api['api_key']
31
+ self.units = api.get('units', 'standard')
32
+ self.locations = api.get('locations', {})
33
+ self.parameters = api.get('parameters', {})
34
+ self.times = api.get('times', [3, 6, 12, 24, 48])
35
+ self.current_url = "https://api.openweathermap.org/data/2.5/weather"
36
+ self.forecast_url = "https://api.openweathermap.org/data/2.5/forecast"
37
+ self.queue = asyncio.Queue()
38
+ self.session = None
39
+ self.handle = None
40
+ self.periodic = None
41
+
42
+ def update_tags(self, location, data, suffix):
43
+ """Update tags for forecast weather."""
44
+ for parameter in self.parameters:
45
+ tagname = f"{location}_{parameter}{suffix}"
46
+ if parameter == 'Temp':
47
+ value = data['main']['temp']
48
+ elif parameter == 'WindSpeed':
49
+ value = data['wind']['speed']
50
+ elif parameter == 'WindDir':
51
+ value = data['wind'].get('deg', 0)
52
+ elif parameter == 'Rain':
53
+ value = data.get('rain', {}).get('1h', 0)
54
+ try:
55
+ self.tags[tagname].value = value
56
+ except KeyError:
57
+ logging.warning(f'{tagname} not found setting weather value')
58
+
59
+ async def handle_response(self):
60
+ """Handle responses from the API."""
61
+ while True:
62
+ location, data = await self.queue.get()
63
+ now = int(time())
64
+ if 'dt' in data:
65
+ self.update_tags(location, data, '')
66
+ elif 'list' in data:
67
+ for forecast in data['list']:
68
+ hours_ahead = int((forecast['dt'] - now) / 3600)
69
+ if hours_ahead not in self.times:
70
+ continue
71
+ suffix = f'_{hours_ahead:02d}'
72
+ self.update_tags(location, forecast, suffix)
73
+
74
+ async def fetch_current_data(self):
75
+ """Fetch current weather data for all locations."""
76
+ if self.session is None:
77
+ self.session = aiohttp.ClientSession()
78
+ for location, coords in self.locations.items():
79
+ base_params = {'lat': coords['lat'], 'lon': coords['lon'],
80
+ 'appid': self.api_key, 'units': self.units }
81
+ try:
82
+ async with self.session.get(self.current_url,
83
+ params=base_params, proxy=self.proxy) as resp:
84
+ if resp.status == 200:
85
+ self.queue.put_nowait((location, await resp.json()))
86
+ else:
87
+ logging.warning('OpenWeather current API error for '
88
+ f'{location}: {resp.status}')
89
+ except Exception as e:
90
+ logging.warning('OpenWeather current API error for '
91
+ f'{location}: {e}')
92
+
93
+ async def fetch_forecast_data(self):
94
+ """Fetch forecast weather data for all locations."""
95
+ if self.session is None:
96
+ self.session = aiohttp.ClientSession()
97
+ for location, coords in self.locations.items():
98
+ base_params = {'lat': coords['lat'], 'lon': coords['lon'],
99
+ 'appid': self.api_key, 'units': self.units }
100
+ try:
101
+ async with self.session.get(self.forecast_url,
102
+ params=base_params, proxy=self.proxy) as resp:
103
+ if resp.status == 200:
104
+ self.queue.put_nowait((location, await resp.json()))
105
+ else:
106
+ logging.warning('OpenWeather forecast API error '
107
+ f'for {location}: {resp.status}')
108
+ except Exception as e:
109
+ logging.warning('OpenWeather forecast API error for '
110
+ f'{location}: {e}')
111
+
112
+ async def poll(self):
113
+ """Poll OpenWeather APIs every 10 minutes."""
114
+ now = int(time())
115
+ if now % 600 == 0: # Every 10 minutes
116
+ await self.fetch_current_data()
117
+ if now % 10800 == 60: # Every 3 hours, offset by 1 minute
118
+ await self.fetch_forecast_data()
119
+
120
+ async def start(self):
121
+ """Start bus connection and API polling."""
122
+ if self.busclient is not None:
123
+ await self.busclient.start()
124
+ self.handle = asyncio.create_task(self.handle_response())
125
+ self.periodic = Periodic(self.poll, 1.0)
126
+ await self.periodic.start()