micrOSDevToolKit 2.17.1__py3-none-any.whl → 2.19.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 micrOSDevToolKit might be problematic. Click here for more details.
- micrOS/release_info/micrOS_ReleaseInfo/system_analysis_sum.json +15 -15
- micrOS/source/Config.py +2 -2
- micrOS/source/Espnow.py +29 -12
- micrOS/source/InterConnect.py +107 -31
- micrOS/source/Server.py +2 -3
- micrOS/source/Shell.py +1 -1
- micrOS/source/Tasks.py +22 -20
- micrOS/source/micrOSloader.py +1 -1
- micrOS/source/modules/LM_espnow.py +18 -1
- micrOS/source/modules/LM_neoeffects.py +1 -1
- micrOS/source/modules/LM_neomatrix.py +1 -1
- micrOS/source/modules/LM_oled_ui.py +4 -3
- micrOS/source/modules/LM_oledui.py +4 -3
- micrOS/source/modules/LM_tcs3472.py +131 -17
- micrOS/source/modules/LM_telegram.py +18 -18
- {microsdevtoolkit-2.17.1.dist-info → microsdevtoolkit-2.19.0.dist-info}/METADATA +3 -3
- {microsdevtoolkit-2.17.1.dist-info → microsdevtoolkit-2.19.0.dist-info}/RECORD +40 -42
- toolkit/DevEnvUSB.py +4 -1
- toolkit/Gateway.py +1 -1
- toolkit/dashboard_apps/SystemTest.py +22 -18
- toolkit/micrOSdashboard.py +8 -13
- toolkit/socketClient.py +27 -7
- toolkit/workspace/precompiled/Config.mpy +0 -0
- toolkit/workspace/precompiled/Espnow.mpy +0 -0
- toolkit/workspace/precompiled/InterConnect.mpy +0 -0
- toolkit/workspace/precompiled/Server.mpy +0 -0
- toolkit/workspace/precompiled/Shell.mpy +0 -0
- toolkit/workspace/precompiled/Tasks.mpy +0 -0
- toolkit/workspace/precompiled/micrOSloader.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_espnow.py +18 -1
- toolkit/workspace/precompiled/modules/LM_neoeffects.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_neomatrix.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_oled_ui.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_oledui.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_tcs3472.py +131 -17
- toolkit/workspace/precompiled/modules/LM_telegram.mpy +0 -0
- micrOS/micropython/esp32s2-LOLIN_MINI-20220618-v1.19.1.bin +0 -0
- micrOS/micropython/esp32s3_spiram_oct-20231005-v1.21.0.bin +0 -0
- {microsdevtoolkit-2.17.1.data → microsdevtoolkit-2.19.0.data}/scripts/devToolKit.py +0 -0
- {microsdevtoolkit-2.17.1.dist-info → microsdevtoolkit-2.19.0.dist-info}/WHEEL +0 -0
- {microsdevtoolkit-2.17.1.dist-info → microsdevtoolkit-2.19.0.dist-info}/licenses/LICENSE +0 -0
- {microsdevtoolkit-2.17.1.dist-info → microsdevtoolkit-2.19.0.dist-info}/top_level.txt +0 -0
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
1
|
|
26
26
|
],
|
|
27
27
|
"Tasks.py": [
|
|
28
|
-
9.
|
|
28
|
+
9.46,
|
|
29
29
|
12
|
|
30
30
|
],
|
|
31
31
|
"Config.py": [
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
],
|
|
47
47
|
"Types.py": [
|
|
48
48
|
8.91,
|
|
49
|
-
|
|
49
|
+
29
|
|
50
50
|
],
|
|
51
51
|
"Logger.py": [
|
|
52
52
|
8.84,
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
35
|
|
58
58
|
],
|
|
59
59
|
"InterConnect.py": [
|
|
60
|
-
9.
|
|
60
|
+
9.62,
|
|
61
61
|
2
|
|
62
62
|
],
|
|
63
63
|
"Debug.py": [
|
|
@@ -69,8 +69,8 @@
|
|
|
69
69
|
8
|
|
70
70
|
],
|
|
71
71
|
"Espnow.py": [
|
|
72
|
-
9.
|
|
73
|
-
|
|
72
|
+
9.48,
|
|
73
|
+
4
|
|
74
74
|
],
|
|
75
75
|
"Scheduler.py": [
|
|
76
76
|
9.62,
|
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
0
|
|
118
118
|
],
|
|
119
119
|
"modules/LM_oled_ui.py": [
|
|
120
|
-
|
|
120
|
+
9.2,
|
|
121
121
|
0
|
|
122
122
|
],
|
|
123
123
|
"modules/LM_system.py": [
|
|
@@ -141,7 +141,7 @@
|
|
|
141
141
|
0
|
|
142
142
|
],
|
|
143
143
|
"modules/LM_tcs3472.py": [
|
|
144
|
-
9.
|
|
144
|
+
9.48,
|
|
145
145
|
0
|
|
146
146
|
],
|
|
147
147
|
"modules/LM_oled.py": [
|
|
@@ -245,15 +245,15 @@
|
|
|
245
245
|
0
|
|
246
246
|
],
|
|
247
247
|
"modules/LM_oledui.py": [
|
|
248
|
-
8.
|
|
248
|
+
8.94,
|
|
249
249
|
0
|
|
250
250
|
],
|
|
251
251
|
"modules/LM_espnow.py": [
|
|
252
|
-
|
|
252
|
+
7.14,
|
|
253
253
|
0
|
|
254
254
|
],
|
|
255
255
|
"modules/LM_telegram.py": [
|
|
256
|
-
9.
|
|
256
|
+
9.63,
|
|
257
257
|
0
|
|
258
258
|
],
|
|
259
259
|
"modules/LM_OV2640.py": [
|
|
@@ -323,11 +323,11 @@
|
|
|
323
323
|
},
|
|
324
324
|
"summary": {
|
|
325
325
|
"core": [
|
|
326
|
-
|
|
326
|
+
4004,
|
|
327
327
|
24
|
|
328
328
|
],
|
|
329
329
|
"load": [
|
|
330
|
-
|
|
330
|
+
9988,
|
|
331
331
|
56
|
|
332
332
|
],
|
|
333
333
|
"core_dep": [
|
|
@@ -338,8 +338,8 @@
|
|
|
338
338
|
true,
|
|
339
339
|
4
|
|
340
340
|
],
|
|
341
|
-
"core_score": 9.
|
|
342
|
-
"load_score": 8.
|
|
343
|
-
"version": "2.
|
|
341
|
+
"core_score": 9.25,
|
|
342
|
+
"load_score": 8.4,
|
|
343
|
+
"version": "2.19.0-0"
|
|
344
344
|
}
|
|
345
345
|
}
|
micrOS/source/Config.py
CHANGED
|
@@ -102,12 +102,12 @@ class Data:
|
|
|
102
102
|
liveconf = Data.read_cfg_file(nosafe=True)
|
|
103
103
|
# Remove obsolete keys from conf
|
|
104
104
|
try:
|
|
105
|
-
remove('cleanup
|
|
105
|
+
remove('.cleanup') # Try to remove .cleanup (cleanup indicator by micrOSloader)
|
|
106
106
|
console_write("[CONF] Purge obsolete keys")
|
|
107
107
|
for key in (key for key in liveconf if key not in Data.CONFIG_CACHE):
|
|
108
108
|
liveconf.pop(key, None)
|
|
109
109
|
except Exception:
|
|
110
|
-
console_write("[CONF] SKIP obsolete keys check (no cleanup
|
|
110
|
+
console_write("[CONF] SKIP obsolete keys check (no .cleanup)")
|
|
111
111
|
# Merge template to live conf (store active conf in Data.CONFIG_CACHE)
|
|
112
112
|
Data.CONFIG_CACHE.update(liveconf)
|
|
113
113
|
console_write("[CONF] User config injection done")
|
micrOS/source/Espnow.py
CHANGED
|
@@ -99,14 +99,14 @@ class ESPNowSS:
|
|
|
99
99
|
self.devfid = cfgget('devfid')
|
|
100
100
|
self.devices: dict[bytes, str] = {} # mapping for { "mac address": "devfid" } pairs
|
|
101
101
|
self.server_ready = False
|
|
102
|
-
self.
|
|
102
|
+
self.peer_cache_path = path_join(OSPath.DATA, "espnow_peers.app_json")
|
|
103
103
|
self._load_peers()
|
|
104
104
|
|
|
105
105
|
def _load_peers(self):
|
|
106
|
-
if not is_file(self.
|
|
106
|
+
if not is_file(self.peer_cache_path):
|
|
107
107
|
return
|
|
108
108
|
try:
|
|
109
|
-
with open(self.
|
|
109
|
+
with open(self.peer_cache_path, 'r') as f:
|
|
110
110
|
devices_map = load(f)
|
|
111
111
|
self.devices = {bytes(k): v for k, v in devices_map}
|
|
112
112
|
for mac in self.devices:
|
|
@@ -179,6 +179,7 @@ class ESPNowSS:
|
|
|
179
179
|
self.server_ready = True
|
|
180
180
|
my_task.out = "ESPNow receiver running"
|
|
181
181
|
async for mac, msg in self.espnow:
|
|
182
|
+
reply, response = False, ""
|
|
182
183
|
try:
|
|
183
184
|
reply, response = self._request_handler(msg, my_task, mac)
|
|
184
185
|
if reply:
|
|
@@ -188,7 +189,6 @@ class ESPNowSS:
|
|
|
188
189
|
if len(err.args) > 1 and err.args[1] == 'ESP_ERR_ESPNOW_NOT_FOUND':
|
|
189
190
|
# AUTOMATIC PEER REGISTRATION
|
|
190
191
|
self.espnow.add_peer(mac)
|
|
191
|
-
reply, response = self._request_handler(msg, my_task, mac)
|
|
192
192
|
if reply:
|
|
193
193
|
await self.__asend_raw(mac, response)
|
|
194
194
|
else:
|
|
@@ -262,6 +262,14 @@ class ESPNowSS:
|
|
|
262
262
|
return _tasks
|
|
263
263
|
|
|
264
264
|
# ----------- OTHER METHODS --------------
|
|
265
|
+
def save_peers(self):
|
|
266
|
+
try:
|
|
267
|
+
with open(self.peer_cache_path, "w") as f:
|
|
268
|
+
dump([[list(k), v] for k, v in self.devices.items()], f)
|
|
269
|
+
return True
|
|
270
|
+
except Exception as e:
|
|
271
|
+
syslog(f"[ERR][ESPNOW] Saving peers: {e}")
|
|
272
|
+
return False
|
|
265
273
|
|
|
266
274
|
async def _handshake(self, peer:bytes, tag:str):
|
|
267
275
|
"""
|
|
@@ -285,16 +293,10 @@ class ESPNowSS:
|
|
|
285
293
|
expected_response = f"hello {self.devfid}"
|
|
286
294
|
is_ok = False
|
|
287
295
|
if result == expected_response:
|
|
288
|
-
|
|
289
|
-
with open(self.peer_cache, "w") as f:
|
|
290
|
-
dump([[list(k), v] for k, v in self.devices.items()], f)
|
|
291
|
-
is_ok = True
|
|
292
|
-
except Exception as e:
|
|
293
|
-
syslog(f"[ERR][ESPNOW] Saving peers: {e}")
|
|
296
|
+
is_ok = self.save_peers()
|
|
294
297
|
my_task.out = f"Handshake: {result} from {self.devices.get(peer)} [{'OK' if is_ok else 'NOK'}]"
|
|
295
298
|
sender_task.cancel() # Delete sender task (cleanup)
|
|
296
299
|
|
|
297
|
-
|
|
298
300
|
def handshake(self, peer:bytes|str):
|
|
299
301
|
task_id = f"con.espnow.handshake"
|
|
300
302
|
# Create an asynchronous sending task.
|
|
@@ -321,4 +323,19 @@ class ESPNowSS:
|
|
|
321
323
|
_peers = self.espnow.peers_table
|
|
322
324
|
except Exception as e:
|
|
323
325
|
_peers = str(e)
|
|
324
|
-
return {"stats": _stats, "peers": _peers, "
|
|
326
|
+
return {"stats": _stats, "peers": _peers, "ready": self.server_ready}
|
|
327
|
+
|
|
328
|
+
def members(self):
|
|
329
|
+
return self.devices
|
|
330
|
+
|
|
331
|
+
def remove_peer(self, peer:bytes) -> bool:
|
|
332
|
+
"""
|
|
333
|
+
Remove peer from ESPNow devices
|
|
334
|
+
:param peer: MAC address as bytes to remove
|
|
335
|
+
"""
|
|
336
|
+
if isinstance(peer, bytes):
|
|
337
|
+
if self.devices.pop(peer, None) is not None:
|
|
338
|
+
self.save_peers()
|
|
339
|
+
self.espnow.del_peer(peer)
|
|
340
|
+
return True
|
|
341
|
+
return False
|
micrOS/source/InterConnect.py
CHANGED
|
@@ -1,14 +1,34 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module is responsible for device-device communication
|
|
3
|
+
dedicated to micrOS framework.
|
|
4
|
+
Built-in-function:
|
|
5
|
+
- Socket InterConnect interface
|
|
6
|
+
- ESPNow redirection
|
|
7
|
+
|
|
8
|
+
Designed by
|
|
9
|
+
Marcell Ban aka BxNxM
|
|
10
|
+
Kristof Kasza aka KKristof452
|
|
11
|
+
"""
|
|
12
|
+
|
|
1
13
|
from socket import getaddrinfo, SOCK_STREAM
|
|
2
|
-
from re import compile
|
|
14
|
+
from re import compile as re_compile
|
|
15
|
+
from json import loads
|
|
3
16
|
from uasyncio import open_connection
|
|
17
|
+
|
|
4
18
|
from Debug import syslog
|
|
5
19
|
from Config import cfgget
|
|
6
20
|
from Server import Server
|
|
7
21
|
from Tasks import NativeTask
|
|
8
22
|
|
|
23
|
+
if cfgget('espnow'):
|
|
24
|
+
from Espnow import ESPNowSS
|
|
25
|
+
else:
|
|
26
|
+
ESPNowSS = None
|
|
27
|
+
|
|
9
28
|
|
|
10
29
|
class InterCon:
|
|
11
|
-
CONN_MAP = {}
|
|
30
|
+
CONN_MAP: dict[str, str] = {} # hostname: IP address pairs
|
|
31
|
+
NO_ESPNOW: list[str] = [] # disabled ESPNow hostname list (cache for fallback speed-up)
|
|
12
32
|
PORT = cfgget('socport')
|
|
13
33
|
|
|
14
34
|
def __init__(self):
|
|
@@ -19,7 +39,7 @@ class InterCon:
|
|
|
19
39
|
|
|
20
40
|
@staticmethod
|
|
21
41
|
def validate_ipv4(str_in):
|
|
22
|
-
pattern =
|
|
42
|
+
pattern = re_compile(r'^(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])$')
|
|
23
43
|
return bool(pattern.match(str_in))
|
|
24
44
|
|
|
25
45
|
async def send_cmd(self, host:str, cmd:list):
|
|
@@ -34,7 +54,12 @@ class InterCon:
|
|
|
34
54
|
# Check if host is a hostname (example.local) and resolve its IP address
|
|
35
55
|
if not InterCon.validate_ipv4(host):
|
|
36
56
|
hostname = host
|
|
37
|
-
#
|
|
57
|
+
# Lookup hostname without .domain (sub-hostname matching)
|
|
58
|
+
if '.' not in hostname:
|
|
59
|
+
_hosts = list([d for d in InterCon.CONN_MAP if hostname in d])
|
|
60
|
+
if len(_hosts) > 0:
|
|
61
|
+
hostname = _hosts[0]
|
|
62
|
+
# Retrieve IP address by hostname.domain dynamically
|
|
38
63
|
if InterCon.CONN_MAP.get(hostname, None) is None:
|
|
39
64
|
try:
|
|
40
65
|
addr_info = getaddrinfo(host, InterCon.PORT, 0, SOCK_STREAM)
|
|
@@ -68,8 +93,7 @@ class InterCon:
|
|
|
68
93
|
InterCon.CONN_MAP[hostname] = None if output is None else host
|
|
69
94
|
# None: ServerBusy(or \0) or Prompt mismatch (auto delete cached IP), STR: valid comm. output
|
|
70
95
|
return output
|
|
71
|
-
|
|
72
|
-
syslog(f"[ERR][intercon] Invalid host: {host}")
|
|
96
|
+
syslog(f"[ERR][intercon] Invalid host: {host}")
|
|
73
97
|
return ''
|
|
74
98
|
|
|
75
99
|
async def __run_command(self, cmd:list, hostname:str):
|
|
@@ -85,7 +109,6 @@ class InterCon:
|
|
|
85
109
|
# Compare prompt |node01 $| with hostname 'node01.local'
|
|
86
110
|
if hostname is None or prompt is None or str(prompt).replace('$', '').strip() == str(hostname).split('.')[0]:
|
|
87
111
|
# Run command on validated device
|
|
88
|
-
# TODO: Handle multiple cmd as input, separated by ; (????)
|
|
89
112
|
self.writer.write(str.encode(' '.join(cmd)))
|
|
90
113
|
await self.writer.drain()
|
|
91
114
|
data, _ = await self.__receive_data(prompt=prompt)
|
|
@@ -109,7 +132,7 @@ class InterCon:
|
|
|
109
132
|
return True # AuthOk
|
|
110
133
|
return False # AuthFailed
|
|
111
134
|
|
|
112
|
-
async def __receive_data(self, prompt=None):
|
|
135
|
+
async def __receive_data(self, prompt=None) -> tuple[str, str]:
|
|
113
136
|
"""
|
|
114
137
|
Implements data receive loop until prompt / [configure] / Bye!
|
|
115
138
|
:param prompt: socket shell prompt
|
|
@@ -139,8 +162,34 @@ class InterCon:
|
|
|
139
162
|
data = data.replace(prompt, '').replace('\n', ' ')
|
|
140
163
|
return data, prompt
|
|
141
164
|
|
|
165
|
+
async def auto_espnow_handshake(self, host:str) -> dict:
|
|
166
|
+
"""
|
|
167
|
+
[1] Check espnow.server running on host
|
|
168
|
+
[2] Get MAC address for host (from system info)
|
|
169
|
+
[3] Execute ESPNowSS.handshake
|
|
170
|
+
"""
|
|
171
|
+
response = await self.send_cmd(host, ["task", "list", ">json"])
|
|
172
|
+
if not response:
|
|
173
|
+
return {None: f"[ERR] ESPNow auto handshake: task list, {response}"}
|
|
174
|
+
|
|
175
|
+
active_tasks = loads(response).get("active")
|
|
176
|
+
if "espnow.server" in active_tasks:
|
|
177
|
+
response = await self.send_cmd(host, ["system", "info", ">json"])
|
|
178
|
+
if not response:
|
|
179
|
+
return {None: "[ERR] ESPNow auto handshake: system info"}
|
|
180
|
+
try:
|
|
181
|
+
host_mac = loads(response).get("mac")
|
|
182
|
+
except Exception as ex:
|
|
183
|
+
return {None: f"[ERR] ESPNow auto handshake: {ex}"}
|
|
184
|
+
|
|
185
|
+
return ESPNowSS().handshake(host_mac)
|
|
186
|
+
|
|
187
|
+
if not InterCon.validate_ipv4(host):
|
|
188
|
+
InterCon.NO_ESPNOW.append(str(host).split(".")[0]) # host.local -> host
|
|
189
|
+
return {None: f"ESPNow auto handshake: espnow disabled on host {host}"}
|
|
190
|
+
|
|
142
191
|
|
|
143
|
-
async def
|
|
192
|
+
async def _socket_send_cmd(host:str, cmd:list, com_obj:InterCon) -> None:
|
|
144
193
|
"""
|
|
145
194
|
Async send command wrapper for further async task integration and sync send_cmd usage (main)
|
|
146
195
|
:param host: hostname / IP address
|
|
@@ -148,21 +197,57 @@ async def _send_cmd(host:str, cmd:list, com_obj):
|
|
|
148
197
|
:param com_obj: InterCon object to utilize send_cmd method and task status updates
|
|
149
198
|
"""
|
|
150
199
|
# Send command
|
|
200
|
+
for _ in range(0, 2): # Retry mechanism
|
|
201
|
+
out = await com_obj.send_cmd(host, cmd) # Send CMD
|
|
202
|
+
if out is not None: # Retry mechanism
|
|
203
|
+
break
|
|
204
|
+
await com_obj.task.feed(sleep_ms=100) # Retry mechanism
|
|
205
|
+
com_obj.task.out = '' if out is None else out
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
async def _send_cmd(host:str, cmd:list|str, com_obj:InterCon):
|
|
209
|
+
"""
|
|
210
|
+
Top level InterConnect callback function
|
|
211
|
+
[1] node01.domain -> ESPNow, Socket
|
|
212
|
+
(domain: .local, .net, etc.)
|
|
213
|
+
[2] node01 -> ESPNow, Socket fallback in case found in InterConnect cache
|
|
214
|
+
[3] IP address -> Socket
|
|
215
|
+
"""
|
|
151
216
|
with com_obj.task:
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
217
|
+
if ESPNowSS:
|
|
218
|
+
# [1] ESPNow Active
|
|
219
|
+
name = str(host).split(".")[0] # host.local -> host
|
|
220
|
+
if name not in InterCon.NO_ESPNOW and name in list(ESPNowSS().devices.values()):
|
|
221
|
+
com_obj.task.out = "Redirected to ESPNow"
|
|
222
|
+
if isinstance(cmd, list):
|
|
223
|
+
cmd = ' '.join(cmd)
|
|
224
|
+
# Send command and retrieve result
|
|
225
|
+
sender = ESPNowSS().send(peer=name, msg=cmd)
|
|
226
|
+
sender_task = NativeTask.TASKS.get(list(sender.keys())[0])
|
|
227
|
+
result = await sender_task.await_result(timeout=10)
|
|
228
|
+
if result != "Timeout has beed exceeded":
|
|
229
|
+
# Successful command execution
|
|
230
|
+
com_obj.task.out = result # Output mirroring: Child -> Parent
|
|
231
|
+
sender_task.out = "Redirected to ParentTask" # Remove redundant data in embedded mode
|
|
232
|
+
return
|
|
233
|
+
|
|
234
|
+
# Handle legacy string input
|
|
235
|
+
if isinstance(cmd, str):
|
|
236
|
+
cmd = cmd.split()
|
|
237
|
+
# [1][2] Socket send (default and fallback)
|
|
238
|
+
await _socket_send_cmd(host, cmd, com_obj)
|
|
239
|
+
|
|
240
|
+
if ESPNowSS and name not in InterCon.NO_ESPNOW:
|
|
241
|
+
# [3] Automatic ESPNow handshake
|
|
242
|
+
verdict = await com_obj.auto_espnow_handshake(host)
|
|
243
|
+
if list(verdict.keys())[0] is None:
|
|
244
|
+
syslog(str(list(verdict.values())[0]))
|
|
159
245
|
|
|
160
246
|
|
|
161
247
|
def send_cmd(host:str, cmd:list|str) -> dict:
|
|
162
248
|
"""
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
:param cmd: command string to server socket shell
|
|
249
|
+
Top level InterConnect send task creation
|
|
250
|
+
Handles ESPNow and socket communication
|
|
166
251
|
"""
|
|
167
252
|
def _tagify():
|
|
168
253
|
nonlocal host, cmd
|
|
@@ -171,18 +256,10 @@ def send_cmd(host:str, cmd:list|str) -> dict:
|
|
|
171
256
|
return f"{'.'.join(host.split('.')[-2:])}.{_mod}"
|
|
172
257
|
return f"{host.replace('.local', '')}.{_mod}"
|
|
173
258
|
|
|
174
|
-
# Handle legacy string input
|
|
175
|
-
if isinstance(cmd, str):
|
|
176
|
-
cmd = cmd.split()
|
|
177
|
-
|
|
178
259
|
com_obj = InterCon()
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if
|
|
182
|
-
result = {"verdict": f"Task started: task show {tag}", "tag": tag}
|
|
183
|
-
else:
|
|
184
|
-
result = {"verdict": "Task is Busy", "tag": tag}
|
|
185
|
-
return result
|
|
260
|
+
task_id = f"con.{_tagify()}" # CHECK TASK ID CONFLICT
|
|
261
|
+
state = com_obj.task.create(callback=_send_cmd(host, cmd, com_obj), tag=task_id)
|
|
262
|
+
return {task_id: "Starting"} if state else {task_id: "Already running"}
|
|
186
263
|
|
|
187
264
|
|
|
188
265
|
def host_cache() -> dict:
|
|
@@ -190,4 +267,3 @@ def host_cache() -> dict:
|
|
|
190
267
|
Dump InterCon connection cache
|
|
191
268
|
"""
|
|
192
269
|
return InterCon.CONN_MAP
|
|
193
|
-
|
micrOS/source/Server.py
CHANGED
|
@@ -183,12 +183,11 @@ class WebCli(Client, WebEngine):
|
|
|
183
183
|
:param method: HTTP method name
|
|
184
184
|
:param auto_enable: enable webui when register (endpoint)
|
|
185
185
|
"""
|
|
186
|
-
if method not in WebEngine.METHODS:
|
|
187
|
-
raise ValueError(f"method must be one of {WebEngine.METHODS}")
|
|
188
|
-
|
|
189
186
|
if cfgget('webui'):
|
|
190
187
|
if not endpoint in WebEngine.ENDPOINTS:
|
|
191
188
|
WebEngine.ENDPOINTS[endpoint] = {}
|
|
189
|
+
if method not in WebEngine.METHODS:
|
|
190
|
+
raise ValueError(f"method must be one of {WebEngine.METHODS}")
|
|
192
191
|
WebEngine.ENDPOINTS[endpoint][method] = callback
|
|
193
192
|
return
|
|
194
193
|
# AUTO ENABLE webui when register (endpoint) called and webui is False
|
micrOS/source/Shell.py
CHANGED
micrOS/source/Tasks.py
CHANGED
|
@@ -43,6 +43,20 @@ class TaskBase:
|
|
|
43
43
|
self.done = asyncio.Event() # Store task done state
|
|
44
44
|
self.out = "" # Store task output
|
|
45
45
|
|
|
46
|
+
def _create(self, callback:callable) -> bool:
|
|
47
|
+
"""
|
|
48
|
+
Create async task and register it to TASKS dict by tag
|
|
49
|
+
:param callback: coroutine function
|
|
50
|
+
"""
|
|
51
|
+
if TaskBase.is_busy(self.tag):
|
|
52
|
+
# Skip task if already running
|
|
53
|
+
return False
|
|
54
|
+
# Create async task from coroutine function
|
|
55
|
+
self.task = asyncio.get_event_loop().create_task(callback)
|
|
56
|
+
# Store Task object by key - for task control
|
|
57
|
+
TaskBase.TASKS[self.tag] = self
|
|
58
|
+
return True
|
|
59
|
+
|
|
46
60
|
@staticmethod
|
|
47
61
|
def is_busy(tag:str) -> bool:
|
|
48
62
|
"""
|
|
@@ -50,7 +64,7 @@ class TaskBase:
|
|
|
50
64
|
:param tag: for task selection
|
|
51
65
|
"""
|
|
52
66
|
task = TaskBase.TASKS.get(tag, None)
|
|
53
|
-
# return True: busy OR False: not busy (inactive)
|
|
67
|
+
# return True: busy OR False: not busy (inactive) OR None: not exists
|
|
54
68
|
return bool(task is not None and not task.done.is_set())
|
|
55
69
|
|
|
56
70
|
@staticmethod
|
|
@@ -138,15 +152,8 @@ class NativeTask(TaskBase):
|
|
|
138
152
|
"""
|
|
139
153
|
# Create task tag
|
|
140
154
|
self.tag = f"aio.{ticks_ms()}" if tag is None else tag
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
return False
|
|
144
|
-
|
|
145
|
-
# Start task with coroutine callback
|
|
146
|
-
self.task = asyncio.get_event_loop().create_task(callback)
|
|
147
|
-
# Store Task object by key - for task control
|
|
148
|
-
TaskBase.TASKS[self.tag] = self
|
|
149
|
-
return True
|
|
155
|
+
# Create task with coroutine callback
|
|
156
|
+
return super()._create(callback)
|
|
150
157
|
|
|
151
158
|
def __enter__(self):
|
|
152
159
|
"""
|
|
@@ -189,9 +196,6 @@ class MagicTask(TaskBase):
|
|
|
189
196
|
"""
|
|
190
197
|
# Create task tag
|
|
191
198
|
self.tag = '.'.join(callback[0:2])
|
|
192
|
-
if TaskBase.is_busy(self.tag):
|
|
193
|
-
# Skip task if already running
|
|
194
|
-
return False
|
|
195
199
|
|
|
196
200
|
# Set parameters for async wrapper
|
|
197
201
|
self.__callback = callback
|
|
@@ -199,10 +203,8 @@ class MagicTask(TaskBase):
|
|
|
199
203
|
# Set sleep value for async loop - optional parameter with min sleep limit check (20ms)
|
|
200
204
|
self.__sleep = self.__sleep if sleep is None else sleep if sleep > 19 else self.__sleep
|
|
201
205
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
TaskBase.TASKS[self.tag] = self
|
|
205
|
-
return True
|
|
206
|
+
# Create task with coroutine callback
|
|
207
|
+
return super()._create(self.__task_wrapper())
|
|
206
208
|
|
|
207
209
|
async def __task_wrapper(self):
|
|
208
210
|
"""
|
|
@@ -373,7 +375,7 @@ class Manager:
|
|
|
373
375
|
return '\n'.join(output)
|
|
374
376
|
|
|
375
377
|
@staticmethod
|
|
376
|
-
def kill(tag):
|
|
378
|
+
def kill(tag:str) -> (bool, str):
|
|
377
379
|
"""
|
|
378
380
|
Primary interface
|
|
379
381
|
Kill/terminate async task
|
|
@@ -474,11 +476,11 @@ def exec_builtins(func):
|
|
|
474
476
|
if arg_len > 1 and 'list' == arg_list[1]:
|
|
475
477
|
on, off = Manager.list_tasks(json=json_flag)
|
|
476
478
|
# RETURN: JSON mode Human readable mode with cpu & queue info
|
|
477
|
-
return (True, {'active': on[3:], 'inactive': off}) if json_flag else (True, '\n'.join(on) + '\n' + '\n'.join(off) + '\n')
|
|
479
|
+
return (True, dumps({'active': on[3:], 'inactive': off})) if json_flag else (True, '\n'.join(on) + '\n' + '\n'.join(off) + '\n')
|
|
478
480
|
# task kill <taskID> / task show <taskID>
|
|
479
481
|
if arg_len > 2:
|
|
480
482
|
if 'kill' == arg_list[1]:
|
|
481
|
-
|
|
483
|
+
_, msg = Manager.kill(tag=arg_list[2])
|
|
482
484
|
return True, msg
|
|
483
485
|
if 'show' == arg_list[1]:
|
|
484
486
|
return True, Manager.show(tag=arg_list[2])
|
micrOS/source/micrOSloader.py
CHANGED
|
@@ -102,7 +102,7 @@ def __auto_restart_event():
|
|
|
102
102
|
if trigger_is_active and _is_micrOS():
|
|
103
103
|
print("[loader][ota-rebooter][micros][trigger: True] OTA was finished - reboot")
|
|
104
104
|
# Create cleanup indicator file for ConfigHandler
|
|
105
|
-
with open('cleanup
|
|
105
|
+
with open('.cleanup', 'w') as f:
|
|
106
106
|
f.write('')
|
|
107
107
|
# Reboot machine
|
|
108
108
|
reset()
|
|
@@ -17,6 +17,13 @@ def stats():
|
|
|
17
17
|
return ESPNOW.stats()
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
def members():
|
|
21
|
+
"""
|
|
22
|
+
Get ESPNow devices
|
|
23
|
+
"""
|
|
24
|
+
return ESPNOW.members()
|
|
25
|
+
|
|
26
|
+
|
|
20
27
|
def handshake(peer:bytes|str):
|
|
21
28
|
"""
|
|
22
29
|
Handshake with ESPNow Peer
|
|
@@ -27,10 +34,20 @@ def handshake(peer:bytes|str):
|
|
|
27
34
|
return ESPNOW.handshake(peer)
|
|
28
35
|
|
|
29
36
|
|
|
37
|
+
def remove(peer:bytes):
|
|
38
|
+
"""
|
|
39
|
+
Remove peer by binary mac address
|
|
40
|
+
:param peer: binary mac address of espnow device
|
|
41
|
+
"""
|
|
42
|
+
return ESPNOW.remove_peer(peer)
|
|
43
|
+
|
|
44
|
+
|
|
30
45
|
def help():
|
|
31
46
|
"""
|
|
32
47
|
ESPNOW sender/receiver with LM execution
|
|
33
48
|
"""
|
|
34
49
|
return ('handshake peer=<mac-address>',
|
|
35
50
|
'send peer=<peer-name> cmd="hello"',
|
|
36
|
-
'
|
|
51
|
+
'remove peer=<binary-mac-address>',
|
|
52
|
+
'stats',
|
|
53
|
+
'members')
|
|
@@ -277,7 +277,7 @@ def help(widgets=False):
|
|
|
277
277
|
'BUTTON fire speed_ms=150',
|
|
278
278
|
'BUTTON stop',
|
|
279
279
|
'shader offset=0 size=6',
|
|
280
|
-
'control speed_ms
|
|
280
|
+
'SLIDER control speed_ms=<1-200> batch=None',
|
|
281
281
|
'random max_val=255',
|
|
282
282
|
'pinmap',
|
|
283
283
|
'COLOR color r=<0-255-10> g b'
|
|
@@ -430,7 +430,7 @@ def help(widgets=False):
|
|
|
430
430
|
'BUTTON rainbow',
|
|
431
431
|
'BUTTON spiral speed_ms=40',
|
|
432
432
|
'BUTTON noise speed_ms=85',
|
|
433
|
-
'SLIDER control speed_ms=<1-200
|
|
433
|
+
'SLIDER control speed_ms=<1-200> bt_draw=None',
|
|
434
434
|
'draw_colormap bitmap=[(0,0,(10,2,0)),(x,y,color),...]',
|
|
435
435
|
'get_colormap',
|
|
436
436
|
'status'
|
|
@@ -343,9 +343,10 @@ class PageUI:
|
|
|
343
343
|
# Send CMD to other device & show result
|
|
344
344
|
state, data_meta = exec_cmd(cmd + [f">>{host}"], jsonify=True)
|
|
345
345
|
if state:
|
|
346
|
-
self.cmd_task_tag = data_meta[
|
|
347
|
-
|
|
348
|
-
|
|
346
|
+
self.cmd_task_tag = list(data_meta.keys())[0]
|
|
347
|
+
verdict = list(data_meta.values())[0]
|
|
348
|
+
if "Already running" in verdict and not run:
|
|
349
|
+
self.cmd_out = verdict # Otherwise the task start output not relevant on UI
|
|
349
350
|
else:
|
|
350
351
|
self.cmd_out = f"Error: {data_meta}"
|
|
351
352
|
except Exception as e:
|
|
@@ -782,9 +782,10 @@ class PageUI:
|
|
|
782
782
|
# Send CMD to other device & show result
|
|
783
783
|
state, data_meta = exec_cmd(cmd + [f">>{host}"], jsonify=True)
|
|
784
784
|
if state:
|
|
785
|
-
self._cmd_task_tag = data_meta[
|
|
786
|
-
|
|
787
|
-
|
|
785
|
+
self._cmd_task_tag = list(data_meta.keys())[0]
|
|
786
|
+
verdict = list(data_meta.values())[0]
|
|
787
|
+
if "Already running" in verdict and not run:
|
|
788
|
+
self.app_frame.press_output = verdict # Otherwise the task start output not relevant on UI
|
|
788
789
|
else:
|
|
789
790
|
self.app_frame.press_output = f"Error: {data_meta}"
|
|
790
791
|
except Exception as e:
|