pymscada 0.1.0a1__py3-none-any.whl → 0.1.0a4__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/checkout.py +38 -5
- pymscada/demo/modbusserver.yaml +8 -0
- pymscada/demo/ping.yaml +12 -0
- pymscada/demo/pymscada-io-ping.service +15 -0
- pymscada/demo/tags.yaml +10 -1
- pymscada/demo/wwwserver.yaml +447 -1
- pymscada/iodrivers/modbus_client.py +4 -5
- pymscada/iodrivers/modbus_map.py +1 -1
- pymscada/iodrivers/ping_client.py +120 -0
- pymscada/iodrivers/ping_map.py +43 -0
- pymscada/main.py +70 -38
- pymscada-0.1.0a4.dist-info/METADATA +88 -0
- {pymscada-0.1.0a1.dist-info → pymscada-0.1.0a4.dist-info}/RECORD +17 -13
- pymscada-0.1.0a1.dist-info/METADATA +0 -270
- /pymscada/{iodrivers → tools}/snmp_client2.py +0 -0
- {pymscada-0.1.0a1.dist-info → pymscada-0.1.0a4.dist-info}/WHEEL +0 -0
- {pymscada-0.1.0a1.dist-info → pymscada-0.1.0a4.dist-info}/entry_points.txt +0 -0
- {pymscada-0.1.0a1.dist-info → pymscada-0.1.0a4.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Network monitoring / scanning."""
|
|
2
|
+
import asyncio
|
|
3
|
+
import logging
|
|
4
|
+
import socket
|
|
5
|
+
import struct
|
|
6
|
+
import time
|
|
7
|
+
from pymscada.bus_client import BusClient
|
|
8
|
+
from pymscada.periodic import Periodic
|
|
9
|
+
from pymscada.iodrivers.ping_map import PingMaps
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
ICMP_REQUEST = 8
|
|
13
|
+
ICMP_REPLY = 0
|
|
14
|
+
RATE = 60
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def checksum_chat(data):
|
|
18
|
+
"""Calculate ICMP Checksum."""
|
|
19
|
+
if len(data) & 1: # If the packet length is odd
|
|
20
|
+
data += b'\0'
|
|
21
|
+
res = 0
|
|
22
|
+
# Process two bytes at a time
|
|
23
|
+
for i in range(0, len(data), 2):
|
|
24
|
+
res += (data[i] << 8) + data[i + 1]
|
|
25
|
+
res = (res >> 16) + (res & 0xffff)
|
|
26
|
+
res += res >> 16
|
|
27
|
+
return (~res) & 0xffff # Return ones' complement of the result
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class PingClientConnector:
|
|
31
|
+
"""Ping a list of addresses."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, mapping: PingMaps):
|
|
34
|
+
"""Accept list of addresses, ip or name."""
|
|
35
|
+
self.mapping = mapping
|
|
36
|
+
self.dns = {}
|
|
37
|
+
self.addr_info = {}
|
|
38
|
+
self.socket = None
|
|
39
|
+
self.ping_id = 0
|
|
40
|
+
self.ping_dict = {}
|
|
41
|
+
|
|
42
|
+
async def poll(self):
|
|
43
|
+
"""Do pings."""
|
|
44
|
+
self.reply_dict = {}
|
|
45
|
+
if self.socket is None:
|
|
46
|
+
self.socket = socket.socket(socket.AF_INET, socket.SOCK_RAW,
|
|
47
|
+
socket.IPPROTO_ICMP)
|
|
48
|
+
asyncio.get_event_loop().add_reader(self.socket,
|
|
49
|
+
self.read_response)
|
|
50
|
+
for ping_id, address in list(self.ping_dict.items()):
|
|
51
|
+
logging.info(f'failed {self.dns[address]} {ping_id}')
|
|
52
|
+
self.mapping.polled_data(self.dns[address], float('NAN'))
|
|
53
|
+
del self.ping_dict[ping_id]
|
|
54
|
+
self.send_ping()
|
|
55
|
+
|
|
56
|
+
def send_ping(self):
|
|
57
|
+
"""Build and send ping messages."""
|
|
58
|
+
for address in self.dns.keys():
|
|
59
|
+
self.ping_id = (self.ping_id + 1) & 0xffff
|
|
60
|
+
self.ping_dict[self.ping_id] = address
|
|
61
|
+
logging.info(f'ping {address} id {self.ping_id}')
|
|
62
|
+
header = struct.pack("!BbHHh", ICMP_REQUEST, 0, 0, self.ping_id, 1)
|
|
63
|
+
data = struct.pack("!d", time.perf_counter()) + (60 * b'\0')
|
|
64
|
+
checksum = checksum_chat(header + data)
|
|
65
|
+
packet = struct.pack("!BbHHh", ICMP_REQUEST, 0, checksum,
|
|
66
|
+
self.ping_id, 1) + data
|
|
67
|
+
self.socket.sendto(packet, (address, 0))
|
|
68
|
+
|
|
69
|
+
def read_response(self):
|
|
70
|
+
"""Match ping response."""
|
|
71
|
+
data, address = self.socket.recvfrom(1024)
|
|
72
|
+
msgtype, _, _, ping_id, _ = struct.unpack('!BBHHH', data[20:28])
|
|
73
|
+
if msgtype != ICMP_REPLY:
|
|
74
|
+
return
|
|
75
|
+
if ping_id in self.ping_dict:
|
|
76
|
+
latency = 1000 * (time.perf_counter() -
|
|
77
|
+
struct.unpack('!d', data[28:36])[0])
|
|
78
|
+
name = self.dns[address[0]]
|
|
79
|
+
logging.info(f'success {name} {ping_id} {latency}ms')
|
|
80
|
+
self.mapping.polled_data(name, latency)
|
|
81
|
+
del self.ping_dict[ping_id]
|
|
82
|
+
|
|
83
|
+
async def start(self):
|
|
84
|
+
"""Start pinging."""
|
|
85
|
+
loop = asyncio.get_event_loop()
|
|
86
|
+
for address in self.mapping.var_map.keys():
|
|
87
|
+
info = await loop.getaddrinfo(address, None, family=socket.AF_INET,
|
|
88
|
+
type=socket.SOCK_STREAM)
|
|
89
|
+
ip = info[0][4][0]
|
|
90
|
+
self.dns[ip] = address
|
|
91
|
+
self.periodic = Periodic(self.poll, RATE)
|
|
92
|
+
await self.periodic.start()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class PingClient:
|
|
96
|
+
"""Ping client."""
|
|
97
|
+
|
|
98
|
+
def __init__(self, bus_ip: str = '127.0.0.1', bus_port: int = 1324,
|
|
99
|
+
tags: dict = {}) -> None:
|
|
100
|
+
"""
|
|
101
|
+
Connect to bus on bus_ip:bus_port, ping a list.
|
|
102
|
+
|
|
103
|
+
Event loop must be running.
|
|
104
|
+
"""
|
|
105
|
+
self.busclient = None
|
|
106
|
+
if bus_ip is not None:
|
|
107
|
+
self.busclient = BusClient(bus_ip, bus_port)
|
|
108
|
+
self.mapping = PingMaps(tags)
|
|
109
|
+
self.pinger = PingClientConnector(mapping=self.mapping)
|
|
110
|
+
|
|
111
|
+
async def _poll(self):
|
|
112
|
+
"""For testing."""
|
|
113
|
+
for connection in self.connections:
|
|
114
|
+
await connection.poll()
|
|
115
|
+
|
|
116
|
+
async def start(self):
|
|
117
|
+
"""Start bus connection and PLC polling."""
|
|
118
|
+
if self.busclient is not None:
|
|
119
|
+
await self.busclient.start()
|
|
120
|
+
await self.pinger.start()
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Map between snmp MIB and Tag."""
|
|
2
|
+
from time import time
|
|
3
|
+
from pymscada.tag import Tag
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PingMap:
|
|
7
|
+
"""Do value updates for each tag."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, tagname: str, addr: str):
|
|
10
|
+
"""Initialise MIB map and Tag."""
|
|
11
|
+
self.last_value = None
|
|
12
|
+
self.tag = Tag(tagname, float)
|
|
13
|
+
self.addr = addr
|
|
14
|
+
self.map_bus = id(self)
|
|
15
|
+
|
|
16
|
+
def set_tag_value(self, value, time_us):
|
|
17
|
+
"""Pass update from IO driver to tag value."""
|
|
18
|
+
if self.last_value is None:
|
|
19
|
+
self.last_value = value
|
|
20
|
+
return
|
|
21
|
+
if self.last_value != value:
|
|
22
|
+
self.tag.value = value, time_us, self.map_bus
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class PingMaps:
|
|
26
|
+
"""Link tags with protocol connector."""
|
|
27
|
+
|
|
28
|
+
def __init__(self, tags: dict):
|
|
29
|
+
"""Collect maps based on a tag dictionary."""
|
|
30
|
+
# use the tagname to access the map.
|
|
31
|
+
self.tag_map: dict[str, PingMap] = {}
|
|
32
|
+
# use the plc_name then variable name to access a list of maps.
|
|
33
|
+
self.var_map: dict[str, PingMap] = {}
|
|
34
|
+
for tagname, v in tags.items():
|
|
35
|
+
addr = v['addr']
|
|
36
|
+
map = PingMap(tagname, addr)
|
|
37
|
+
self.var_map[addr] = map
|
|
38
|
+
self.tag_map[tagname] = map
|
|
39
|
+
|
|
40
|
+
def polled_data(self, address, latency):
|
|
41
|
+
"""Pass updates read from the PLC to the tags."""
|
|
42
|
+
time_us = int(time() * 1e6)
|
|
43
|
+
self.var_map[address].set_tag_value(latency, time_us)
|
pymscada/main.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"""Main server entry point."""
|
|
2
2
|
import argparse
|
|
3
3
|
import asyncio
|
|
4
|
+
from importlib.metadata import version
|
|
4
5
|
import logging
|
|
6
|
+
import sys
|
|
5
7
|
from pymscada.bus_server import BusServer
|
|
6
8
|
from pymscada.checkout import checkout
|
|
7
9
|
from pymscada.config import Config
|
|
@@ -11,11 +13,15 @@ from pymscada.history import History
|
|
|
11
13
|
from pymscada.iodrivers.logix_client import LogixClient
|
|
12
14
|
from pymscada.iodrivers.modbus_client import ModbusClient
|
|
13
15
|
from pymscada.iodrivers.modbus_server import ModbusServer
|
|
16
|
+
from pymscada.iodrivers.ping_client import PingClient
|
|
14
17
|
from pymscada.iodrivers.snmp_client import SnmpClient
|
|
15
18
|
from pymscada.www_server import WwwServer
|
|
16
19
|
from pymscada.validate import validate
|
|
17
20
|
|
|
18
21
|
|
|
22
|
+
MODULES = {}
|
|
23
|
+
|
|
24
|
+
|
|
19
25
|
async def bus(options):
|
|
20
26
|
"""Return bus module."""
|
|
21
27
|
config = Config(options.config)
|
|
@@ -49,7 +55,7 @@ async def console(_options):
|
|
|
49
55
|
|
|
50
56
|
async def _checkout(options):
|
|
51
57
|
"""Checkout files in current working directory."""
|
|
52
|
-
checkout(overwrite=options.overwrite)
|
|
58
|
+
checkout(overwrite=options.overwrite, diff=options.diff)
|
|
53
59
|
return
|
|
54
60
|
|
|
55
61
|
|
|
@@ -81,59 +87,85 @@ async def logixclient(options):
|
|
|
81
87
|
return LogixClient(**config)
|
|
82
88
|
|
|
83
89
|
|
|
90
|
+
async def ping(options):
|
|
91
|
+
"""Return logixclient module."""
|
|
92
|
+
if sys.platform.startswith("win"):
|
|
93
|
+
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
|
94
|
+
config = Config(options.config)
|
|
95
|
+
return PingClient(**config)
|
|
96
|
+
|
|
97
|
+
|
|
84
98
|
async def snmpclient(options):
|
|
85
99
|
"""Return snmpclient module."""
|
|
86
100
|
config = Config(options.config)
|
|
87
101
|
return SnmpClient(**config)
|
|
88
102
|
|
|
89
103
|
|
|
90
|
-
def
|
|
104
|
+
def add_subparser_defaults(
|
|
105
|
+
parser: argparse._SubParsersAction,
|
|
106
|
+
name: str, call, help: str, epilog: str):
|
|
107
|
+
"""Add arguments common to all subparsers."""
|
|
108
|
+
s = parser.add_parser(name, help=help, epilog=epilog)
|
|
109
|
+
s.set_defaults(get_module=call, module=name)
|
|
110
|
+
s.add_argument('--config', metavar='file', default=None,
|
|
111
|
+
help=f"Config file, default is '{name}.yaml'")
|
|
112
|
+
s.add_argument('--tags', metavar='file', default=None,
|
|
113
|
+
help="Tags file, default is 'tags.yaml'")
|
|
114
|
+
s.add_argument('--verbose', action='store_true',
|
|
115
|
+
help="Set level to logging.INFO")
|
|
116
|
+
return s
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def args(_version: str):
|
|
91
120
|
"""Read commandline arguments."""
|
|
92
121
|
parser = argparse.ArgumentParser(
|
|
93
122
|
prog='pymscada',
|
|
94
123
|
description='Connect IO, logic, applications, and webpage UI',
|
|
95
|
-
epilog='Python Mobile SCADA'
|
|
124
|
+
epilog=f'Python Mobile SCADA {_version}'
|
|
96
125
|
)
|
|
97
|
-
parser.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
126
|
+
subparsers = parser.add_subparsers(title='module')
|
|
127
|
+
for module, func, help, epilog in [
|
|
128
|
+
['bus', bus, 'run the message bus', None],
|
|
129
|
+
['wwwserver', wwwserver, 'serve web pages', None],
|
|
130
|
+
['history', history, 'collect and serve history', None],
|
|
131
|
+
['files', files, 'receive and send files', None],
|
|
132
|
+
['console', console, 'interactive bus console', None],
|
|
133
|
+
['checkout', _checkout, 'create example config files', """
|
|
134
|
+
To add to systemd `f="pymscada-bus" && cp config/$f.service
|
|
135
|
+
/lib/systemd/system && systemctl enable $f && systemctl start
|
|
136
|
+
$f`"""],
|
|
137
|
+
['validate', _validate, 'validate config files', None],
|
|
138
|
+
['modbusserver', modbusserver, 'receive modbus messages', """
|
|
139
|
+
Needs `setcap CAP_NET_BIND_SERVICE=+eip /usr/bin/python3.nn` to
|
|
140
|
+
bind to port 502"""],
|
|
141
|
+
['modbusclient', modbusclient, 'poll/write to modbus devices', None],
|
|
142
|
+
['ping', ping, 'ping a list of addresses, return time', """
|
|
143
|
+
Needs `setcap CAP_NET_RAW+ep /usr/bin/python3.nn` to open SOCK_RAW
|
|
144
|
+
"""],
|
|
145
|
+
['logixclient', logixclient, 'poll/write to logix devices', None],
|
|
146
|
+
['snmpclient', snmpclient, 'poll snmp oids', None],
|
|
147
|
+
]:
|
|
148
|
+
modparser = add_subparser_defaults(subparsers, module, func, help,
|
|
149
|
+
epilog)
|
|
150
|
+
if module == 'checkout':
|
|
151
|
+
modparser.add_argument(
|
|
152
|
+
'--overwrite', action='store_true', default=False,
|
|
153
|
+
help='checkout may overwrite files, CARE!')
|
|
154
|
+
modparser.add_argument(
|
|
155
|
+
'--diff', action='store_true', default=False,
|
|
156
|
+
help='compare default with existing')
|
|
157
|
+
elif module == 'validate':
|
|
158
|
+
modparser.add_argument(
|
|
159
|
+
'--path', metavar='file',
|
|
160
|
+
help='default is current working directory')
|
|
131
161
|
return parser.parse_args()
|
|
132
162
|
|
|
133
163
|
|
|
134
164
|
async def run():
|
|
135
165
|
"""Run bus and wwwserver."""
|
|
136
|
-
|
|
166
|
+
_version = version("pymscada")
|
|
167
|
+
logging.warning(f'pymscada {_version} starting')
|
|
168
|
+
options = args(_version)
|
|
137
169
|
if options.verbose:
|
|
138
170
|
logging.basicConfig(level=logging.INFO)
|
|
139
171
|
if options.config is None:
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: pymscada
|
|
3
|
+
Version: 0.1.0a4
|
|
4
|
+
Summary: Shared tag value SCADA with python backup and Angular UI
|
|
5
|
+
Author-Email: Jamie Walton <jamie@walton.net.nz>
|
|
6
|
+
License: GPL-3.0-or-later
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: JavaScript
|
|
9
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Development Status :: 1 - Planning
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Requires-Dist: PyYAML>=6.0.1
|
|
15
|
+
Requires-Dist: aiohttp>=3.8.5
|
|
16
|
+
Requires-Dist: pymscada-html<=0.1.0
|
|
17
|
+
Requires-Dist: cerberus>=1.3.5
|
|
18
|
+
Requires-Dist: pycomm3>=1.2.14
|
|
19
|
+
Requires-Dist: pysnmplib>=5.0.24
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# pymscada
|
|
23
|
+
#### [Docs](https://github.com/jamie0walton/pymscada/blob/main/docs/README.md)
|
|
24
|
+
|
|
25
|
+
#### [@Github](https://github.com/jamie0walton/pymscada/blob/main/README.md)
|
|
26
|
+
|
|
27
|
+
## Python Mobile SCADA
|
|
28
|
+
|
|
29
|
+
```pymscada``` read / write to Modbus and Logix PLCs. Read SNMP OIDs.
|
|
30
|
+
Collect history values and provide the ability to set values and trends
|
|
31
|
+
and issue commands.
|
|
32
|
+
|
|
33
|
+
User interface is via a web client embedded in this package. Examples included
|
|
34
|
+
for securing with Apache as a proxy.
|
|
35
|
+
|
|
36
|
+
Configuration with text yaml files, including the web page which are
|
|
37
|
+
procedurally built.
|
|
38
|
+
|
|
39
|
+
# See also
|
|
40
|
+
|
|
41
|
+
- The angular project [angmscada](https://github.com/jamie0walton/angmscada)
|
|
42
|
+
- Python container for the compiled angular pages [pymscada-html](https://github.com/jamie0walton/pymscada-html)
|
|
43
|
+
|
|
44
|
+
# Licence
|
|
45
|
+
|
|
46
|
+
```pymscada``` is distributed under the GPLv3 [license](./LICENSE).
|
|
47
|
+
|
|
48
|
+
# Use
|
|
49
|
+
Checkout the example files.
|
|
50
|
+
```bash
|
|
51
|
+
mscada@raspberrypi:~/test $ pymscada checkout
|
|
52
|
+
making 'history' folder
|
|
53
|
+
making pdf dir
|
|
54
|
+
making config dir
|
|
55
|
+
Creating /home/mscada/test/config/modbusclient.yaml
|
|
56
|
+
Creating /home/mscada/test/config/pymscada-history.service
|
|
57
|
+
Creating /home/mscada/test/config/wwwserver.yaml
|
|
58
|
+
Creating /home/mscada/test/config/pymscada-demo-modbus_plc.service
|
|
59
|
+
Creating /home/mscada/test/config/files.yaml
|
|
60
|
+
Creating /home/mscada/test/config/pymscada-modbusserver.service
|
|
61
|
+
Creating /home/mscada/test/config/pymscada-wwwserver.service
|
|
62
|
+
Creating /home/mscada/test/config/simulate.yaml
|
|
63
|
+
Creating /home/mscada/test/config/tags.yaml
|
|
64
|
+
Creating /home/mscada/test/config/history.yaml
|
|
65
|
+
Creating /home/mscada/test/config/pymscada-files.service
|
|
66
|
+
Creating /home/mscada/test/config/bus.yaml
|
|
67
|
+
Creating /home/mscada/test/config/modbusserver.yaml
|
|
68
|
+
Creating /home/mscada/test/config/modbus_plc.py
|
|
69
|
+
Creating /home/mscada/test/config/pymscada-modbusclient.service
|
|
70
|
+
Creating /home/mscada/test/config/pymscada-bus.service
|
|
71
|
+
Creating /home/mscada/test/config/README.md
|
|
72
|
+
mscada@raspberrypi:~/test $ pymscada validate
|
|
73
|
+
WARNING:root:pymscada 0.1.0 starting
|
|
74
|
+
Config files in ./ valid.
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Runs on a Raspberry Pi and includes preconfigured systemd files to
|
|
78
|
+
automate running the services. Mostly works on Windows, works better
|
|
79
|
+
on linux.
|
|
80
|
+
|
|
81
|
+
Modules can be run from the command line, although you need
|
|
82
|
+
a terminal for each running module (better with systemd).
|
|
83
|
+
```bash
|
|
84
|
+
pymscada bus --config bus.yaml
|
|
85
|
+
pymscada wwwserver --config wwwserver.yaml --tags tags.yaml
|
|
86
|
+
pymscada history --config history.yaml --tags tags.yaml
|
|
87
|
+
python weather.py
|
|
88
|
+
```
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
pymscada-0.1.
|
|
2
|
-
pymscada-0.1.
|
|
3
|
-
pymscada-0.1.
|
|
4
|
-
pymscada-0.1.
|
|
1
|
+
pymscada-0.1.0a4.dist-info/METADATA,sha256=wO0mKOshzVvaWM04iMsTSmdS11hvPc1uf7ieZOMg8xQ,3214
|
|
2
|
+
pymscada-0.1.0a4.dist-info/WHEEL,sha256=N2J68yzZqJh3mI_Wg92rwhw0rtJDFpZj9bwQIMJgaVg,90
|
|
3
|
+
pymscada-0.1.0a4.dist-info/entry_points.txt,sha256=AcZZ7HFj8k1ztP6ge-5bdRinYF8glW2s6lFEQG3esN4,57
|
|
4
|
+
pymscada-0.1.0a4.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
5
5
|
pymscada/__init__.py,sha256=D_4aEDWkW6xMVQane3CbehBKPxT3FCaDY_QpFwglCe8,653
|
|
6
6
|
pymscada/__main__.py,sha256=WcyVlrYOoDdktJhOoyubTOycMwpayksFdxwelRU5xpQ,272
|
|
7
7
|
pymscada/bus_client.py,sha256=A6EI3OafyOBKd8UVFXS4K39SpmwBoFE-kSIwldVeDKE,8695
|
|
8
8
|
pymscada/bus_server.py,sha256=-Bn4rfdE4OWXE0av459sROnCwAqV0VKFWulF0m1abHE,11255
|
|
9
|
-
pymscada/checkout.py,sha256=
|
|
9
|
+
pymscada/checkout.py,sha256=NcnrfETIknaUIDlpcUrjBN3e2iri-wCVjE_QAGECjxA,3361
|
|
10
10
|
pymscada/config.py,sha256=vwGxieaJBYXiHNQEOYVDFaPuGmnUlCnbNm_W9bugKlc,1851
|
|
11
11
|
pymscada/console.py,sha256=sw1k6eXY53S8qMR-kx8pHOyhXHHt88e4RTcXmC7AQlw,946
|
|
12
12
|
pymscada/demo/README.md,sha256=iNcVbCTkq-d4agLV-979lNRaqf_hbJCn3OFzY-6qfU8,880
|
|
@@ -17,7 +17,8 @@ pymscada/demo/history.yaml,sha256=mn_Xf4h_bK6vwZVQ0Iz9BzJpwWok2gEgSKbDgEM8AOQ,46
|
|
|
17
17
|
pymscada/demo/logixclient.yaml,sha256=G_NlJhBYwT1a9ceHDgO6fCNKFmBM2pVO_t9Xa1NqlRY,912
|
|
18
18
|
pymscada/demo/modbus_plc.py,sha256=3zZHHbyrdxyryEHBeNIw-fpcDGcS1MaJiqEwQDr6zWI,2397
|
|
19
19
|
pymscada/demo/modbusclient.yaml,sha256=geeCsUJZkkEj7jjXR_Yk6R5zA5Ta9IczrHsARz7ZgXY,1099
|
|
20
|
-
pymscada/demo/modbusserver.yaml,sha256=
|
|
20
|
+
pymscada/demo/modbusserver.yaml,sha256=67_mED6jXgtnzlDIky9Cg4j-nXur06iz9ve3JUwSyG8,1133
|
|
21
|
+
pymscada/demo/ping.yaml,sha256=r_VBGTLU5r4cZi9bIGL3M4eNw70KnoBptOUoNrSbnFY,210
|
|
21
22
|
pymscada/demo/pymscada-bus.service,sha256=rRTFwHaS8XWd9YAIB3cET4QvASaIO9emmxFiUAbl14g,257
|
|
22
23
|
pymscada/demo/pymscada-demo-modbus_plc.service,sha256=jmgk_peoxwKVXe-LbyK2VluMS1JMmoTud4JZHi9Tgec,316
|
|
23
24
|
pymscada/demo/pymscada-files.service,sha256=yGTxYnmQ_QBjIzItFatw_uIg_cG11vadLDaR0C9pPEk,322
|
|
@@ -25,23 +26,25 @@ pymscada/demo/pymscada-history.service,sha256=kEU_RsrRSmEUE-nR23n2q2yZxjALm_nCrJ
|
|
|
25
26
|
pymscada/demo/pymscada-io-logixclient.service,sha256=gvnCJgUeqnIgnuN-Wf1XhB0FJxVYyLksmMO7JC0wT-Y,344
|
|
26
27
|
pymscada/demo/pymscada-io-modbusclient.service,sha256=4tenKcrfRi0iMdv8-k2gtMQA4OPTM59zAyKpovwemxM,344
|
|
27
28
|
pymscada/demo/pymscada-io-modbusserver.service,sha256=FqCMD3EJKoiq6EbYnoijRLX5UeUWbZrNzDs50eQj7iE,344
|
|
29
|
+
pymscada/demo/pymscada-io-ping.service,sha256=d1n32srVKGd8qo8JWeBYEEznCRZWRWaBQLOYzdqEXWg,327
|
|
28
30
|
pymscada/demo/pymscada-io-snmpclient.service,sha256=wrA2kDR3bgO30lP_lNJrIsVXNQiXmWKnphoqUj3QTRI,339
|
|
29
31
|
pymscada/demo/pymscada-wwwserver.service,sha256=uDnqzfvAdAnTrqOCqDm1PN7SmeMSuOdmAhorHPJdEVI,366
|
|
30
32
|
pymscada/demo/snmpclient.yaml,sha256=z8iACrFvMftYUtqGrRjPZYZTpn7aOXI-Kp675NAM8cU,2013
|
|
31
|
-
pymscada/demo/tags.yaml,sha256=
|
|
32
|
-
pymscada/demo/wwwserver.yaml,sha256=
|
|
33
|
+
pymscada/demo/tags.yaml,sha256=GH90X3QRBANUhvd2E9OuyIoiZD25OBihHHlDBw1uzlw,4231
|
|
34
|
+
pymscada/demo/wwwserver.yaml,sha256=jyboShIbwkwgMAcFLkpn88VMvGfoc7a44jNrI_B6OqY,12493
|
|
33
35
|
pymscada/files.py,sha256=kEkiD7j5k69EK4jfMHEp-lbTnulYyrtUjkQDa5xmGPc,1784
|
|
34
36
|
pymscada/history.py,sha256=yAUfjzo4O3w9-hGEAbtvz9UseMTwRVz7NqS832RtvIs,9455
|
|
35
37
|
pymscada/iodrivers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
36
38
|
pymscada/iodrivers/logix_client.py,sha256=wDhl0pCqs_k7Hd1MtbOuBrEx9emOwDrLJqBNussH1qU,2806
|
|
37
39
|
pymscada/iodrivers/logix_map.py,sha256=ljjBAMJcw199v1V5u0Yfl38U6zbZzba5mdY4I3ZvdIM,5401
|
|
38
|
-
pymscada/iodrivers/modbus_client.py,sha256=
|
|
39
|
-
pymscada/iodrivers/modbus_map.py,sha256=
|
|
40
|
+
pymscada/iodrivers/modbus_client.py,sha256=mo7VRLWi4W2J5Ft3M6Y5jSbK5vbd6wn4R2dncbiL4nU,9529
|
|
41
|
+
pymscada/iodrivers/modbus_map.py,sha256=ihkb6xEXNQa5JDFklajjq4W8I4UHPsdOGe2MyHHwXYw,7172
|
|
40
42
|
pymscada/iodrivers/modbus_server.py,sha256=nnUjd6iCny_Abylk06J8dq4slNAyNPQC2Rw2ZWEElKM,6507
|
|
43
|
+
pymscada/iodrivers/ping_client.py,sha256=dIR4SuKeNCu2FyhUNfTbkv-El3qPFMb0zUG1GJSD-80,4166
|
|
44
|
+
pymscada/iodrivers/ping_map.py,sha256=EbOteqfEYKIOMqPymROJ4now2If-ekEj6jnM5hthoSA,1403
|
|
41
45
|
pymscada/iodrivers/snmp_client.py,sha256=fBBE7SJjfcf9Y9zKRqgGMktnk6RoVMVc5Mj05ffk6ds,2601
|
|
42
|
-
pymscada/iodrivers/snmp_client2.py,sha256=pdn5dYyEv4q-ubA0zQ8X-3tQDYxGC7f7Xexa7QPaL40,1675
|
|
43
46
|
pymscada/iodrivers/snmp_map.py,sha256=rCTniik40Yy6avooIW8PqWanrnUglwBLKReIM7Ab5lA,2369
|
|
44
|
-
pymscada/main.py,sha256=
|
|
47
|
+
pymscada/main.py,sha256=KbKX_NzEAX7uwmx9Ms4vbgq4kvg-Aiw5eMjxS8D3A5M,5998
|
|
45
48
|
pymscada/misc.py,sha256=0Cj6OFhQonyhyk9x0BG5MiS-6EPk_w6zvavt8o_Hlf0,622
|
|
46
49
|
pymscada/pdf/__init__.py,sha256=WsDDgkWnZBJbt2-cJCdc2NvRAv_T4a7WOC1Q0k_l0gI,29
|
|
47
50
|
pymscada/pdf/one.pdf,sha256=eoJ45DrAjVZrwmwdA_EAz1fwmT44eRnt_tkc2pmMrKY,1488
|
|
@@ -50,7 +53,8 @@ pymscada/periodic.py,sha256=MLlL93VLvFqBBgjO1Us1t0aLHTZ5BFdW0B__G02T1nQ,1235
|
|
|
50
53
|
pymscada/protocol_constants.py,sha256=ndFeuzhWjKTr_ahvmGJc6Cs5pYkHM8CRgNNDe0Tqecs,1997
|
|
51
54
|
pymscada/samplers.py,sha256=t0IscgsCm5YByioOZ6aOKMO_guDFS_wxnJSiOGKI4Nw,2583
|
|
52
55
|
pymscada/tag.py,sha256=Q2EgacTnsxnGLM_zoHYsVWdwmd1faqfbxw9byI5fCgY,9463
|
|
56
|
+
pymscada/tools/snmp_client2.py,sha256=pdn5dYyEv4q-ubA0zQ8X-3tQDYxGC7f7Xexa7QPaL40,1675
|
|
53
57
|
pymscada/tools/walk.py,sha256=OgpprUbKLhEWMvJGfU1ckUt_PFEpwZVOD8HucCgzmOc,1625
|
|
54
58
|
pymscada/validate.py,sha256=VPpAVEwfgori5OREEwWlbPoPxz5Tfqr6dw-O5pINHyI,13125
|
|
55
59
|
pymscada/www_server.py,sha256=BW6-ctE5BgyFDiyUJsOpe3c06DRp8k83MCGjwPh5fco,11473
|
|
56
|
-
pymscada-0.1.
|
|
60
|
+
pymscada-0.1.0a4.dist-info/RECORD,,
|