pymscada 0.1.7__tar.gz → 0.1.9__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.
- {pymscada-0.1.7 → pymscada-0.1.9}/PKG-INFO +1 -1
- {pymscada-0.1.7 → pymscada-0.1.9}/pyproject.toml +1 -1
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/__init__.py +2 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/bus_client.py +1 -1
- pymscada-0.1.9/src/pymscada/demo/accuweather.yaml +16 -0
- pymscada-0.1.9/src/pymscada/demo/pymscada-io-accuweather.service +15 -0
- pymscada-0.1.9/src/pymscada/iodrivers/accuweather.py +96 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/main.py +14 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/tag.py +1 -1
- {pymscada-0.1.7 → pymscada-0.1.9}/LICENSE +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/README.md +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/__main__.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/bus_server.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/checkout.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/config.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/console.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/demo/README.md +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/demo/__init__.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/demo/bus.yaml +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/demo/files.yaml +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/demo/history.yaml +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/demo/logixclient.yaml +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/demo/modbus_plc.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/demo/modbusclient.yaml +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/demo/modbusserver.yaml +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/demo/opnotes.yaml +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/demo/ping.yaml +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/demo/pymscada-bus.service +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/demo/pymscada-demo-modbus_plc.service +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/demo/pymscada-files.service +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/demo/pymscada-history.service +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/demo/pymscada-io-logixclient.service +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/demo/pymscada-io-modbusclient.service +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/demo/pymscada-io-modbusserver.service +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/demo/pymscada-io-ping.service +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/demo/pymscada-io-snmpclient.service +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/demo/pymscada-opnotes.service +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/demo/pymscada-wwwserver.service +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/demo/snmpclient.yaml +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/demo/tags.yaml +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/demo/wwwserver.yaml +6 -6
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/files.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/history.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/iodrivers/__init__.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/iodrivers/logix_client.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/iodrivers/logix_map.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/iodrivers/modbus_client.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/iodrivers/modbus_map.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/iodrivers/modbus_server.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/iodrivers/ping_client.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/iodrivers/ping_map.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/iodrivers/snmp_client.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/iodrivers/snmp_map.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/misc.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/opnotes.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/pdf/__init__.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/pdf/one.pdf +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/pdf/two.pdf +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/periodic.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/protocol_constants.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/samplers.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/tools/snmp_client2.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/tools/walk.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/validate.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/src/pymscada/www_server.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/tests/__init__.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/tests/bus_echo.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/tests/iodrivers/test_logix.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/tests/iodrivers/test_modbus.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/tests/test_assets/busserver.yaml +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/tests/test_assets/db.sqlite +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/tests/test_assets/hist_tag_0_0.dat +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/tests/test_assets/hist_tag_0_10_2.dat +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/tests/test_assets/hist_tag_0_15.dat +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/tests/test_assets/hist_tag_0_26.dat +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/tests/test_assets/hist_tag_0_50.dat +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/tests/test_bus_server.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/tests/test_config.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/tests/test_history.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/tests/test_misc.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/tests/test_opnotes.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/tests/test_periodic.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/tests/test_samplers.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/tests/test_tag.py +0 -0
- {pymscada-0.1.7 → pymscada-0.1.9}/tests/test_validate.py +0 -0
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
from pymscada.bus_client import BusClient
|
|
3
3
|
from pymscada.bus_server import BusServer
|
|
4
4
|
from pymscada.config import Config
|
|
5
|
+
from pymscada.iodrivers.accuweather import AccuWeatherClient
|
|
5
6
|
from pymscada.iodrivers.logix_client import LogixClient
|
|
6
7
|
from pymscada.iodrivers.modbus_client import ModbusClient
|
|
7
8
|
from pymscada.iodrivers.modbus_server import ModbusServer
|
|
@@ -14,6 +15,7 @@ __all__ = [
|
|
|
14
15
|
'BusClient',
|
|
15
16
|
'BusServer',
|
|
16
17
|
'Config',
|
|
18
|
+
'AccuWeatherClient',
|
|
17
19
|
'LogixClient',
|
|
18
20
|
'ModbusClient',
|
|
19
21
|
'ModbusServer',
|
|
@@ -109,7 +109,7 @@ class BusClient:
|
|
|
109
109
|
self.ip, self.port)
|
|
110
110
|
self.addr = self.writer.get_extra_info('sockname')
|
|
111
111
|
self.write(pc.CMD_LOG, 0, 0, f'{self.module} connected'.encode())
|
|
112
|
-
logging.
|
|
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)
|
|
115
115
|
Tag.set_notify(self.add_tag)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
bus_ip: 127.0.0.1
|
|
2
|
+
bus_port: 1325
|
|
3
|
+
proxy: 'http://127.0.0.1:3128'
|
|
4
|
+
api:
|
|
5
|
+
url: http://dataservice.accuweather.com/forecasts/v1/hourly/12hour/
|
|
6
|
+
query:
|
|
7
|
+
apikey: <your key>
|
|
8
|
+
details: 'true'
|
|
9
|
+
metric: 'true'
|
|
10
|
+
locations:
|
|
11
|
+
Murupara: 246970
|
|
12
|
+
times: [1, 3, 12]
|
|
13
|
+
tags:
|
|
14
|
+
- MuruparaTemp_1
|
|
15
|
+
- MuruparaTemp_3
|
|
16
|
+
- MuruparaTemp_12
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=pymscada - AccuWeather client
|
|
3
|
+
BindsTo=pymscada-bus.service
|
|
4
|
+
After=pymscada-bus.service
|
|
5
|
+
|
|
6
|
+
[Service]
|
|
7
|
+
WorkingDirectory=__DIR__
|
|
8
|
+
ExecStart=__PYMSCADA__ accuweatherclient --config __DIR__/config/accuweather.yaml
|
|
9
|
+
Restart=always
|
|
10
|
+
RestartSec=5
|
|
11
|
+
User=__USER__
|
|
12
|
+
Group=__USER__
|
|
13
|
+
|
|
14
|
+
[Install]
|
|
15
|
+
WantedBy=multi-user.target
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Poll SNMP OIDs from devices."""
|
|
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
|
+
|
|
11
|
+
class AccuWeatherClient:
|
|
12
|
+
"""Get forecast information from AccuWeather."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, bus_ip: str = '127.0.0.1', bus_port: int = 1324,
|
|
15
|
+
proxy: str = None, api: dict = {}, tags: dict = {}) -> None:
|
|
16
|
+
"""
|
|
17
|
+
Connect to bus on bus_ip:bus_port.
|
|
18
|
+
|
|
19
|
+
Event loop must be running.
|
|
20
|
+
"""
|
|
21
|
+
self.busclient = None
|
|
22
|
+
if bus_ip is not None:
|
|
23
|
+
self.busclient = BusClient(bus_ip, bus_port, module='AccuWeather')
|
|
24
|
+
self.proxy = proxy
|
|
25
|
+
self.map_bus = id(self)
|
|
26
|
+
self.tags = {tagname: Tag(tagname, float) for tagname in tags}
|
|
27
|
+
self.api = api
|
|
28
|
+
self.urls = [[f'{api["url"]}{x}?', api['query']]
|
|
29
|
+
for x in api['locations'].values()]
|
|
30
|
+
self.session = aiohttp.ClientSession()
|
|
31
|
+
self.queue = asyncio.Queue()
|
|
32
|
+
self.init_run = True
|
|
33
|
+
|
|
34
|
+
async def handle_response(self):
|
|
35
|
+
"""Unpack the weather values from the json response."""
|
|
36
|
+
while True:
|
|
37
|
+
resp = await self.queue.get()
|
|
38
|
+
site = resp[0]
|
|
39
|
+
json = resp[1]
|
|
40
|
+
time_s = int(time())
|
|
41
|
+
time_mod = time_s - time_s % 3600
|
|
42
|
+
for record in json:
|
|
43
|
+
epoch = record['EpochDateTime']
|
|
44
|
+
hour = int((epoch - time_mod) / 3600)
|
|
45
|
+
logging.warning(f'epoch {epoch} time_s {time_s} hour {hour}')
|
|
46
|
+
if hour not in self.api['times']:
|
|
47
|
+
continue
|
|
48
|
+
suffix = ''
|
|
49
|
+
if hour > 0:
|
|
50
|
+
suffix = f'_{int(hour)}'
|
|
51
|
+
for parm, key1, key2, key in [
|
|
52
|
+
['Temp', 'Temperature', None, 'Value'],
|
|
53
|
+
['WindDir', 'Wind', 'Direction', 'Degrees'],
|
|
54
|
+
['WindSpeed', 'Wind', 'Speed', 'Value'],
|
|
55
|
+
['Rain', 'Rain', None, 'Value'],
|
|
56
|
+
['Snow', 'Snow', None, 'Value']
|
|
57
|
+
]:
|
|
58
|
+
tag = self.tags[f'{site}{parm}{suffix}']
|
|
59
|
+
if key2 is None:
|
|
60
|
+
value = record[key1][key]
|
|
61
|
+
else:
|
|
62
|
+
value = record[key1][key2][key]
|
|
63
|
+
if tag.value != value:
|
|
64
|
+
tag.value = value, int(epoch * 1e6), self.map_bus
|
|
65
|
+
|
|
66
|
+
async def fetch_data(self, location, url, query):
|
|
67
|
+
"""HTTP get."""
|
|
68
|
+
logging.warning(f'poll {location} {url}')
|
|
69
|
+
try:
|
|
70
|
+
async with self.session.get(url, params=query,
|
|
71
|
+
proxy=self.proxy) as resp:
|
|
72
|
+
self.queue.put_nowait([location, await resp.json()])
|
|
73
|
+
except asyncio.TimeoutError as e:
|
|
74
|
+
logging.warning('AccuWeather {e}')
|
|
75
|
+
|
|
76
|
+
async def poll(self):
|
|
77
|
+
"""Poll the weather site near the start of each hour."""
|
|
78
|
+
now = int(time())
|
|
79
|
+
if now % 3600 != 120 and not self.init_run:
|
|
80
|
+
return
|
|
81
|
+
self.init_run = False
|
|
82
|
+
if not self.queue.empty():
|
|
83
|
+
return
|
|
84
|
+
# Get the weather forecasts
|
|
85
|
+
query = self.api['query']
|
|
86
|
+
for location, location_id in self.api['locations'].items():
|
|
87
|
+
url = f'{self.api["url"]}{location_id}'
|
|
88
|
+
asyncio.create_task(self.fetch_data(location, url, query))
|
|
89
|
+
|
|
90
|
+
async def start(self):
|
|
91
|
+
"""Start bus connection and PLC polling."""
|
|
92
|
+
if self.busclient is not None:
|
|
93
|
+
await self.busclient.start()
|
|
94
|
+
self.handle = asyncio.create_task(self.handle_response())
|
|
95
|
+
self.periodic = Periodic(self.poll, 1.0)
|
|
96
|
+
await self.periodic.start()
|
|
@@ -12,6 +12,7 @@ from pymscada.files import Files
|
|
|
12
12
|
from pymscada.history import History
|
|
13
13
|
from pymscada.opnotes import OpNotes
|
|
14
14
|
from pymscada.www_server import WwwServer
|
|
15
|
+
from pymscada.iodrivers.accuweather import AccuWeatherClient
|
|
15
16
|
from pymscada.iodrivers.logix_client import LogixClient
|
|
16
17
|
from pymscada.iodrivers.modbus_client import ModbusClient
|
|
17
18
|
from pymscada.iodrivers.modbus_server import ModbusServer
|
|
@@ -186,6 +187,18 @@ class _validate(Module):
|
|
|
186
187
|
print(e)
|
|
187
188
|
|
|
188
189
|
|
|
190
|
+
class _AccuWeatherClient(Module):
|
|
191
|
+
"""Bus Module."""
|
|
192
|
+
|
|
193
|
+
name = 'accuweatherclient'
|
|
194
|
+
help = 'poll weather information'
|
|
195
|
+
|
|
196
|
+
def run_once(self, options):
|
|
197
|
+
"""Create the module."""
|
|
198
|
+
config = Config(options.config)
|
|
199
|
+
self.module = AccuWeatherClient(**config)
|
|
200
|
+
|
|
201
|
+
|
|
189
202
|
class _LogixClient(Module):
|
|
190
203
|
"""Bus Module."""
|
|
191
204
|
|
|
@@ -275,6 +288,7 @@ def args(_version: str):
|
|
|
275
288
|
_Console(s)
|
|
276
289
|
_checkout(s)
|
|
277
290
|
_validate(s)
|
|
291
|
+
_AccuWeatherClient(s)
|
|
278
292
|
_LogixClient(s)
|
|
279
293
|
_ModbusServer(s)
|
|
280
294
|
_ModbusClient(s)
|
|
@@ -240,7 +240,7 @@ class Tag(metaclass=UniqueTag):
|
|
|
240
240
|
def time_us(self, time_us: int):
|
|
241
241
|
"""Make sure this is int, should _always_ be. TODO remove."""
|
|
242
242
|
if type(time_us) is not int:
|
|
243
|
-
logging.warning(f"{self.name} was not an int, FIX.")
|
|
243
|
+
logging.warning(f"{self.name} time_us was not an int, FIX.")
|
|
244
244
|
self.__time_us = int(time_us)
|
|
245
245
|
else:
|
|
246
246
|
self.__time_us = time_us
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -5,6 +5,12 @@ port: 8324
|
|
|
5
5
|
get_path: __HOME__/angmscada/dist/angmscada
|
|
6
6
|
serve_path: __HOME__/pymscada
|
|
7
7
|
pages:
|
|
8
|
+
- name: Notes
|
|
9
|
+
parent:
|
|
10
|
+
items:
|
|
11
|
+
- type: opnote
|
|
12
|
+
site: [Site 1, Site 2]
|
|
13
|
+
by: [B1, B2]
|
|
8
14
|
- name: Default Main
|
|
9
15
|
parent:
|
|
10
16
|
items:
|
|
@@ -35,12 +41,6 @@ pages:
|
|
|
35
41
|
- Four
|
|
36
42
|
- Five
|
|
37
43
|
- {tagname: FloatSelect, type: selectdict, opts: {type: float, dp: 2}}
|
|
38
|
-
- name: Notes
|
|
39
|
-
parent:
|
|
40
|
-
items:
|
|
41
|
-
- type: opnote
|
|
42
|
-
site: [Site 1, Site 2]
|
|
43
|
-
by: [B1, B2]
|
|
44
44
|
- name: Trends
|
|
45
45
|
parent: Dropdown
|
|
46
46
|
items:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|