pymscada 0.0.15__py3-none-any.whl → 0.1.0__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.

@@ -1,270 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: pymscada
3
- Version: 0.0.15
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
- This is an open source Python SCADA package intended for use over your
30
- mobile phone. It has been developed to connect custom python scripts to
31
- Modicon and Rockwell PLCs, and present a web based user interface, with
32
- minimal coding. ```pymscada``` does expect you to be able to use python.
33
-
34
- ```pymscada``` shares values through a ```Tag``` for all data. Tag values
35
- are updated by exception through a message bus. Changes are in any
36
- direction with simple loops stopped with a simplistic _don't send updates
37
- back to where they came from_ check. It's too simple to stop you defeating
38
- it (but there are easier ways to break the code).
39
-
40
- There are primary owner Tag's. A _request to set_ command is passed to the
41
- author of the Tag, where it is updated. This allows database information
42
- to pass through the Tag values as well. Tag values are typically float or
43
- int however they may also be megabyte sized binary or dictionary values.
44
-
45
- Example ```systemd``` service files, pymscada module config files and
46
- custom script examples are included. The example scripts include polling
47
- weather from tommorrow.io and a python script based Modbus TCP PLC.
48
-
49
- ## Introduction
50
-
51
- This is a small SCADA package that will run on Linux (preferably) or
52
- Windows. The server runs as several modules on the host, sharing
53
- information through a message bus. A __subset__ of modules is:
54
-
55
- - Bus server - shares tag values with by exception updates
56
- - Modbus client - reads and writes to a PLC using Modbus/TCP
57
- - Logix client - uses ```pycomm3``` to read / write to Rockwell PLCs
58
- - SNMP client - polls SNMP OID values
59
- - History - saves data changes, serves history to web pages
60
- - Web server - serves web pages which connect with a web socket
61
- - Web pages - procedurally generated setpoint, indication and trends
62
-
63
- ## Objectives
64
-
65
- Traditional SCADA has a fixed 19:6, 1920x1080 or some equivalent layout.
66
- It's great on a big screen but not good on a phone. Hence __Mobile__
67
- SCADA with a responsive layout.
68
-
69
- I wrote Mobile SCADA to provide a GUI to the other things I was trying to
70
- do, I wanted to leverage web browsers and eliminate a dedicated
71
- _viewer.exe_. Display on the client is fast, trends, as fast as I can
72
- make them.
73
-
74
- Uptimes should be excellent. The best I have on an earlier version is
75
- over 5 years for about half of the script modules. This version is a
76
- complete rewrite, however the aim is the same.
77
-
78
- All tag value updates are by exception. So an update from you setting a
79
- value to seeing the feedback should be __FAST__.
80
-
81
- # See also
82
-
83
- - The angular project [angmscada](https://github.com/jamie0walton/angmscada)
84
- - Python container for the compiled angular pages [pymscada-html](https://github.com/jamie0walton/pymscada-html)
85
- - An add-on IO driver for Rockwell using pycomm3 [pymscada-pycomm3](https://github.com/jamie0walton/pymscada-pycomm3)
86
-
87
- # Licence
88
-
89
- ```pymscada``` is distributed under the GPLv3 [license](./LICENSE).
90
-
91
- # Example Use
92
- This was all run on a Raspberry Pi 3B+ with a 16GB SDRAM card.
93
-
94
- ## First
95
- Checkout the example files. Start in an empty directory. Plan to keep
96
- in the directory you check out into as the config file path details
97
- are auto-generated for the location you check out in to.
98
- ```bash
99
- mscada@raspberrypi:~/test $ pymscada checkout
100
- making 'history' folder
101
- making pdf dir
102
- making config dir
103
- Creating /home/mscada/test/config/modbusclient.yaml
104
- Creating /home/mscada/test/config/pymscada-history.service
105
- Creating /home/mscada/test/config/wwwserver.yaml
106
- Creating /home/mscada/test/config/pymscada-demo-modbus_plc.service
107
- Creating /home/mscada/test/config/files.yaml
108
- Creating /home/mscada/test/config/pymscada-modbusserver.service
109
- Creating /home/mscada/test/config/pymscada-wwwserver.service
110
- Creating /home/mscada/test/config/simulate.yaml
111
- Creating /home/mscada/test/config/tags.yaml
112
- Creating /home/mscada/test/config/history.yaml
113
- Creating /home/mscada/test/config/pymscada-files.service
114
- Creating /home/mscada/test/config/bus.yaml
115
- Creating /home/mscada/test/config/modbusserver.yaml
116
- Creating /home/mscada/test/config/modbus_plc.py
117
- Creating /home/mscada/test/config/pymscada-modbusclient.service
118
- Creating /home/mscada/test/config/pymscada-bus.service
119
- Creating /home/mscada/test/config/README.md
120
- mscada@raspberrypi:~/test $
121
- ```
122
-
123
- ## Objective
124
- To show a trend of the temperature forecast with a custom pymscada bus
125
- client program. The end result should look like ...
126
-
127
- ![Temperature](temperature%20trend.png)
128
-
129
- ## Configuration
130
- ### Bus
131
- Defaults in ```bus.yaml``` are fine.
132
-
133
- ### Tags
134
- Add some tags in ```tags.yaml```:
135
- ```yaml
136
- temperature:
137
- desc: temperature
138
- type: float
139
- min: 0
140
- max: 35
141
- units: C
142
- dp: 1
143
- temperature_01:
144
- desc: temperature_01
145
- type: float
146
- min: 0
147
- max: 35
148
- units: C
149
- dp: 1
150
- ... etc.
151
- ```
152
-
153
- ### History
154
- Defaults in ```history.yaml``` are fine.
155
-
156
- ### Web Server
157
- You will need to add a trend page to ```wwwserver.yaml``` as:
158
- ```yaml
159
- - name: Temperature # Creates a Temperature page in the web client
160
- parent: Weather # Add the Temperature page in a submenu under Weather
161
- items:
162
- - type: uplot # Identify the Angular component to use
163
- ms:
164
- desc: Temperature
165
- age: 172800
166
- legend_pos: left
167
- time_pos: left
168
- time_res: m
169
- axes:
170
- - scale: x
171
- range: [-604800, 86400] # initial time range for the trend
172
- - scale: 'C'
173
- range: [0.0, 35.0]
174
- dp: 1
175
- series:
176
- - tagname: temperature # pymscada Tag name
177
- label: Current Temperature
178
- scale: 'C'
179
- color: black # standard html colour names
180
- width: 2
181
- dp: 1 # number of decimal places
182
- ... etc for additional series
183
- ```
184
-
185
- ### Your custom pymscada Module
186
- For this example I polled [tomorrow.io](https://www.tomorrow.io/weather-api/)
187
-
188
- ```weather.py```
189
- ```python
190
- from datetime import datetime
191
- import time
192
- from pymscada import BusClient, Periodic, Tag
193
-
194
- URL = 'https://api.tomorrow.io/v4/timelines'
195
- QUERY = {'location': '-43.527934570040124, 172.6415203551829',
196
- 'fields': ['temperature'],
197
- 'units': 'metric',
198
- 'timesteps': '1h',
199
- 'startTime': 'now',
200
- 'endTime': 'nowPlus24h',
201
- 'apikey': '<your key>'}
202
-
203
- class PollWeather():
204
- def __init__(self):
205
- self.tags = {}
206
- for tagname in ['temperature', 'temperature_01', 'temperature_04',
207
- 'temperature_12', 'temperature_24']:
208
- # Create pymscada tags, tags are singletons by 'tagname'
209
- self.tags[tagname] = Tag(tagname, float)
210
-
211
- async def periodic(self):
212
- now = int(time.time())
213
- if now % 3600 != 120:
214
- return
215
- # Get the weather forecast from tomorrow.io
216
- async with aiohttp.ClientSession() as session:
217
- async with session.get(URL, params=QUERY) as resp:
218
- response = await resp.json()
219
- utc_now = None
220
- for row in response['data']['timelines'][0]['intervals']:
221
- convert = row['startTime'].replace('Z', '+0000')
222
- utc = datetime.strptime(convert, '%Y-%m-%dT%H:%M:%S%z').timestamp()
223
- if utc_now is None:
224
- utc_now = utc
225
- forecast = ''
226
- else:
227
- forecast = f'_{int((utc - utc_now) / 3600):02d}'
228
- if forecast not in ['', '_01', '_04', '_12', '_24']:
229
- continue
230
- for k, v in row['values'].items():
231
- ftag = k + forecast
232
- value = float(v)
233
- time_us = int(utc * 1000000)
234
- if ftag in self.tags:
235
- # Write the tag value. This is one of the following:
236
- # - value # time_us, bus_id auto-generated
237
- # - value, time_us # bus_id auto-generated
238
- # - value, time_us, bus_id # Don't use this one
239
- self.tags[ftag].value = value, time_us
240
-
241
- async def main():
242
- # Connect to the bus and poll the weather service.
243
- bus = BusClient()
244
- await bus.start()
245
- weather = PollWeather() # demo function
246
- periodic = Periodic(weather.periodic, 1.0) # part of pymscada
247
- await periodic.start()
248
- await asyncio.get_event_loop().create_future() # run forever
249
-
250
- if __name__ == '__main__':
251
- asyncio.run(main())
252
- ```
253
-
254
- # Run the modules
255
- You can run the modules in one of: individual terminals, ```nohup ... &``` or as a
256
- ```systemd``` service. I run as a service, the snips are abbreviated (no path) from
257
- the exec line in the auto-generated service files.
258
-
259
- Run the bus first! This needs to remain running all the time. It does not need to
260
- know the tagnames in advance so it can run forever for most tests. It will gather
261
- dead tagnames over time as you are experimenting, however this only requires a
262
- small amount of memory (unless you are setting tag values in the MB - which does
263
- work).
264
-
265
- ```bash
266
- pymscada bus --config bus.yaml
267
- pymscada wwwserver --config wwwserver.yaml --tags tags.yaml
268
- pymscada history --config history.yaml --tags tags.yaml
269
- python weather.py
270
- ```
File without changes