pymscada 0.0.14__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.

@@ -0,0 +1,88 @@
1
+ Metadata-Version: 2.1
2
+ Name: pymscada
3
+ Version: 0.1.0
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.0.14.dist-info/METADATA,sha256=jJFPngLLwjv1ujm89kVFQwtjIybU5q1ALpFJSmFMFgA,8818
2
- pymscada-0.0.14.dist-info/WHEEL,sha256=N2J68yzZqJh3mI_Wg92rwhw0rtJDFpZj9bwQIMJgaVg,90
3
- pymscada-0.0.14.dist-info/entry_points.txt,sha256=AcZZ7HFj8k1ztP6ge-5bdRinYF8glW2s6lFEQG3esN4,57
4
- pymscada-0.0.14.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
1
+ pymscada-0.1.0.dist-info/METADATA,sha256=4k0xZCUfrBdcOG1vnI9reNEvrOg8UKqG6MaN5vp_Oqs,3212
2
+ pymscada-0.1.0.dist-info/WHEEL,sha256=N2J68yzZqJh3mI_Wg92rwhw0rtJDFpZj9bwQIMJgaVg,90
3
+ pymscada-0.1.0.dist-info/entry_points.txt,sha256=AcZZ7HFj8k1ztP6ge-5bdRinYF8glW2s6lFEQG3esN4,57
4
+ pymscada-0.1.0.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=ISXhwRJbZBRu0cMeVHwgHzMgk9YslhnPtjs7-0lgjRo,3351
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
@@ -14,35 +14,37 @@ pymscada/demo/__init__.py,sha256=WsDDgkWnZBJbt2-cJCdc2NvRAv_T4a7WOC1Q0k_l0gI,29
14
14
  pymscada/demo/bus.yaml,sha256=zde5JDo2Yv5s7NvJ569gAEoTDvsvgBwRPxfrYhsxj3w,26
15
15
  pymscada/demo/files.yaml,sha256=Uizvo-LEhHCRePGNLdvl9m_Z40qOlT7aZDLE4zTFSMc,169
16
16
  pymscada/demo/history.yaml,sha256=mn_Xf4h_bK6vwZVQ0Iz9BzJpwWok2gEgSKbDgEM8AOQ,46
17
- pymscada/demo/logixclient.yaml,sha256=0fsi702CozjvshZNy8KirzCaah3asHR_Bjl-76mrNOA,925
17
+ pymscada/demo/logixclient.yaml,sha256=G_NlJhBYwT1a9ceHDgO6fCNKFmBM2pVO_t9Xa1NqlRY,912
18
18
  pymscada/demo/modbus_plc.py,sha256=3zZHHbyrdxyryEHBeNIw-fpcDGcS1MaJiqEwQDr6zWI,2397
19
- pymscada/demo/modbusclient.yaml,sha256=vF96CTA-fsIbTUMnVa1yE4NBnMW5-jeVyEe4HrwfvCY,1020
20
- pymscada/demo/modbusserver.yaml,sha256=xit3bfQrf_CnJZXXiGnWAOOAfH33KIxJC4jQWqkEJLc,1036
19
+ pymscada/demo/modbusclient.yaml,sha256=geeCsUJZkkEj7jjXR_Yk6R5zA5Ta9IczrHsARz7ZgXY,1099
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
24
25
  pymscada/demo/pymscada-history.service,sha256=kEU_RsrRSmEUE-nR23n2q2yZxjALm_nCrJhjxS4pcSQ,382
25
26
  pymscada/demo/pymscada-io-logixclient.service,sha256=gvnCJgUeqnIgnuN-Wf1XhB0FJxVYyLksmMO7JC0wT-Y,344
27
+ pymscada/demo/pymscada-io-modbusclient.service,sha256=4tenKcrfRi0iMdv8-k2gtMQA4OPTM59zAyKpovwemxM,344
28
+ pymscada/demo/pymscada-io-modbusserver.service,sha256=FqCMD3EJKoiq6EbYnoijRLX5UeUWbZrNzDs50eQj7iE,344
29
+ pymscada/demo/pymscada-io-ping.service,sha256=d1n32srVKGd8qo8JWeBYEEznCRZWRWaBQLOYzdqEXWg,327
26
30
  pymscada/demo/pymscada-io-snmpclient.service,sha256=wrA2kDR3bgO30lP_lNJrIsVXNQiXmWKnphoqUj3QTRI,339
27
- pymscada/demo/pymscada-modbusclient.service,sha256=4tenKcrfRi0iMdv8-k2gtMQA4OPTM59zAyKpovwemxM,344
28
- pymscada/demo/pymscada-modbusserver.service,sha256=FqCMD3EJKoiq6EbYnoijRLX5UeUWbZrNzDs50eQj7iE,344
29
31
  pymscada/demo/pymscada-wwwserver.service,sha256=uDnqzfvAdAnTrqOCqDm1PN7SmeMSuOdmAhorHPJdEVI,366
30
- pymscada/demo/simulate.yaml,sha256=SprrcLY8sx5c-2RggtZqhZS3SL03QM3Yp-Rh5F0trA8,324
31
- pymscada/demo/snmpclient.yaml,sha256=SCjAN8kg7cmS3-Hu6gHw2mo7KxQSYtSi5ew-fIxRs6I,2013
32
- pymscada/demo/tags.yaml,sha256=cLSS6tIQ1XyVf7ldcdW0J2Tv8VsIQwfka4dPuBsY7FI,3494
33
- pymscada/demo/wwwserver.yaml,sha256=KDbe3mmslcK1L0ZnuvtOhhuJG-Hyw9OztMz813pChYw,1920
32
+ pymscada/demo/snmpclient.yaml,sha256=z8iACrFvMftYUtqGrRjPZYZTpn7aOXI-Kp675NAM8cU,2013
33
+ pymscada/demo/tags.yaml,sha256=GH90X3QRBANUhvd2E9OuyIoiZD25OBihHHlDBw1uzlw,4231
34
+ pymscada/demo/wwwserver.yaml,sha256=jyboShIbwkwgMAcFLkpn88VMvGfoc7a44jNrI_B6OqY,12493
34
35
  pymscada/files.py,sha256=kEkiD7j5k69EK4jfMHEp-lbTnulYyrtUjkQDa5xmGPc,1784
35
36
  pymscada/history.py,sha256=yAUfjzo4O3w9-hGEAbtvz9UseMTwRVz7NqS832RtvIs,9455
36
37
  pymscada/iodrivers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
- pymscada/iodrivers/logix_client.py,sha256=czoMLo6VXaj-JrhOkmvoZlXUTzqF4Ez_I2Fe0oW15p8,2808
38
- pymscada/iodrivers/logix_map.py,sha256=vPEUmmqwtCAq8Zu86T_cn7EE19XMa5TlihXWI7aiEHQ,5015
39
- pymscada/iodrivers/modbus_client.py,sha256=aGmy1lAlpmnJxEALZc8VfOXCZpI4Ukgslv14S9EYrpE,10140
40
- pymscada/iodrivers/modbus_map.py,sha256=oFCCkHeGQudSPKhiUnSK3gg2WL_O25wuRCQXEZfpqcA,7172
41
- pymscada/iodrivers/modbus_server.py,sha256=e_wCiF044CK_a-WgfHBfHwj4v3Qwybt3yIpBcInoPMk,7087
42
- pymscada/iodrivers/snmp_client.py,sha256=plKh0gyI_KqD-RLorBJ3OfPrmMG0tuPwOB-SUrC-qFs,2542
43
- pymscada/iodrivers/snmp_client2.py,sha256=pdn5dYyEv4q-ubA0zQ8X-3tQDYxGC7f7Xexa7QPaL40,1675
44
- pymscada/iodrivers/snmp_map.py,sha256=rCTniik40Yy6avooIW8PqWanrnUglwBLKReIM7Ab5lA,2369
45
- pymscada/main.py,sha256=ss_wlV5Pmtp4yKOhk8-KaEloBGGytKLEntdK7WNjcaQ,3975
38
+ pymscada/iodrivers/logix_client.py,sha256=wDhl0pCqs_k7Hd1MtbOuBrEx9emOwDrLJqBNussH1qU,2806
39
+ pymscada/iodrivers/logix_map.py,sha256=ljjBAMJcw199v1V5u0Yfl38U6zbZzba5mdY4I3ZvdIM,5401
40
+ pymscada/iodrivers/modbus_client.py,sha256=mo7VRLWi4W2J5Ft3M6Y5jSbK5vbd6wn4R2dncbiL4nU,9529
41
+ pymscada/iodrivers/modbus_map.py,sha256=af2J3CGSeYQ4mSy8rNsERp9z7fRgRUYk3it5Mrc_IQA,7255
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
45
+ pymscada/iodrivers/snmp_client.py,sha256=e-ea5BQnp-PxTetM9d61Mm-GkQrNZkUUlNp1Km9ZasI,2601
46
+ pymscada/iodrivers/snmp_map.py,sha256=sDdIR5ZPAETpozDfBt_XQiZ-f4t99UCPlzj7BxFxQyM,2369
47
+ pymscada/main.py,sha256=LxFUuijrIuNLOGhNy_5yNIq4pvNz4npsUwHaDtvrdCQ,7191
46
48
  pymscada/misc.py,sha256=0Cj6OFhQonyhyk9x0BG5MiS-6EPk_w6zvavt8o_Hlf0,622
47
49
  pymscada/pdf/__init__.py,sha256=WsDDgkWnZBJbt2-cJCdc2NvRAv_T4a7WOC1Q0k_l0gI,29
48
50
  pymscada/pdf/one.pdf,sha256=eoJ45DrAjVZrwmwdA_EAz1fwmT44eRnt_tkc2pmMrKY,1488
@@ -50,9 +52,9 @@ pymscada/pdf/two.pdf,sha256=TAuW5yLU1_wfmTH_I5ezHwY0pxhCVuZh3ixu0kwmJwE,1516
50
52
  pymscada/periodic.py,sha256=MLlL93VLvFqBBgjO1Us1t0aLHTZ5BFdW0B__G02T1nQ,1235
51
53
  pymscada/protocol_constants.py,sha256=ndFeuzhWjKTr_ahvmGJc6Cs5pYkHM8CRgNNDe0Tqecs,1997
52
54
  pymscada/samplers.py,sha256=t0IscgsCm5YByioOZ6aOKMO_guDFS_wxnJSiOGKI4Nw,2583
53
- pymscada/simulate.py,sha256=39PfIc_jJC4ekuSEqlVb8SlxvIjwgyTkCL5fSlwmanc,2404
54
55
  pymscada/tag.py,sha256=Q2EgacTnsxnGLM_zoHYsVWdwmd1faqfbxw9byI5fCgY,9463
56
+ pymscada/tools/snmp_client2.py,sha256=pdn5dYyEv4q-ubA0zQ8X-3tQDYxGC7f7Xexa7QPaL40,1675
55
57
  pymscada/tools/walk.py,sha256=OgpprUbKLhEWMvJGfU1ckUt_PFEpwZVOD8HucCgzmOc,1625
56
- pymscada/validate.py,sha256=h2VnIT3EV3hLGtaAaBTjeDVMrVl8SpP9nz_fqyRtU4Y,10634
58
+ pymscada/validate.py,sha256=VPpAVEwfgori5OREEwWlbPoPxz5Tfqr6dw-O5pINHyI,13125
57
59
  pymscada/www_server.py,sha256=BW6-ctE5BgyFDiyUJsOpe3c06DRp8k83MCGjwPh5fco,11473
58
- pymscada-0.0.14.dist-info/RECORD,,
60
+ pymscada-0.1.0.dist-info/RECORD,,
@@ -1,21 +0,0 @@
1
- bus_ip: 127.0.0.1
2
- bus_port: 1324
3
- process:
4
- one:
5
- type: sine
6
- offset: 50
7
- amplitude: 40
8
- frequency: 0.0005
9
- sub:
10
- offset: FloatSet
11
- pub:
12
- result: FloatVal
13
- two:
14
- type: sawtooth
15
- offset: 10
16
- amplitude: 80
17
- frequency: 0.00005
18
- sub:
19
- offset: IntSet
20
- pub:
21
- result: IntVal
pymscada/simulate.py DELETED
@@ -1,66 +0,0 @@
1
- """Simulate logic."""
2
- import asyncio
3
- import logging
4
- import math
5
- from pymscada.bus_client import BusClient
6
- from pymscada.misc import find_nodes
7
- from pymscada.tag import Tag, TYPES
8
- from time import monotonic
9
-
10
-
11
- class Simulate():
12
- """Modify tag values to simulate a real process."""
13
-
14
- def __init__(self, bus_ip: str = '127.0.0.1', bus_port: int = 1324,
15
- tag_info: dict = {}, process: dict = {}) -> None:
16
- """
17
- Connect to bus on bus_ip:bus_port, modify tag values per process.
18
-
19
- Event loop must be running.
20
- """
21
- self.busclient = BusClient(bus_ip, bus_port, tag_info)
22
- self.process = process
23
- self.tags = {}
24
- for x in ['sub', 'pub']: # resist the temptation to
25
- for t in find_nodes(x, self.process):
26
- for tagname in t[x].values():
27
- self.tags[tagname] = Tag(
28
- tagname, TYPES[tag_info[tagname]['type']])
29
-
30
- def step(self):
31
- """Do simulation step."""
32
- for e in self.process.values():
33
- time = monotonic()
34
- tagname = e['pub']['result']
35
- if e['type'] == 'sine':
36
- result = e['offset'] + e['amplitude'] * \
37
- math.sin(e['frequency'] * time)
38
- elif e['type'] == 'sawtooth':
39
- period = 1 / e['frequency']
40
- result = int(e['offset'] + e['amplitude'] * (time %
41
- period) / period)
42
- else:
43
- return
44
- if self.tags[tagname].value is None or \
45
- abs(self.tags[tagname].value - result) > 0.01:
46
- self.tags[tagname].value = result
47
- logging.warning(f'simulate {tagname} {result}')
48
-
49
- async def periodic(self):
50
- """Run simulation step every 5 seconds."""
51
- while True:
52
- self.next_run += 1.0
53
- self.step()
54
- self.last_ran = monotonic()
55
- sleep_time = self.next_run - self.last_ran
56
- if sleep_time < 0:
57
- self.next_run = self.last_ran
58
- logging.warning(f'Health count skipped at {self.last_ran}')
59
- else:
60
- await asyncio.sleep(sleep_time)
61
-
62
- async def start(self):
63
- """Provide the simulation process."""
64
- await self.busclient.start()
65
- self.next_run = monotonic()
66
- await asyncio.create_task(self.periodic())
@@ -1,251 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: pymscada
3
- Version: 0.0.14
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 a small SCADA package that will run on Linux (preferably) or
30
- Windows. The server runs as several modules on the host, sharing
31
- information through a message bus. A __subset__ of modules is:
32
-
33
- - Bus server - shares tag values with by exception updates
34
- - Modbus client - reads and writes to a PLC using Modbus/TCP
35
- - History - saves data changes, serves history to web pages
36
- - Web server - serves web pages which connect with a web socket
37
- - Web pages - an Angular single page web application
38
-
39
- Web pages are responsive and defined procedurally from the
40
- ```wwwserver.yaml``` config file.
41
-
42
- Trends use [uPlot](https://github.com/leeoniya/uPlot).
43
-
44
- ## Objectives
45
-
46
- Traditional SCADA has a fixed 19:6, 1920x1080 or some equivalent layout.
47
- It's great on a big screen but not good on a phone. Hence __Mobile__
48
- SCADA with a responsive layout.
49
-
50
- I wrote Mobile SCADA to provide a GUI to the other things I was trying to
51
- do, I wanted to leverage web browsers and eliminate a dedicated
52
- _viewer.exe_. Display on the client is fast, trends, as fast as I can
53
- make them.
54
-
55
- Uptimes should be excellent. The best I have on an earlier version is
56
- over 5 years for about half of the script modules. This version is a
57
- complete rewrite, however the aim is the same.
58
-
59
- All tag value updates are by exception. So an update from you setting a
60
- value to seeing the feedback should be __FAST__.
61
-
62
- # See also
63
-
64
- - The angular project [angmscada](https://github.com/jamie0walton/angmscada)
65
- - Python container for the compiled angular pages [pymscada-html](https://github.com/jamie0walton/pymscada-html)
66
- - An add-on IO driver for Rockwell using pycomm3 [pymscada-pycomm3](https://github.com/jamie0walton/pymscada-pycomm3)
67
-
68
- # Licence
69
-
70
- ```pymscada``` is distributed under the GPLv3 [license](./LICENSE).
71
-
72
- # Example Use
73
- This was all run on a Raspberry Pi 3B+ with a 16GB SDRAM card.
74
-
75
- ## First
76
- Checkout the example files. Start in an empty directory. Plan to keep
77
- in the directory you check out into as the config file path details
78
- are auto-generated for the location you check out in to.
79
- ```bash
80
- mscada@raspberrypi:~/test $ pymscada checkout
81
- making 'history' folder
82
- making pdf dir
83
- making config dir
84
- Creating /home/mscada/test/config/modbusclient.yaml
85
- Creating /home/mscada/test/config/pymscada-history.service
86
- Creating /home/mscada/test/config/wwwserver.yaml
87
- Creating /home/mscada/test/config/pymscada-demo-modbus_plc.service
88
- Creating /home/mscada/test/config/files.yaml
89
- Creating /home/mscada/test/config/pymscada-modbusserver.service
90
- Creating /home/mscada/test/config/pymscada-wwwserver.service
91
- Creating /home/mscada/test/config/simulate.yaml
92
- Creating /home/mscada/test/config/tags.yaml
93
- Creating /home/mscada/test/config/history.yaml
94
- Creating /home/mscada/test/config/pymscada-files.service
95
- Creating /home/mscada/test/config/bus.yaml
96
- Creating /home/mscada/test/config/modbusserver.yaml
97
- Creating /home/mscada/test/config/modbus_plc.py
98
- Creating /home/mscada/test/config/pymscada-modbusclient.service
99
- Creating /home/mscada/test/config/pymscada-bus.service
100
- Creating /home/mscada/test/config/README.md
101
- mscada@raspberrypi:~/test $
102
- ```
103
-
104
- ## Objective
105
- To show a trend of the temperature forecast with a custom pymscada bus
106
- client program. The end result should look like ...
107
-
108
- ![Temperature](temperature%20trend.png)
109
-
110
- ## Configuration
111
- ### Bus
112
- Defaults in ```bus.yaml``` are fine.
113
-
114
- ### Tags
115
- Add some tags in ```tags.yaml```:
116
- ```yaml
117
- temperature:
118
- desc: temperature
119
- type: float
120
- min: 0
121
- max: 35
122
- units: C
123
- dp: 1
124
- temperature_01:
125
- desc: temperature_01
126
- type: float
127
- min: 0
128
- max: 35
129
- units: C
130
- dp: 1
131
- ... etc.
132
- ```
133
-
134
- ### History
135
- Defaults in ```history.yaml``` are fine.
136
-
137
- ### Web Server
138
- You will need to add a trend page to ```wwwserver.yaml``` as:
139
- ```yaml
140
- - name: Temperature # Creates a Temperature page in the web client
141
- parent: Weather # Add the Temperature page in a submenu under Weather
142
- items:
143
- - type: uplot # Identify the Angular component to use
144
- ms:
145
- desc: Temperature
146
- age: 172800
147
- legend_pos: left
148
- time_pos: left
149
- time_res: m
150
- axes:
151
- - scale: x
152
- range: [-604800, 86400] # initial time range for the trend
153
- - scale: 'C'
154
- range: [0.0, 35.0]
155
- dp: 1
156
- series:
157
- - tagname: temperature # pymscada Tag name
158
- label: Current Temperature
159
- scale: 'C'
160
- color: black # standard html colour names
161
- width: 2
162
- dp: 1 # number of decimal places
163
- ... etc for additional series
164
- ```
165
-
166
- ### Your custom pymscada Module
167
- For this example I polled [tomorrow.io](https://www.tomorrow.io/weather-api/)
168
-
169
- ```weather.py```
170
- ```python
171
- from datetime import datetime
172
- import time
173
- from pymscada import BusClient, Periodic, Tag
174
-
175
- URL = 'https://api.tomorrow.io/v4/timelines'
176
- QUERY = {'location': '-43.527934570040124, 172.6415203551829',
177
- 'fields': ['temperature'],
178
- 'units': 'metric',
179
- 'timesteps': '1h',
180
- 'startTime': 'now',
181
- 'endTime': 'nowPlus24h',
182
- 'apikey': '<your key>'}
183
-
184
- class PollWeather():
185
- def __init__(self):
186
- self.tags = {}
187
- for tagname in ['temperature', 'temperature_01', 'temperature_04',
188
- 'temperature_12', 'temperature_24']:
189
- # Create pymscada tags, tags are singletons by 'tagname'
190
- self.tags[tagname] = Tag(tagname, float)
191
-
192
- async def periodic(self):
193
- now = int(time.time())
194
- if now % 3600 != 120:
195
- return
196
- # Get the weather forecast from tomorrow.io
197
- async with aiohttp.ClientSession() as session:
198
- async with session.get(URL, params=QUERY) as resp:
199
- response = await resp.json()
200
- utc_now = None
201
- for row in response['data']['timelines'][0]['intervals']:
202
- convert = row['startTime'].replace('Z', '+0000')
203
- utc = datetime.strptime(convert, '%Y-%m-%dT%H:%M:%S%z').timestamp()
204
- if utc_now is None:
205
- utc_now = utc
206
- forecast = ''
207
- else:
208
- forecast = f'_{int((utc - utc_now) / 3600):02d}'
209
- if forecast not in ['', '_01', '_04', '_12', '_24']:
210
- continue
211
- for k, v in row['values'].items():
212
- ftag = k + forecast
213
- value = float(v)
214
- time_us = int(utc * 1000000)
215
- if ftag in self.tags:
216
- # Write the tag value. This is one of the following:
217
- # - value # time_us, bus_id auto-generated
218
- # - value, time_us # bus_id auto-generated
219
- # - value, time_us, bus_id # Don't use this one
220
- self.tags[ftag].value = value, time_us
221
-
222
- async def main():
223
- # Connect to the bus and poll the weather service.
224
- bus = BusClient()
225
- await bus.start()
226
- weather = PollWeather() # demo function
227
- periodic = Periodic(weather.periodic, 1.0) # part of pymscada
228
- await periodic.start()
229
- await asyncio.get_event_loop().create_future() # run forever
230
-
231
- if __name__ == '__main__':
232
- asyncio.run(main())
233
- ```
234
-
235
- # Run the modules
236
- You can run the modules in one of: individual terminals, ```nohup ... &``` or as a
237
- ```systemd``` service. I run as a service, the snips are abbreviated (no path) from
238
- the exec line in the auto-generated service files.
239
-
240
- Run the bus first! This needs to remain running all the time. It does not need to
241
- know the tagnames in advance so it can run forever for most tests. It will gather
242
- dead tagnames over time as you are experimenting, however this only requires a
243
- small amount of memory (unless you are setting tag values in the MB - which does
244
- work).
245
-
246
- ```bash
247
- pymscada bus --config bus.yaml
248
- pymscada wwwserver --config wwwserver.yaml --tags tags.yaml
249
- pymscada history --config history.yaml --tags tags.yaml
250
- python weather.py
251
- ```
File without changes