pymscada 0.2.0rc6__py3-none-any.whl → 0.2.0rc8__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.
Potentially problematic release.
This version of pymscada might be problematic. Click here for more details.
- pymscada/alarms.py +8 -9
- pymscada/bus_client.py +2 -2
- pymscada/callout.py +203 -0
- pymscada/demo/callout.yaml +16 -0
- pymscada/demo/pymscada-io-witsapi.service +15 -0
- pymscada/demo/tags.yaml +4 -0
- pymscada/module_config.py +9 -3
- {pymscada-0.2.0rc6.dist-info → pymscada-0.2.0rc8.dist-info}/METADATA +1 -1
- {pymscada-0.2.0rc6.dist-info → pymscada-0.2.0rc8.dist-info}/RECORD +14 -11
- /pymscada/demo/{wits.yaml → witsapi.yaml} +0 -0
- {pymscada-0.2.0rc6.dist-info → pymscada-0.2.0rc8.dist-info}/WHEEL +0 -0
- {pymscada-0.2.0rc6.dist-info → pymscada-0.2.0rc8.dist-info}/entry_points.txt +0 -0
- {pymscada-0.2.0rc6.dist-info → pymscada-0.2.0rc8.dist-info}/licenses/LICENSE +0 -0
- {pymscada-0.2.0rc6.dist-info → pymscada-0.2.0rc8.dist-info}/top_level.txt +0 -0
pymscada/alarms.py
CHANGED
|
@@ -11,14 +11,12 @@ ALM = 0
|
|
|
11
11
|
RTN = 1
|
|
12
12
|
ACT = 2
|
|
13
13
|
INF = 3
|
|
14
|
-
|
|
15
14
|
KIND = {
|
|
16
15
|
ALM: 'ALM',
|
|
17
16
|
RTN: 'RTN',
|
|
18
17
|
ACT: 'ACT',
|
|
19
18
|
INF: 'INF'
|
|
20
19
|
}
|
|
21
|
-
|
|
22
20
|
NORMAL = 0
|
|
23
21
|
ALARM = 1
|
|
24
22
|
|
|
@@ -46,8 +44,8 @@ def standardise_tag_info(tagname: str, tag: dict):
|
|
|
46
44
|
if 'desc' not in tag:
|
|
47
45
|
logging.warning(f"Tag {tagname} has no description, using name")
|
|
48
46
|
tag['desc'] = tag['name']
|
|
49
|
-
if '
|
|
50
|
-
tag['
|
|
47
|
+
if 'group' not in tag:
|
|
48
|
+
tag['group'] = ''
|
|
51
49
|
if 'multi' in tag:
|
|
52
50
|
tag['type'] = int
|
|
53
51
|
else:
|
|
@@ -107,7 +105,7 @@ class Alarm():
|
|
|
107
105
|
Generates the ALM and RTN messages for Alarms to publish via rta_tag.
|
|
108
106
|
"""
|
|
109
107
|
|
|
110
|
-
def __init__(self, tagname: str, tag: dict, alarm: str,
|
|
108
|
+
def __init__(self, tagname: str, tag: dict, alarm: str, group: str, rta_cb, alarms) -> None:
|
|
111
109
|
"""Initialize alarm with tag and condition(s)."""
|
|
112
110
|
self.alarm_id = f'{tagname} {alarm}'
|
|
113
111
|
self.tag = Tag(tagname, tag['type'])
|
|
@@ -115,7 +113,7 @@ class Alarm():
|
|
|
115
113
|
self.tag.dp = tag['dp']
|
|
116
114
|
self.tag.units = tag['units']
|
|
117
115
|
self.tag.add_callback(self.callback)
|
|
118
|
-
self.
|
|
116
|
+
self.group = group
|
|
119
117
|
self.rta_cb = rta_cb
|
|
120
118
|
self.alarms = alarms
|
|
121
119
|
self.alarm = split_operator(alarm)
|
|
@@ -166,7 +164,7 @@ class Alarm():
|
|
|
166
164
|
'kind': kind,
|
|
167
165
|
'desc': f'{self.tag.desc} {value:.{self.tag.dp}f}'
|
|
168
166
|
f' {self.tag.units}',
|
|
169
|
-
'group': self.
|
|
167
|
+
'group': self.group
|
|
170
168
|
})
|
|
171
169
|
|
|
172
170
|
def check_duration(self, current_time_us: int):
|
|
@@ -224,9 +222,10 @@ class Alarms:
|
|
|
224
222
|
standardise_tag_info(tagname, tag)
|
|
225
223
|
if 'alarm' not in tag or tag['type'] not in (int, float):
|
|
226
224
|
continue
|
|
227
|
-
|
|
225
|
+
group = tag['group']
|
|
228
226
|
for alarm in tag['alarm']:
|
|
229
|
-
new_alarm = Alarm(tagname, tag, alarm,
|
|
227
|
+
new_alarm = Alarm(tagname, tag, alarm, group, self.rta_cb,
|
|
228
|
+
self)
|
|
230
229
|
self.alarms.append(new_alarm)
|
|
231
230
|
self.busclient = BusClient(bus_ip, bus_port, module='Alarms')
|
|
232
231
|
self.rta = Tag(rta_tag, dict)
|
pymscada/bus_client.py
CHANGED
|
@@ -17,8 +17,8 @@ class BusClient:
|
|
|
17
17
|
the client dies. A connection is mandatory for the client to run.
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
|
-
def __init__(self, ip: str = '127.0.0.1', port: int = 1324,
|
|
21
|
-
module: str = '_unset_'):
|
|
20
|
+
def __init__(self, ip: str | None = '127.0.0.1', port: int | None = 1324,
|
|
21
|
+
tag_info=None, module: str = '_unset_'):
|
|
22
22
|
"""Create bus server."""
|
|
23
23
|
self.ip = ip
|
|
24
24
|
self.port = port
|
pymscada/callout.py
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""Callout handling."""
|
|
2
|
+
import logging
|
|
3
|
+
import socket
|
|
4
|
+
import time
|
|
5
|
+
from pymscada.bus_client import BusClient
|
|
6
|
+
from pymscada.periodic import Periodic
|
|
7
|
+
from pymscada.tag import Tag
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
Callout monitors alarms and sends SMS notifications to configured callees.
|
|
12
|
+
|
|
13
|
+
Configuration:
|
|
14
|
+
- callees: list of recipients with SMS numbers, delays, and group filters
|
|
15
|
+
- alarms_tag: RTA tag receiving alarms from alarms.py
|
|
16
|
+
- ack_tag: tag for receiving acknowledgments
|
|
17
|
+
- rta_tag: tag for configuration updates and SMS requests
|
|
18
|
+
|
|
19
|
+
Operation:
|
|
20
|
+
1. Collect alarms from alarms_tag (kind=ALM)
|
|
21
|
+
2. For each callee, check if alarms match their group filter and delay
|
|
22
|
+
3. Send SMS via rta_tag when conditions are met
|
|
23
|
+
4. Track sent notifications in alarm['sent'] hash
|
|
24
|
+
5. Remove alarms when callee acknowledges (matches their group)
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
ALM = 0
|
|
28
|
+
IDLE = 0
|
|
29
|
+
NEW_ALM = 1
|
|
30
|
+
CALLOUT = 2
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def normalise_callees(callees: list | None):
|
|
34
|
+
"""Normalise callees to include delay_ms and remove delay."""
|
|
35
|
+
if callees is None:
|
|
36
|
+
return []
|
|
37
|
+
for callee in callees:
|
|
38
|
+
if 'sms' not in callee:
|
|
39
|
+
raise ValueError(f'Callee {callee["name"]} has no sms number')
|
|
40
|
+
if 'delay' in callee:
|
|
41
|
+
callee['delay_ms'] = callee['delay'] * 1000
|
|
42
|
+
del callee['delay']
|
|
43
|
+
else:
|
|
44
|
+
callee['delay_ms'] = 0
|
|
45
|
+
if 'group' not in callee:
|
|
46
|
+
callee['group'] = []
|
|
47
|
+
callees.sort(key=lambda x: x['delay_ms'])
|
|
48
|
+
return callees
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def alarm_in_callee_group(alarm_group: list, callee_group: list):
|
|
52
|
+
"""Check if alarm_group is in callee_group."""
|
|
53
|
+
if not callee_group:
|
|
54
|
+
if not alarm_group:
|
|
55
|
+
return ''
|
|
56
|
+
return alarm_group[0]
|
|
57
|
+
if not alarm_group:
|
|
58
|
+
return None
|
|
59
|
+
for group in alarm_group:
|
|
60
|
+
if group in callee_group:
|
|
61
|
+
return group
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class Callout:
|
|
66
|
+
"""Connect to bus_ip:bus_port, monitor alarms and manage callouts."""
|
|
67
|
+
|
|
68
|
+
def __init__(
|
|
69
|
+
self,
|
|
70
|
+
bus_ip: str | None = '127.0.0.1',
|
|
71
|
+
bus_port: int | None = 1324,
|
|
72
|
+
rta_tag: str = '__callout__',
|
|
73
|
+
alarms_tag: str | None = None,
|
|
74
|
+
ack_tag: str | None = None,
|
|
75
|
+
status_tag: str | None = None,
|
|
76
|
+
callees: list | None = None,
|
|
77
|
+
groups: list | None = None
|
|
78
|
+
) -> None:
|
|
79
|
+
"""
|
|
80
|
+
Connect to bus_ip:bus_port, monitor alarms and manage callouts.
|
|
81
|
+
|
|
82
|
+
Monitor alarms via alarms_tag and manage callout messages to callees
|
|
83
|
+
based on configured delays and area filters.
|
|
84
|
+
|
|
85
|
+
Event loop must be running.
|
|
86
|
+
|
|
87
|
+
For testing only: bus_ip can be None to skip connection.
|
|
88
|
+
"""
|
|
89
|
+
if bus_ip is None:
|
|
90
|
+
logging.warning('Callout has bus_ip=None, only use for testing')
|
|
91
|
+
else:
|
|
92
|
+
try:
|
|
93
|
+
socket.gethostbyname(bus_ip)
|
|
94
|
+
except socket.gaierror as e:
|
|
95
|
+
raise ValueError(f'Cannot resolve IP/hostname: {e}')
|
|
96
|
+
if not isinstance(bus_port, int):
|
|
97
|
+
raise TypeError('bus_port must be an integer')
|
|
98
|
+
if not 1024 <= bus_port <= 65535:
|
|
99
|
+
raise ValueError('bus_port must be between 1024 and 65535')
|
|
100
|
+
if not isinstance(rta_tag, str) or not rta_tag:
|
|
101
|
+
raise ValueError('rta_tag must be a non-empty string')
|
|
102
|
+
|
|
103
|
+
logging.warning(f'Callout {bus_ip} {bus_port} {rta_tag}')
|
|
104
|
+
self.alarms = []
|
|
105
|
+
self.callees = normalise_callees(callees)
|
|
106
|
+
self.groups = []
|
|
107
|
+
if groups is not None:
|
|
108
|
+
self.groups = groups
|
|
109
|
+
self.status = None
|
|
110
|
+
if status_tag is not None:
|
|
111
|
+
self.status = Tag(status_tag, int)
|
|
112
|
+
self.status.value = IDLE
|
|
113
|
+
self.busclient = BusClient(bus_ip, bus_port, module='Callout')
|
|
114
|
+
self.busclient.add_callback_rta(alarms_tag, self.alarms_cb)
|
|
115
|
+
self.busclient.add_callback_rta(ack_tag, self.ack_cb)
|
|
116
|
+
self.rta = Tag(rta_tag, dict)
|
|
117
|
+
self.rta.value = {}
|
|
118
|
+
self.busclient.add_callback_rta(rta_tag, self.rta_cb)
|
|
119
|
+
self.periodic = Periodic(self.periodic_cb, 1.0)
|
|
120
|
+
|
|
121
|
+
def alarms_cb(self, request):
|
|
122
|
+
"""Handle alarm messages from alarms.py."""
|
|
123
|
+
if request['kind'] != ALM:
|
|
124
|
+
return
|
|
125
|
+
alarm = {
|
|
126
|
+
'date_ms': request['date_ms'],
|
|
127
|
+
'alarm_string': request['alarm_string'],
|
|
128
|
+
'desc': request['desc'],
|
|
129
|
+
'group': request['group'],
|
|
130
|
+
'sent': {}
|
|
131
|
+
}
|
|
132
|
+
self.alarms.append(alarm)
|
|
133
|
+
logging.info(f'Added alarm to list: {alarm}')
|
|
134
|
+
|
|
135
|
+
def ack_cb(self, ack: str):
|
|
136
|
+
"""Handle ACK requests for alarm acknowledgment."""
|
|
137
|
+
if ack == '__all':
|
|
138
|
+
self.alarms = []
|
|
139
|
+
return
|
|
140
|
+
callee = None
|
|
141
|
+
for c in self.callees:
|
|
142
|
+
if ack == c['sms']:
|
|
143
|
+
callee = c
|
|
144
|
+
break
|
|
145
|
+
if ack == c['name']:
|
|
146
|
+
callee = c
|
|
147
|
+
break
|
|
148
|
+
if callee is None:
|
|
149
|
+
logging.warning(f'ACK rejected: {ack}')
|
|
150
|
+
return
|
|
151
|
+
logging.info(f'ACK accepted: {ack}')
|
|
152
|
+
group = callee['group']
|
|
153
|
+
remaining_alarms = []
|
|
154
|
+
for alarm in self.alarms:
|
|
155
|
+
alarm_group = alarm_in_callee_group(alarm['group'], group)
|
|
156
|
+
if alarm_group is None:
|
|
157
|
+
remaining_alarms.append(alarm)
|
|
158
|
+
self.alarms = remaining_alarms
|
|
159
|
+
|
|
160
|
+
def rta_cb(self, request):
|
|
161
|
+
"""Handle RTA requests for callout configuration."""
|
|
162
|
+
if 'action' not in request:
|
|
163
|
+
logging.warning(f'rta_cb malformed {request}')
|
|
164
|
+
elif request['action'] == 'MODIFY':
|
|
165
|
+
for callee in self.callees:
|
|
166
|
+
if callee['name'] == request['name']:
|
|
167
|
+
if 'delay' in request:
|
|
168
|
+
callee['delay_ms'] = request['delay'] * 1000
|
|
169
|
+
if 'group' in request:
|
|
170
|
+
callee['group'] = request['group']
|
|
171
|
+
logging.info(f'Modified callee with {request}')
|
|
172
|
+
|
|
173
|
+
def check_callouts(self):
|
|
174
|
+
"""Check alarms and send callouts. Can be called independently for testing."""
|
|
175
|
+
time_ms = int(time.time() * 1000)
|
|
176
|
+
for callee in self.callees:
|
|
177
|
+
message = ''
|
|
178
|
+
count = 0
|
|
179
|
+
group = callee['group']
|
|
180
|
+
notify_ms = time_ms - callee['delay_ms']
|
|
181
|
+
for alarm in self.alarms:
|
|
182
|
+
if alarm['date_ms'] < notify_ms:
|
|
183
|
+
alarm_group = alarm_in_callee_group(alarm['group'], group)
|
|
184
|
+
if alarm_group is not None and callee['name'] not in alarm['sent']:
|
|
185
|
+
count += 1
|
|
186
|
+
message += f"{alarm['alarm_string']}\n"
|
|
187
|
+
alarm['sent'][callee['name']] = time_ms
|
|
188
|
+
if count > 0:
|
|
189
|
+
send_message = f"{alarm_group} {count} unack alarms\n{message}"
|
|
190
|
+
logging.warning(f'Callout to {callee["name"]}: {send_message}')
|
|
191
|
+
self.rta.value = {'action': 'SMS', 'sms': callee['sms'],
|
|
192
|
+
'message': send_message}
|
|
193
|
+
if self.status is not None:
|
|
194
|
+
self.status.value = CALLOUT
|
|
195
|
+
|
|
196
|
+
async def periodic_cb(self):
|
|
197
|
+
"""Periodic callback to check alarms and send callouts."""
|
|
198
|
+
self.check_callouts()
|
|
199
|
+
|
|
200
|
+
async def start(self):
|
|
201
|
+
"""Async startup."""
|
|
202
|
+
await self.busclient.start()
|
|
203
|
+
await self.periodic.start()
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
bus_ip: 127.0.0.1
|
|
2
|
+
bus_port: 1324
|
|
3
|
+
rta_tag: __callout__
|
|
4
|
+
alarms: __alarms__
|
|
5
|
+
ack: SI_Alarm_Ack
|
|
6
|
+
status: SO_Alarm_Status
|
|
7
|
+
callees:
|
|
8
|
+
- name: A name
|
|
9
|
+
sms: A number
|
|
10
|
+
- name: B name
|
|
11
|
+
sms: B number
|
|
12
|
+
groups:
|
|
13
|
+
- name: Aniwhenua Station
|
|
14
|
+
group:
|
|
15
|
+
- name: Aniwhenua System
|
|
16
|
+
group: System
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=pymscada - WITS client
|
|
3
|
+
BindsTo=pymscada-bus.service
|
|
4
|
+
After=pymscada-bus.service
|
|
5
|
+
|
|
6
|
+
[Service]
|
|
7
|
+
WorkingDirectory=__DIR__
|
|
8
|
+
ExecStart=__PYMSCADA__ witsapiclient --config __DIR__/config/witsapi.yaml
|
|
9
|
+
Restart=always
|
|
10
|
+
RestartSec=5
|
|
11
|
+
User=__USER__
|
|
12
|
+
Group=__USER__
|
|
13
|
+
|
|
14
|
+
[Install]
|
|
15
|
+
WantedBy=multi-user.target
|
pymscada/demo/tags.yaml
CHANGED
|
@@ -113,10 +113,14 @@ localhost_ping:
|
|
|
113
113
|
desc: Ping time to localhost
|
|
114
114
|
type: float
|
|
115
115
|
units: ms
|
|
116
|
+
alarm: '> 500 for 300'
|
|
117
|
+
group: System
|
|
116
118
|
google_ping:
|
|
117
119
|
desc: Ping time to google
|
|
118
120
|
type: float
|
|
119
121
|
units: ms
|
|
122
|
+
alarm: '> 100 for 30'
|
|
123
|
+
group: System
|
|
120
124
|
Murupara_Temp:
|
|
121
125
|
desc: Murupara Temperature
|
|
122
126
|
type: float
|
pymscada/module_config.py
CHANGED
|
@@ -9,14 +9,14 @@ from pymscada.config import Config
|
|
|
9
9
|
class ModuleArgument:
|
|
10
10
|
def __init__(self, args: tuple[str, ...], kwargs: dict[str, Any]):
|
|
11
11
|
self.args = args
|
|
12
|
-
self.kwargs = kwargs
|
|
12
|
+
self.kwargs = kwargs
|
|
13
13
|
|
|
14
14
|
class ModuleDefinition:
|
|
15
15
|
"""Defines a module's configuration and behavior."""
|
|
16
|
-
def __init__(self, name: str, help: str, module_class:
|
|
16
|
+
def __init__(self, name: str, help: str, module_class: Any, *,
|
|
17
17
|
config: bool = True, tags: bool = True,
|
|
18
18
|
epilog: Optional[str] = None,
|
|
19
|
-
extra_args: list[ModuleArgument] = None,
|
|
19
|
+
extra_args: Optional[list[ModuleArgument]] = None,
|
|
20
20
|
await_future: bool = True):
|
|
21
21
|
self.name = name
|
|
22
22
|
self.help = help
|
|
@@ -63,6 +63,12 @@ def create_module_registry():
|
|
|
63
63
|
help='alarms',
|
|
64
64
|
module_class='pymscada.alarms:Alarms'
|
|
65
65
|
),
|
|
66
|
+
ModuleDefinition(
|
|
67
|
+
name='callout',
|
|
68
|
+
help='alarm callout notifications',
|
|
69
|
+
module_class='pymscada.callout:Callout',
|
|
70
|
+
tags=False
|
|
71
|
+
),
|
|
66
72
|
ModuleDefinition(
|
|
67
73
|
name='validate',
|
|
68
74
|
help='validate config files',
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
pymscada/__init__.py,sha256=NV_cIIwe66Ugp8ns426rtfJIIyskWbqwImD9p_5p0bQ,739
|
|
2
2
|
pymscada/__main__.py,sha256=WcyVlrYOoDdktJhOoyubTOycMwpayksFdxwelRU5xpQ,272
|
|
3
|
-
pymscada/alarms.py,sha256=
|
|
4
|
-
pymscada/bus_client.py,sha256=
|
|
3
|
+
pymscada/alarms.py,sha256=eo-9-DSwV825ihuHApaYkADPpLouKbNLHyh0z4bdPoU,12936
|
|
4
|
+
pymscada/bus_client.py,sha256=eRGvHQ4sFM_n3rKvKDWZQJPQJqvKMWvxbH3MjtT2WBo,9131
|
|
5
5
|
pymscada/bus_server.py,sha256=k7ht2SAr24Oab0hBOPeW4NRDF_RK-F46iE0cMzh7K4w,12323
|
|
6
|
+
pymscada/callout.py,sha256=o_GCB-3IFG-QIcHfrmW-N9CTTlsIu9nMLC-F1jQu1hQ,7274
|
|
6
7
|
pymscada/checkout.py,sha256=RLuCMTEuUI7pp1hIRAUPbo8xYFta8TjArelx0SD4gOY,3897
|
|
7
8
|
pymscada/config.py,sha256=vwGxieaJBYXiHNQEOYVDFaPuGmnUlCnbNm_W9bugKlc,1851
|
|
8
9
|
pymscada/console.py,sha256=EEsJLCvn8AFimN8qGNilX0ks6t3OFcGW5nw6OVAXfac,8850
|
|
@@ -10,7 +11,7 @@ pymscada/files.py,sha256=iouEOPfEkVI0Qbbf1p-L324Y04zSrynVypLW0-1MThA,2499
|
|
|
10
11
|
pymscada/history.py,sha256=7UEOeMnlSMv0LoWTqLWx7QwOW1FZZ4wAvzH6v6b0_vI,11592
|
|
11
12
|
pymscada/main.py,sha256=d6EnK4-tEcvM5AqMHYhvqlnSh-E_wd0Tuxk-kXYSiDw,1854
|
|
12
13
|
pymscada/misc.py,sha256=0Cj6OFhQonyhyk9x0BG5MiS-6EPk_w6zvavt8o_Hlf0,622
|
|
13
|
-
pymscada/module_config.py,sha256=
|
|
14
|
+
pymscada/module_config.py,sha256=K-ZBO-2rZTdlDbVkeQ3_4uI75EUxVMmanVzDuuDNsao,9564
|
|
14
15
|
pymscada/opnotes.py,sha256=MKM51IrB93B2-kgoTzlpOLpaMYs-7rPQHWmRLME-hQQ,7952
|
|
15
16
|
pymscada/periodic.py,sha256=MLlL93VLvFqBBgjO1Us1t0aLHTZ5BFdW0B__G02T1nQ,1235
|
|
16
17
|
pymscada/protocol_constants.py,sha256=lPJ4JEgFJ_puJjTym83EJIOw3UTUFbuFMwg3ohyUAGY,2414
|
|
@@ -23,6 +24,7 @@ pymscada/demo/__init__.py,sha256=WsDDgkWnZBJbt2-cJCdc2NvRAv_T4a7WOC1Q0k_l0gI,29
|
|
|
23
24
|
pymscada/demo/accuweather.yaml,sha256=Fk4rV0S8jCau0173QCzKW8TdUbc4crYVi0aD8fPLNgU,322
|
|
24
25
|
pymscada/demo/alarms.yaml,sha256=Ea8tLZ0aEiyKM_m5MN1TF6xS-lI5ReXiz2oUPO8GvmQ,110
|
|
25
26
|
pymscada/demo/bus.yaml,sha256=zde5JDo2Yv5s7NvJ569gAEoTDvsvgBwRPxfrYhsxj3w,26
|
|
27
|
+
pymscada/demo/callout.yaml,sha256=ze2UfAymU05lzVp-t8wSa9hYJMGSkhT4FwL4rVJQG4o,270
|
|
26
28
|
pymscada/demo/files.yaml,sha256=XWtmGDJxtD4qdl2h7miUfJYkDKsvwNTgQjlGpR6LQNs,163
|
|
27
29
|
pymscada/demo/history.yaml,sha256=c0OuYe8LbTeZqJGU2WKGgTEkOA0IYAjO3e046ddQB8E,55
|
|
28
30
|
pymscada/demo/logixclient.yaml,sha256=G_NlJhBYwT1a9ceHDgO6fCNKFmBM2pVO_t9Xa1NqlRY,912
|
|
@@ -43,11 +45,12 @@ pymscada/demo/pymscada-io-modbusserver.service,sha256=g7Rzm6zGLq_qvTJRL_pcLl4Ps7
|
|
|
43
45
|
pymscada/demo/pymscada-io-openweather.service,sha256=SQnZ-cq1V3qvZY7EgR_Vx36vCOw1ipfGoLoutHsxtNk,359
|
|
44
46
|
pymscada/demo/pymscada-io-ping.service,sha256=Fm8qR4IVq0NupEvHLGONXGwjjQsx5VqaBYPewhg7-k4,329
|
|
45
47
|
pymscada/demo/pymscada-io-snmpclient.service,sha256=Rsm8uiwnoGx-1MkXqYgtj4UP9-r7AEEeB9yoR0y0oVA,343
|
|
48
|
+
pymscada/demo/pymscada-io-witsapi.service,sha256=ZjNwUnZg7WZsCaBFk8aNibnCbwqtbhl1i9D8tdUGXiQ,343
|
|
46
49
|
pymscada/demo/pymscada-opnotes.service,sha256=TlrTRgP3rzrlXT8isAGT_Wy38ScDjT1VvnlgW84XiS8,354
|
|
47
50
|
pymscada/demo/pymscada-wwwserver.service,sha256=7Qy2wsMmFEsQn-b5mgAcsrAQZgXynkv8SpHD6hLvRGc,370
|
|
48
51
|
pymscada/demo/snmpclient.yaml,sha256=z8iACrFvMftYUtqGrRjPZYZTpn7aOXI-Kp675NAM8cU,2013
|
|
49
|
-
pymscada/demo/tags.yaml,sha256=
|
|
50
|
-
pymscada/demo/
|
|
52
|
+
pymscada/demo/tags.yaml,sha256=1HH9SqevBE0P0NXHK0Slfu68gwx5iKpgyirClmAcXGY,2814
|
|
53
|
+
pymscada/demo/witsapi.yaml,sha256=B8F136jvLIYU8t-pOdsEU_j97qMo3RgGQ1Rs4ExhmeE,289
|
|
51
54
|
pymscada/demo/wwwserver.yaml,sha256=mmwvSLUXUDCIPaHeCJdCETAp9Cc4wb5CuK_aGv01KWk,2759
|
|
52
55
|
pymscada/demo/__pycache__/__init__.cpython-311.pyc,sha256=tpxZoW429YA-2mbwzOlhBmbSTcbvTJqgKCfDRMrhEJE,195
|
|
53
56
|
pymscada/iodrivers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -70,9 +73,9 @@ pymscada/pdf/two.pdf,sha256=TAuW5yLU1_wfmTH_I5ezHwY0pxhCVuZh3ixu0kwmJwE,1516
|
|
|
70
73
|
pymscada/pdf/__pycache__/__init__.cpython-311.pyc,sha256=4KTfXrV9bGDbTIEv-zgIj_LvzLbVTj77lEC1wzMh9e0,194
|
|
71
74
|
pymscada/tools/snmp_client2.py,sha256=pdn5dYyEv4q-ubA0zQ8X-3tQDYxGC7f7Xexa7QPaL40,1675
|
|
72
75
|
pymscada/tools/walk.py,sha256=OgpprUbKLhEWMvJGfU1ckUt_PFEpwZVOD8HucCgzmOc,1625
|
|
73
|
-
pymscada-0.2.
|
|
74
|
-
pymscada-0.2.
|
|
75
|
-
pymscada-0.2.
|
|
76
|
-
pymscada-0.2.
|
|
77
|
-
pymscada-0.2.
|
|
78
|
-
pymscada-0.2.
|
|
76
|
+
pymscada-0.2.0rc8.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
77
|
+
pymscada-0.2.0rc8.dist-info/METADATA,sha256=vL-9rZ1w6VfZfJe6AHLdxIzQmu5ncN9q10k-GE1DfVE,2393
|
|
78
|
+
pymscada-0.2.0rc8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
79
|
+
pymscada-0.2.0rc8.dist-info/entry_points.txt,sha256=2UJBi8jrqujnerrcXcq4F8GHJYVDt26sacXl94t3sd8,56
|
|
80
|
+
pymscada-0.2.0rc8.dist-info/top_level.txt,sha256=LxIB-zrtgObJg0fgdGZXBkmNKLDYHfaH1Hw2YP2ZMms,9
|
|
81
|
+
pymscada-0.2.0rc8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|