PyAutomationIO 0.0.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.
- automation/__init__.py +46 -0
- automation/alarms/__init__.py +563 -0
- automation/alarms/states.py +192 -0
- automation/alarms/trigger.py +64 -0
- automation/buffer.py +132 -0
- automation/core.py +1775 -0
- automation/dbmodels/__init__.py +23 -0
- automation/dbmodels/alarms.py +524 -0
- automation/dbmodels/core.py +86 -0
- automation/dbmodels/events.py +153 -0
- automation/dbmodels/logs.py +155 -0
- automation/dbmodels/machines.py +181 -0
- automation/dbmodels/opcua.py +81 -0
- automation/dbmodels/opcua_server.py +174 -0
- automation/dbmodels/tags.py +921 -0
- automation/dbmodels/users.py +259 -0
- automation/extensions/__init__.py +15 -0
- automation/extensions/api.py +149 -0
- automation/extensions/cors.py +18 -0
- automation/filter/__init__.py +19 -0
- automation/iad/__init__.py +3 -0
- automation/iad/frozen_data.py +54 -0
- automation/iad/out_of_range.py +51 -0
- automation/iad/outliers.py +51 -0
- automation/logger/__init__.py +0 -0
- automation/logger/alarms.py +426 -0
- automation/logger/core.py +265 -0
- automation/logger/datalogger.py +646 -0
- automation/logger/events.py +194 -0
- automation/logger/logdict.py +53 -0
- automation/logger/logs.py +203 -0
- automation/logger/machines.py +248 -0
- automation/logger/opcua_server.py +130 -0
- automation/logger/users.py +96 -0
- automation/managers/__init__.py +4 -0
- automation/managers/alarms.py +455 -0
- automation/managers/db.py +328 -0
- automation/managers/opcua_client.py +186 -0
- automation/managers/state_machine.py +183 -0
- automation/models.py +174 -0
- automation/modules/__init__.py +14 -0
- automation/modules/alarms/__init__.py +0 -0
- automation/modules/alarms/resources/__init__.py +10 -0
- automation/modules/alarms/resources/alarms.py +280 -0
- automation/modules/alarms/resources/summary.py +79 -0
- automation/modules/events/__init__.py +0 -0
- automation/modules/events/resources/__init__.py +10 -0
- automation/modules/events/resources/events.py +83 -0
- automation/modules/events/resources/logs.py +109 -0
- automation/modules/tags/__init__.py +0 -0
- automation/modules/tags/resources/__init__.py +8 -0
- automation/modules/tags/resources/tags.py +201 -0
- automation/modules/users/__init__.py +2 -0
- automation/modules/users/resources/__init__.py +10 -0
- automation/modules/users/resources/models/__init__.py +2 -0
- automation/modules/users/resources/models/roles.py +5 -0
- automation/modules/users/resources/models/users.py +14 -0
- automation/modules/users/resources/roles.py +38 -0
- automation/modules/users/resources/users.py +113 -0
- automation/modules/users/roles.py +121 -0
- automation/modules/users/users.py +335 -0
- automation/opcua/__init__.py +1 -0
- automation/opcua/models.py +541 -0
- automation/opcua/subscription.py +259 -0
- automation/pages/__init__.py +0 -0
- automation/pages/alarms.py +34 -0
- automation/pages/alarms_history.py +21 -0
- automation/pages/assets/styles.css +7 -0
- automation/pages/callbacks/__init__.py +28 -0
- automation/pages/callbacks/alarms.py +218 -0
- automation/pages/callbacks/alarms_summary.py +20 -0
- automation/pages/callbacks/db.py +222 -0
- automation/pages/callbacks/filter.py +238 -0
- automation/pages/callbacks/machines.py +29 -0
- automation/pages/callbacks/machines_detailed.py +581 -0
- automation/pages/callbacks/opcua.py +266 -0
- automation/pages/callbacks/opcua_server.py +244 -0
- automation/pages/callbacks/tags.py +495 -0
- automation/pages/callbacks/trends.py +119 -0
- automation/pages/communications.py +129 -0
- automation/pages/components/__init__.py +123 -0
- automation/pages/components/alarms.py +151 -0
- automation/pages/components/alarms_summary.py +45 -0
- automation/pages/components/database.py +128 -0
- automation/pages/components/gaussian_filter.py +69 -0
- automation/pages/components/machines.py +396 -0
- automation/pages/components/opcua.py +384 -0
- automation/pages/components/opcua_server.py +53 -0
- automation/pages/components/tags.py +253 -0
- automation/pages/components/trends.py +66 -0
- automation/pages/database.py +26 -0
- automation/pages/filter.py +55 -0
- automation/pages/machines.py +20 -0
- automation/pages/machines_detailed.py +41 -0
- automation/pages/main.py +63 -0
- automation/pages/opcua_server.py +28 -0
- automation/pages/tags.py +40 -0
- automation/pages/trends.py +35 -0
- automation/singleton.py +30 -0
- automation/state_machine.py +1672 -0
- automation/tags/__init__.py +2 -0
- automation/tags/cvt.py +1198 -0
- automation/tags/filter.py +55 -0
- automation/tags/tag.py +418 -0
- automation/tests/__init__.py +10 -0
- automation/tests/test_alarms.py +110 -0
- automation/tests/test_core.py +257 -0
- automation/tests/test_unit.py +21 -0
- automation/tests/test_user.py +155 -0
- automation/utils/__init__.py +164 -0
- automation/utils/decorators.py +222 -0
- automation/utils/npw.py +294 -0
- automation/utils/observer.py +21 -0
- automation/utils/units.py +118 -0
- automation/variables/__init__.py +55 -0
- automation/variables/adimentional.py +30 -0
- automation/variables/current.py +71 -0
- automation/variables/density.py +115 -0
- automation/variables/eng_time.py +68 -0
- automation/variables/force.py +90 -0
- automation/variables/length.py +104 -0
- automation/variables/mass.py +80 -0
- automation/variables/mass_flow.py +101 -0
- automation/variables/percentage.py +30 -0
- automation/variables/power.py +113 -0
- automation/variables/pressure.py +93 -0
- automation/variables/temperature.py +168 -0
- automation/variables/volume.py +70 -0
- automation/variables/volumetric_flow.py +100 -0
- automation/workers/__init__.py +2 -0
- automation/workers/logger.py +164 -0
- automation/workers/state_machine.py +207 -0
- automation/workers/worker.py +36 -0
- pyautomationio-0.0.0.dist-info/METADATA +198 -0
- pyautomationio-0.0.0.dist-info/RECORD +138 -0
- pyautomationio-0.0.0.dist-info/WHEEL +5 -0
- pyautomationio-0.0.0.dist-info/licenses/LICENSE +21 -0
- pyautomationio-0.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""rackio/workers/logger.py
|
|
3
|
+
|
|
4
|
+
This module implements Logger Worker.
|
|
5
|
+
"""
|
|
6
|
+
import logging, time, datetime, os, shutil
|
|
7
|
+
from .worker import BaseWorker
|
|
8
|
+
from ..managers import DBManager
|
|
9
|
+
from ..opcua.models import Client
|
|
10
|
+
from ..logger.datalogger import DataLoggerEngine
|
|
11
|
+
from ..tags.cvt import CVTEngine
|
|
12
|
+
import sqlite3
|
|
13
|
+
from peewee import SqliteDatabase
|
|
14
|
+
from ..dbmodels.tags import TagValue
|
|
15
|
+
from ..dbmodels.alarms import AlarmSummary
|
|
16
|
+
from ..dbmodels.events import Events
|
|
17
|
+
from ..dbmodels.logs import Logs
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class LoggerWorker(BaseWorker):
|
|
21
|
+
|
|
22
|
+
def __init__(self, manager:DBManager, period:int=5.0):
|
|
23
|
+
|
|
24
|
+
super(LoggerWorker, self).__init__()
|
|
25
|
+
|
|
26
|
+
self._manager = manager
|
|
27
|
+
self._period = period
|
|
28
|
+
self.logger = DataLoggerEngine()
|
|
29
|
+
self.cvt = CVTEngine()
|
|
30
|
+
self.sqlite_db = None
|
|
31
|
+
self.sqlite_db_name = None
|
|
32
|
+
|
|
33
|
+
def sqlite_db_backup(self):
|
|
34
|
+
if self.sqlite_db:
|
|
35
|
+
file_size_mb = os.path.getsize(self.sqlite_db_name) / 1024 / 1024
|
|
36
|
+
if file_size_mb > 1 * 1024: # 1 Gb:
|
|
37
|
+
timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
|
38
|
+
name = self.sqlite_db_name.split(".db")[0]
|
|
39
|
+
name = name.split(os.path.sep)[-1]
|
|
40
|
+
backup_file = os.path.join(".", "db", "backups", f"{name}_{timestamp}.db")
|
|
41
|
+
shutil.copy2(os.path.join(".", "db", "app.db"), backup_file)
|
|
42
|
+
logger = logging.getLogger("pyautomation")
|
|
43
|
+
logger.info(f"Backup creado: {backup_file}")
|
|
44
|
+
# Empty TagValue
|
|
45
|
+
query = TagValue.delete()
|
|
46
|
+
query.execute()
|
|
47
|
+
# Empty Alarm Summary
|
|
48
|
+
query = AlarmSummary.delete()
|
|
49
|
+
query.execute()
|
|
50
|
+
# Empty Events
|
|
51
|
+
query = Events.delete()
|
|
52
|
+
query.execute()
|
|
53
|
+
# Empty Logs
|
|
54
|
+
query = Logs.delete()
|
|
55
|
+
query.execute()
|
|
56
|
+
# Execute Vacuum to compact DB
|
|
57
|
+
self.sqlite_db.close()
|
|
58
|
+
conn = sqlite3.connect(self.sqlite_db_name)
|
|
59
|
+
cur = conn.cursor()
|
|
60
|
+
cur.execute("VACUUM;")
|
|
61
|
+
conn.commit()
|
|
62
|
+
conn.close()
|
|
63
|
+
# Reopen DB connection
|
|
64
|
+
from ..dbmodels import proxy
|
|
65
|
+
self._db = self._manager.get_db()
|
|
66
|
+
proxy.initialize(self._db)
|
|
67
|
+
|
|
68
|
+
else:
|
|
69
|
+
db = self.logger.logger.get_db()
|
|
70
|
+
if db:
|
|
71
|
+
if isinstance(db, SqliteDatabase):
|
|
72
|
+
self.sqlite_db = db
|
|
73
|
+
self.sqlite_db_name = db.database
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def check_opcua_connection(self):
|
|
77
|
+
from automation import PyAutomation
|
|
78
|
+
app = PyAutomation()
|
|
79
|
+
if app.opcua_client_manager._clients:
|
|
80
|
+
for _, opcua_client in app.opcua_client_manager._clients.items():
|
|
81
|
+
|
|
82
|
+
if isinstance(opcua_client, Client):
|
|
83
|
+
|
|
84
|
+
opcua_client.reconnect()
|
|
85
|
+
else:
|
|
86
|
+
|
|
87
|
+
app.load_opcua_clients_from_db()
|
|
88
|
+
|
|
89
|
+
def get_tags_from_queue(self, _queue):
|
|
90
|
+
from .. import SEGMENT, MANUFACTURER
|
|
91
|
+
tags = list()
|
|
92
|
+
while not _queue.empty():
|
|
93
|
+
|
|
94
|
+
item = _queue.get(block=False)
|
|
95
|
+
tag_name = item["tag"]
|
|
96
|
+
tag = self.cvt.get_tag_by_name(name=tag_name)
|
|
97
|
+
if tag:
|
|
98
|
+
|
|
99
|
+
if tag.manufacturer==MANUFACTURER and tag.segment==SEGMENT:
|
|
100
|
+
|
|
101
|
+
value = item['value']
|
|
102
|
+
timestamp = item["timestamp"]
|
|
103
|
+
tags.append({"tag":tag_name, "value":value, "timestamp":timestamp})
|
|
104
|
+
|
|
105
|
+
elif not MANUFACTURER and not SEGMENT:
|
|
106
|
+
|
|
107
|
+
value = item['value']
|
|
108
|
+
timestamp = item["timestamp"]
|
|
109
|
+
tags.append({"tag":tag_name, "value":value, "timestamp":timestamp})
|
|
110
|
+
|
|
111
|
+
return tags
|
|
112
|
+
|
|
113
|
+
def reconnect_to_db(self):
|
|
114
|
+
from automation import PyAutomation
|
|
115
|
+
app = PyAutomation()
|
|
116
|
+
|
|
117
|
+
if self.db_reconnection:
|
|
118
|
+
|
|
119
|
+
logging.critical("Trying reconnect to DB...")
|
|
120
|
+
|
|
121
|
+
self.db_reconnection = False
|
|
122
|
+
db_connected = app.reconnect_to_db()
|
|
123
|
+
|
|
124
|
+
if db_connected:
|
|
125
|
+
|
|
126
|
+
logging.critical("Reconnection successfully")
|
|
127
|
+
self.db_reconnection = True
|
|
128
|
+
|
|
129
|
+
def run(self):
|
|
130
|
+
r"""
|
|
131
|
+
Documentation here
|
|
132
|
+
"""
|
|
133
|
+
_queue = self._manager.get_queue()
|
|
134
|
+
self.db_reconnection = True
|
|
135
|
+
|
|
136
|
+
while True:
|
|
137
|
+
|
|
138
|
+
if self.db_reconnection:
|
|
139
|
+
|
|
140
|
+
db_connection = self.logger.logger.check_connectivity()
|
|
141
|
+
|
|
142
|
+
if db_connection:
|
|
143
|
+
self.sqlite_db_backup()
|
|
144
|
+
tags = self.get_tags_from_queue(_queue=_queue)
|
|
145
|
+
|
|
146
|
+
if tags:
|
|
147
|
+
|
|
148
|
+
self.logger.write_tags(tags=tags)
|
|
149
|
+
|
|
150
|
+
else:
|
|
151
|
+
|
|
152
|
+
self.reconnect_to_db()
|
|
153
|
+
|
|
154
|
+
else:
|
|
155
|
+
|
|
156
|
+
self.reconnect_to_db()
|
|
157
|
+
|
|
158
|
+
self.check_opcua_connection()
|
|
159
|
+
|
|
160
|
+
if self.stop_event.is_set():
|
|
161
|
+
logging.critical("Alarm worker shutdown successfully!")
|
|
162
|
+
break
|
|
163
|
+
|
|
164
|
+
time.sleep(self._period)
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""automation/workers/state.py
|
|
3
|
+
|
|
4
|
+
This module implements State Machine Worker.
|
|
5
|
+
"""
|
|
6
|
+
import heapq
|
|
7
|
+
import logging
|
|
8
|
+
import time
|
|
9
|
+
from collections import deque
|
|
10
|
+
from threading import Thread
|
|
11
|
+
from .worker import BaseWorker
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MachineScheduler():
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
|
|
18
|
+
self._ready = deque()
|
|
19
|
+
self._sleeping = list()
|
|
20
|
+
self._sequence = 0
|
|
21
|
+
self.last = None
|
|
22
|
+
self._stop = False
|
|
23
|
+
|
|
24
|
+
def call_soon(self, func):
|
|
25
|
+
|
|
26
|
+
self._ready.append(func)
|
|
27
|
+
|
|
28
|
+
def call_later(self, delay, func, machine):
|
|
29
|
+
self._sequence += 1
|
|
30
|
+
deadline = time.time() + delay
|
|
31
|
+
heapq.heappush(self._sleeping, (deadline, self._sequence, func, machine))
|
|
32
|
+
|
|
33
|
+
def stop(self):
|
|
34
|
+
|
|
35
|
+
self._stop = True
|
|
36
|
+
|
|
37
|
+
def run(self):
|
|
38
|
+
|
|
39
|
+
self.set_last()
|
|
40
|
+
|
|
41
|
+
while self._ready or self._sleeping:
|
|
42
|
+
|
|
43
|
+
if self._stop:
|
|
44
|
+
break
|
|
45
|
+
|
|
46
|
+
if not self._ready and self._sleeping:
|
|
47
|
+
deadline, _, func, machine = heapq.heappop(self._sleeping)
|
|
48
|
+
self.sleep_elapsed(machine)
|
|
49
|
+
|
|
50
|
+
self._ready.append(func)
|
|
51
|
+
|
|
52
|
+
while self._ready:
|
|
53
|
+
func = self._ready.popleft()
|
|
54
|
+
func()
|
|
55
|
+
|
|
56
|
+
def set_last(self):
|
|
57
|
+
|
|
58
|
+
self.last = time.time()
|
|
59
|
+
|
|
60
|
+
return self.last
|
|
61
|
+
|
|
62
|
+
def sleep_elapsed(self, machine):
|
|
63
|
+
elapsed = time.time() - self.last
|
|
64
|
+
interval = machine.get_interval()
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
time.sleep(interval - elapsed)
|
|
68
|
+
self.set_last()
|
|
69
|
+
except ValueError:
|
|
70
|
+
self.set_last()
|
|
71
|
+
logger = logging.getLogger("pyautomation")
|
|
72
|
+
logger.warning(f"State Machine: {machine.name.value} NOT executed on time - Execution Interval: {interval} - Elapsed: {elapsed}")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class SchedThread(Thread):
|
|
76
|
+
|
|
77
|
+
def __init__(self, machine):
|
|
78
|
+
|
|
79
|
+
super(SchedThread, self).__init__()
|
|
80
|
+
|
|
81
|
+
self.machine = machine
|
|
82
|
+
|
|
83
|
+
def stop(self):
|
|
84
|
+
|
|
85
|
+
self.scheduler.stop()
|
|
86
|
+
|
|
87
|
+
def loop_closure(self, machine, scheduler:MachineScheduler):
|
|
88
|
+
|
|
89
|
+
def loop():
|
|
90
|
+
machine.loop()
|
|
91
|
+
interval = machine.get_interval()
|
|
92
|
+
scheduler.call_later(interval, loop, machine)
|
|
93
|
+
|
|
94
|
+
return loop
|
|
95
|
+
|
|
96
|
+
def target(self, machine):
|
|
97
|
+
scheduler = MachineScheduler()
|
|
98
|
+
self.scheduler = scheduler
|
|
99
|
+
func = self.loop_closure(machine, scheduler)
|
|
100
|
+
scheduler.call_soon(func)
|
|
101
|
+
scheduler.run()
|
|
102
|
+
|
|
103
|
+
def run(self):
|
|
104
|
+
|
|
105
|
+
self.target(self.machine)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class AsyncStateMachineWorker(BaseWorker):
|
|
109
|
+
|
|
110
|
+
def __init__(self):
|
|
111
|
+
|
|
112
|
+
super(AsyncStateMachineWorker, self).__init__()
|
|
113
|
+
self._machines = list()
|
|
114
|
+
self._schedulers = list()
|
|
115
|
+
self.jobs = list()
|
|
116
|
+
|
|
117
|
+
def add_machine(self, machine):
|
|
118
|
+
|
|
119
|
+
self._machines.append(machine)
|
|
120
|
+
|
|
121
|
+
def run(self):
|
|
122
|
+
|
|
123
|
+
for machine in self._machines:
|
|
124
|
+
|
|
125
|
+
sched = SchedThread(machine)
|
|
126
|
+
self._schedulers.append(sched)
|
|
127
|
+
|
|
128
|
+
for sched in self._schedulers:
|
|
129
|
+
|
|
130
|
+
sched.daemon = True
|
|
131
|
+
sched.start()
|
|
132
|
+
|
|
133
|
+
def join(self, machine):
|
|
134
|
+
|
|
135
|
+
sched = SchedThread(machine)
|
|
136
|
+
self._schedulers.append(sched)
|
|
137
|
+
sched.daemon = True
|
|
138
|
+
sched.start()
|
|
139
|
+
|
|
140
|
+
def drop(self, machine):
|
|
141
|
+
r"""
|
|
142
|
+
Documentation here
|
|
143
|
+
"""
|
|
144
|
+
sched_to_drop = None
|
|
145
|
+
for index, sched in enumerate(self._schedulers):
|
|
146
|
+
if machine==sched.machine:
|
|
147
|
+
|
|
148
|
+
sched_to_drop = self._schedulers.pop(index)
|
|
149
|
+
break
|
|
150
|
+
|
|
151
|
+
if sched_to_drop:
|
|
152
|
+
sched.stop()
|
|
153
|
+
|
|
154
|
+
def stop(self):
|
|
155
|
+
|
|
156
|
+
for sched in self._schedulers:
|
|
157
|
+
try:
|
|
158
|
+
sched.stop()
|
|
159
|
+
except Exception as e:
|
|
160
|
+
message = "Error on async scheduler stop"
|
|
161
|
+
logger = logging.getLogger("pyautomation")
|
|
162
|
+
logger.error(f"{message} - {e}")
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class StateMachineWorker(BaseWorker):
|
|
166
|
+
|
|
167
|
+
def __init__(self, manager):
|
|
168
|
+
|
|
169
|
+
super(StateMachineWorker, self).__init__()
|
|
170
|
+
|
|
171
|
+
self._manager = manager
|
|
172
|
+
self._sync_scheduler = MachineScheduler()
|
|
173
|
+
self._async_scheduler = AsyncStateMachineWorker()
|
|
174
|
+
self.jobs = list()
|
|
175
|
+
|
|
176
|
+
def loop_closure(self, machine):
|
|
177
|
+
|
|
178
|
+
self._machine = machine
|
|
179
|
+
|
|
180
|
+
def loop():
|
|
181
|
+
machine.loop()
|
|
182
|
+
interval = machine.get_interval()
|
|
183
|
+
self._sync_scheduler.call_later(interval, loop, machine)
|
|
184
|
+
|
|
185
|
+
return loop
|
|
186
|
+
|
|
187
|
+
def run(self):
|
|
188
|
+
|
|
189
|
+
for machine, interval, mode in self._manager.get_machines():
|
|
190
|
+
|
|
191
|
+
if mode == "async":
|
|
192
|
+
|
|
193
|
+
self._async_scheduler.add_machine(machine)
|
|
194
|
+
|
|
195
|
+
else:
|
|
196
|
+
|
|
197
|
+
func = self.loop_closure(machine)
|
|
198
|
+
self._sync_scheduler.call_soon(func)
|
|
199
|
+
|
|
200
|
+
self._async_scheduler.run()
|
|
201
|
+
self._sync_scheduler.run()
|
|
202
|
+
|
|
203
|
+
def stop(self):
|
|
204
|
+
|
|
205
|
+
self._async_scheduler.stop()
|
|
206
|
+
self._sync_scheduler.stop()
|
|
207
|
+
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""workers/worker.py
|
|
3
|
+
|
|
4
|
+
This module implements all thread classes for workers.
|
|
5
|
+
"""
|
|
6
|
+
from threading import Thread
|
|
7
|
+
from threading import Event as ThreadEvent
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BaseWorker(Thread):
|
|
11
|
+
|
|
12
|
+
def __init__(self):
|
|
13
|
+
|
|
14
|
+
super(BaseWorker, self).__init__()
|
|
15
|
+
|
|
16
|
+
self.stop_event = ThreadEvent()
|
|
17
|
+
|
|
18
|
+
def get_stop_event(self):
|
|
19
|
+
|
|
20
|
+
return self.stop_event
|
|
21
|
+
|
|
22
|
+
def stop(self):
|
|
23
|
+
|
|
24
|
+
self.stop_event.set()
|
|
25
|
+
|
|
26
|
+
def __getstate__(self):
|
|
27
|
+
|
|
28
|
+
state = self.__dict__.copy()
|
|
29
|
+
del state['stop_event']
|
|
30
|
+
return state
|
|
31
|
+
|
|
32
|
+
def __setstate__(self, state):
|
|
33
|
+
|
|
34
|
+
self.__dict__.update(state)
|
|
35
|
+
self.stop_event = ThreadEvent()
|
|
36
|
+
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: PyAutomationIO
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: A python library to develop automation continuous tasks using sync or async concurrent threads
|
|
5
|
+
Home-page: https://github.com/know-ai/PyAutomation
|
|
6
|
+
Author: KnowAI
|
|
7
|
+
Author-email: dev.know.ai@gmail.com
|
|
8
|
+
License: GNU AFFERO GENERAL PUBLIC LICENSE
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: License :: OSI Approved :: GNU Affero General Public License v3
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
17
|
+
Classifier: Topic :: System :: Logging
|
|
18
|
+
Classifier: Topic :: System :: Monitoring
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: python-statemachine==2.4.0
|
|
22
|
+
Requires-Dist: python-statemachine[diagrams]
|
|
23
|
+
Requires-Dist: peewee==3.17.8
|
|
24
|
+
Requires-Dist: psycopg2-binary==2.9.9; sys_platform != "win32"
|
|
25
|
+
Requires-Dist: psycopg2==2.9.9; sys_platform == "win32"
|
|
26
|
+
Requires-Dist: numpy==1.25.0
|
|
27
|
+
Requires-Dist: PyWavelets==1.7.0
|
|
28
|
+
Requires-Dist: dash==2.18.2
|
|
29
|
+
Requires-Dist: dash-bootstrap-components==1.6.0
|
|
30
|
+
Requires-Dist: dash-mantine-components==0.14.7
|
|
31
|
+
Requires-Dist: dash-iconify==0.1.2
|
|
32
|
+
Requires-Dist: opcua==0.98.13
|
|
33
|
+
Requires-Dist: python-dotenv==1.0.1
|
|
34
|
+
Requires-Dist: cryptography==43.0.3
|
|
35
|
+
Requires-Dist: Flask-Cors==5.0.0
|
|
36
|
+
Requires-Dist: flask-restx==1.3.0
|
|
37
|
+
Requires-Dist: Flask-SocketIO==5.4.1
|
|
38
|
+
Requires-Dist: gunicorn==23.0.0
|
|
39
|
+
Requires-Dist: websocket-client==1.8.0
|
|
40
|
+
Requires-Dist: gevent-websocket==0.10.1
|
|
41
|
+
Requires-Dist: PyJWT==2.9.0
|
|
42
|
+
Requires-Dist: pydot==3.0.2
|
|
43
|
+
Dynamic: author
|
|
44
|
+
Dynamic: author-email
|
|
45
|
+
Dynamic: classifier
|
|
46
|
+
Dynamic: description
|
|
47
|
+
Dynamic: description-content-type
|
|
48
|
+
Dynamic: home-page
|
|
49
|
+
Dynamic: license
|
|
50
|
+
Dynamic: license-file
|
|
51
|
+
Dynamic: requires-dist
|
|
52
|
+
Dynamic: summary
|
|
53
|
+
|
|
54
|
+
# PyAutomation
|
|
55
|
+
|
|
56
|
+
The development intention of this framework is to provide the ability to develop industrial applications where processes need to be executed concurrently and field data need to be managed for monitoring, control, and supervision applications.
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+

|
|
61
|
+
|
|
62
|
+
In the image above, you can generally see the architecture and interaction of the different modules that make up the framework.
|
|
63
|
+
|
|
64
|
+
For this, state machines are available that run in the background and concurrently to acquire data by query (DAQ), Acquire Data by Exception (DAS) and any other general purpose state machine.
|
|
65
|
+
|
|
66
|
+
It has a memory persistence module for real-time variables that we call (CVT, Current Value Table).
|
|
67
|
+
|
|
68
|
+
There is also an alarm management system
|
|
69
|
+
|
|
70
|
+
And finally, the disk persistence of the variables to provide functionalities for historical trends of the field variables.
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# Run Config Page
|
|
74
|
+
|
|
75
|
+
## Crearte Virtual Environment
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
python3 -m venv venv
|
|
79
|
+
. venv/bin/activate
|
|
80
|
+
```
|
|
81
|
+
## Install Dependencies
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
pip install --upgrade pip
|
|
85
|
+
pip install -r requirements.txt
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Run Config page
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
python run.py
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
or
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
|
|
98
|
+
./docker-entrypoint.sh
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Deploy documentation on Development mode With mkautodoc
|
|
102
|
+
|
|
103
|
+
### Install Wheel
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
pip install wheel
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Create PyAutomation's package
|
|
110
|
+
|
|
111
|
+
Execute this code where is setup.py file
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
python3 setup.py bdist_wheel
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
This create some folders
|
|
118
|
+
|
|
119
|
+
- build
|
|
120
|
+
- dist
|
|
121
|
+
- PyAutomation.egg-info
|
|
122
|
+
|
|
123
|
+
### Install PyAutomation Folder
|
|
124
|
+
|
|
125
|
+
Located into "dist" folder
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
pip install dist/PyAutomation-1.0.0-py3-none-any.whl
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
After that, you can run mkdocs serve
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# Deploy
|
|
135
|
+
|
|
136
|
+
Make the following `.env` file:
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
PORT=5000
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Docker
|
|
143
|
+
|
|
144
|
+
Export environment variables
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
export $(grep -v '^#' .env | xargs)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Start the app
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
sudo docker run -d \
|
|
154
|
+
--name PyAutomation \
|
|
155
|
+
-p ${PORT}:${PORT}\
|
|
156
|
+
-v $(pwd)/temp/db:/app/db \
|
|
157
|
+
-v $(pwd)/temp/logs:/app/logs \
|
|
158
|
+
-e PORT=${PORT} \
|
|
159
|
+
knowai/automation:1.0.0
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Docker Compose
|
|
163
|
+
|
|
164
|
+
If you want to deploy it using docker compose, make the following `docker-compose.yml` file:
|
|
165
|
+
|
|
166
|
+
```YaMl
|
|
167
|
+
version: '3.3'
|
|
168
|
+
|
|
169
|
+
services:
|
|
170
|
+
|
|
171
|
+
automation:
|
|
172
|
+
container_name: "PyAutomation"
|
|
173
|
+
image: "knowai/automation:1.0.0"
|
|
174
|
+
restart: always
|
|
175
|
+
ports:
|
|
176
|
+
- ${PORT}:${PORT}
|
|
177
|
+
volumes:
|
|
178
|
+
- ./temp/db:/app/db
|
|
179
|
+
- ./temp/logs:/app/logs
|
|
180
|
+
environment:
|
|
181
|
+
PORT: ${PORT}
|
|
182
|
+
OPCUA_SERVER_PORT: ${OPCUA_SERVER_PORT}
|
|
183
|
+
healthcheck:
|
|
184
|
+
test: ["CMD-SHELL", "curl --fail -s -k http://0.0.0.0:${PORT}/api/healthcheck/ || curl --fail -s -k https://0.0.0.0:${PORT}/api/healthcheck/ || exit 1"]
|
|
185
|
+
interval: 15s
|
|
186
|
+
timeout: 10s
|
|
187
|
+
retries: 3
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Start the docker compose file
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
sudo docker-compose --env-file .env up -d
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
Go to http://host:${PORT} to view the config page
|