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.

@@ -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 args():
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.add_argument('--config', metavar='file',
98
- help='Config file, default is "[module].yaml".')
99
- parser.add_argument('--tags', metavar='file',
100
- help='Tags file, default is "tags.yaml".')
101
- parser.add_argument('--verbose', action='store_true',
102
- help="Set level to logging.INFO.")
103
- sp = parser.add_subparsers(title='module')
104
- # Add individual module options, most use --config and --tags
105
- s = sp.add_parser('bus', help='run the message bus')
106
- s.set_defaults(get_module=bus)
107
- s = sp.add_parser('wwwserver', help='serve web pages')
108
- s.set_defaults(get_module=wwwserver)
109
- s = sp.add_parser('history', help='collect and serve history')
110
- s.set_defaults(get_module=history)
111
- s = sp.add_parser('files', help='receive and send files')
112
- s.set_defaults(get_module=files)
113
- s = sp.add_parser('console', help='interactive bus console')
114
- s.set_defaults(get_module=console)
115
- s = sp.add_parser('checkout', help='create example config files')
116
- s.set_defaults(get_module=_checkout)
117
- s.add_argument('--overwrite', action='store_true', default=False,
118
- help='checkout may overwrite files, CARE!')
119
- s = sp.add_parser('validate', help='validate config files')
120
- s.set_defaults(get_module=_validate)
121
- s.add_argument('--path', metavar='file',
122
- help='default is current working directory')
123
- s = sp.add_parser('modbusserver', help='receive modbus commands')
124
- s.set_defaults(get_module=modbusserver)
125
- s = sp.add_parser('modbusclient', help='poll/write to modbus devices')
126
- s.set_defaults(get_module=modbusclient)
127
- s = sp.add_parser('logixclient', help='poll/write to logix devices')
128
- s.set_defaults(get_module=logixclient)
129
- s = sp.add_parser('snmpclient', help='poll to snmp oids')
130
- s.set_defaults(get_module=snmpclient)
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
- options = args()
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.0a1.dist-info/METADATA,sha256=R9y7IP2V-N_bLjOnpq3ON5JF36C6ZFG_pi4v5FYNts4,9947
2
- pymscada-0.1.0a1.dist-info/WHEEL,sha256=N2J68yzZqJh3mI_Wg92rwhw0rtJDFpZj9bwQIMJgaVg,90
3
- pymscada-0.1.0a1.dist-info/entry_points.txt,sha256=AcZZ7HFj8k1ztP6ge-5bdRinYF8glW2s6lFEQG3esN4,57
4
- pymscada-0.1.0a1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
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=c8Fy--Oz4GMXaOO48VmySyI31Y2qjxL-_Tuh7V29JeY,2236
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=wBeLHiT2V0KLOgXXPdATy20tmlIudJQDiRMJGxBZrPc,941
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=X6Jz0YvbPrqJixesFURNhtMSWC9hGJ6c4QOtKM6fs8s,4058
32
- pymscada/demo/wwwserver.yaml,sha256=KDbe3mmslcK1L0ZnuvtOhhuJG-Hyw9OztMz813pChYw,1920
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=Qt0adM8RgnQEWSlISS9JS7zjjOVIggklB8jMuxP-A3U,9566
39
- pymscada/iodrivers/modbus_map.py,sha256=oFCCkHeGQudSPKhiUnSK3gg2WL_O25wuRCQXEZfpqcA,7172
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=Z0JPf5VWFkpt57_c-9reJrWkVh13BO3Ee-oxTJBG_iU,4823
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.0a1.dist-info/RECORD,,
60
+ pymscada-0.1.0a4.dist-info/RECORD,,