micrOSDevToolKit 2.17.2__py3-none-any.whl → 2.20.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 +29 -33
- micrOS/source/Common.py +5 -13
- micrOS/source/Config.py +2 -2
- micrOS/source/Espnow.py +32 -18
- micrOS/source/InterConnect.py +106 -31
- micrOS/source/Server.py +2 -3
- micrOS/source/Shell.py +1 -1
- micrOS/source/Tasks.py +66 -62
- micrOS/source/micrOSloader.py +1 -1
- micrOS/source/modules/LM_buzzer.py +1 -4
- micrOS/source/modules/LM_cct.py +2 -4
- micrOS/source/modules/LM_dimmer.py +1 -2
- micrOS/source/modules/LM_distance.py +1 -3
- micrOS/source/modules/LM_espnow.py +18 -1
- micrOS/source/modules/LM_i2s_mic.py +1 -2
- micrOS/source/modules/LM_keychain.py +1 -2
- micrOS/source/modules/LM_light_sensor.py +1 -4
- micrOS/source/modules/LM_mqtt_client.py +13 -10
- micrOS/source/modules/LM_neoeffects.py +1 -1
- micrOS/source/modules/LM_neomatrix.py +1 -1
- micrOS/source/modules/LM_neopixel.py +1 -2
- micrOS/source/modules/LM_oled_ui.py +4 -3
- micrOS/source/modules/LM_oledui.py +6 -7
- micrOS/source/modules/LM_presence.py +1 -2
- micrOS/source/modules/LM_rest.py +1 -2
- micrOS/source/modules/LM_rgb.py +1 -2
- micrOS/source/modules/LM_roboarm.py +3 -4
- micrOS/source/modules/LM_robustness.py +1 -2
- micrOS/source/modules/LM_tcs3472.py +131 -17
- micrOS/source/modules/LM_telegram.py +17 -18
- {microsdevtoolkit-2.17.2.dist-info → microsdevtoolkit-2.20.0.dist-info}/METADATA +151 -215
- {microsdevtoolkit-2.17.2.dist-info → microsdevtoolkit-2.20.0.dist-info}/RECORD +69 -73
- toolkit/Gateway.py +1 -1
- toolkit/dashboard_apps/SystemTest.py +39 -32
- toolkit/micrOSdashboard.py +8 -13
- toolkit/socketClient.py +27 -7
- toolkit/workspace/precompiled/Common.mpy +0 -0
- 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_buzzer.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_cct.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_dimmer.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_distance.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_espnow.py +18 -1
- toolkit/workspace/precompiled/modules/LM_i2s_mic.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_keychain.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_light_sensor.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_mqtt_client.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_neoeffects.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_neomatrix.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_neopixel.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_presence.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_rest.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_rgb.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_roboarm.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_robustness.py +1 -2
- 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
- micrOS/source/modules/LM_pet_feeder.py +0 -78
- toolkit/workspace/precompiled/modules/LM_pet_feeder.py +0 -78
- {microsdevtoolkit-2.17.2.data → microsdevtoolkit-2.20.0.data}/scripts/devToolKit.py +0 -0
- {microsdevtoolkit-2.17.2.dist-info → microsdevtoolkit-2.20.0.dist-info}/WHEEL +0 -0
- {microsdevtoolkit-2.17.2.dist-info → microsdevtoolkit-2.20.0.dist-info}/licenses/LICENSE +0 -0
- {microsdevtoolkit-2.17.2.dist-info → microsdevtoolkit-2.20.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.48,
|
|
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,
|
|
@@ -54,10 +54,10 @@
|
|
|
54
54
|
],
|
|
55
55
|
"Common.py": [
|
|
56
56
|
9.85,
|
|
57
|
-
|
|
57
|
+
34
|
|
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.47,
|
|
73
|
+
4
|
|
74
74
|
],
|
|
75
75
|
"Scheduler.py": [
|
|
76
76
|
9.62,
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
5
|
|
98
98
|
],
|
|
99
99
|
"modules/LM_roboarm.py": [
|
|
100
|
-
7.
|
|
100
|
+
7.83,
|
|
101
101
|
0
|
|
102
102
|
],
|
|
103
103
|
"modules/LM_stepper.py": [
|
|
@@ -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": [
|
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
0
|
|
126
126
|
],
|
|
127
127
|
"modules/LM_robustness.py": [
|
|
128
|
-
8.
|
|
128
|
+
8.33,
|
|
129
129
|
0
|
|
130
130
|
],
|
|
131
131
|
"modules/LM_qmi8658.py": [
|
|
@@ -137,11 +137,11 @@
|
|
|
137
137
|
0
|
|
138
138
|
],
|
|
139
139
|
"modules/LM_rest.py": [
|
|
140
|
-
8.
|
|
140
|
+
8.6,
|
|
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": [
|
|
@@ -161,7 +161,7 @@
|
|
|
161
161
|
0
|
|
162
162
|
],
|
|
163
163
|
"modules/LM_buzzer.py": [
|
|
164
|
-
9.
|
|
164
|
+
9.09,
|
|
165
165
|
0
|
|
166
166
|
],
|
|
167
167
|
"modules/LM_switch.py": [
|
|
@@ -189,11 +189,11 @@
|
|
|
189
189
|
0
|
|
190
190
|
],
|
|
191
191
|
"modules/LM_neopixel.py": [
|
|
192
|
-
7.
|
|
192
|
+
7.77,
|
|
193
193
|
0
|
|
194
194
|
],
|
|
195
195
|
"modules/LM_cct.py": [
|
|
196
|
-
9.
|
|
196
|
+
9.03,
|
|
197
197
|
0
|
|
198
198
|
],
|
|
199
199
|
"modules/LM_L9110_DCmotor.py": [
|
|
@@ -220,10 +220,6 @@
|
|
|
220
220
|
7.62,
|
|
221
221
|
0
|
|
222
222
|
],
|
|
223
|
-
"modules/LM_pet_feeder.py": [
|
|
224
|
-
7.94,
|
|
225
|
-
0
|
|
226
|
-
],
|
|
227
223
|
"modules/LM_rencoder.py": [
|
|
228
224
|
8.65,
|
|
229
225
|
0
|
|
@@ -245,15 +241,15 @@
|
|
|
245
241
|
0
|
|
246
242
|
],
|
|
247
243
|
"modules/LM_oledui.py": [
|
|
248
|
-
8.
|
|
244
|
+
8.94,
|
|
249
245
|
0
|
|
250
246
|
],
|
|
251
247
|
"modules/LM_espnow.py": [
|
|
252
|
-
|
|
248
|
+
7.14,
|
|
253
249
|
0
|
|
254
250
|
],
|
|
255
251
|
"modules/LM_telegram.py": [
|
|
256
|
-
9.
|
|
252
|
+
9.62,
|
|
257
253
|
0
|
|
258
254
|
],
|
|
259
255
|
"modules/LM_OV2640.py": [
|
|
@@ -265,7 +261,7 @@
|
|
|
265
261
|
0
|
|
266
262
|
],
|
|
267
263
|
"modules/LM_distance.py": [
|
|
268
|
-
8.
|
|
264
|
+
8.86,
|
|
269
265
|
0
|
|
270
266
|
],
|
|
271
267
|
"modules/LM_VL53L0X.py": [
|
|
@@ -273,7 +269,7 @@
|
|
|
273
269
|
0
|
|
274
270
|
],
|
|
275
271
|
"modules/LM_light_sensor.py": [
|
|
276
|
-
9.
|
|
272
|
+
9.12,
|
|
277
273
|
0
|
|
278
274
|
],
|
|
279
275
|
"modules/LM_rp2w.py": [
|
|
@@ -281,7 +277,7 @@
|
|
|
281
277
|
0
|
|
282
278
|
],
|
|
283
279
|
"modules/LM_presence.py": [
|
|
284
|
-
8.
|
|
280
|
+
8.89,
|
|
285
281
|
0
|
|
286
282
|
],
|
|
287
283
|
"modules/LM_trackball.py": [
|
|
@@ -289,7 +285,7 @@
|
|
|
289
285
|
0
|
|
290
286
|
],
|
|
291
287
|
"modules/LM_mqtt_client.py": [
|
|
292
|
-
8.
|
|
288
|
+
8.68,
|
|
293
289
|
0
|
|
294
290
|
],
|
|
295
291
|
"modules/LM_dashboard_be.py": [
|
|
@@ -297,7 +293,7 @@
|
|
|
297
293
|
0
|
|
298
294
|
],
|
|
299
295
|
"modules/LM_dimmer.py": [
|
|
300
|
-
8.
|
|
296
|
+
8.32,
|
|
301
297
|
0
|
|
302
298
|
],
|
|
303
299
|
"modules/LM_gameOfLife.py": [
|
|
@@ -309,7 +305,7 @@
|
|
|
309
305
|
0
|
|
310
306
|
],
|
|
311
307
|
"modules/LM_i2s_mic.py": [
|
|
312
|
-
8.
|
|
308
|
+
8.42,
|
|
313
309
|
0
|
|
314
310
|
],
|
|
315
311
|
"modules/LM_sdcard.py": [
|
|
@@ -323,12 +319,12 @@
|
|
|
323
319
|
},
|
|
324
320
|
"summary": {
|
|
325
321
|
"core": [
|
|
326
|
-
|
|
322
|
+
3994,
|
|
327
323
|
24
|
|
328
324
|
],
|
|
329
325
|
"load": [
|
|
330
|
-
|
|
331
|
-
|
|
326
|
+
9912,
|
|
327
|
+
55
|
|
332
328
|
],
|
|
333
329
|
"core_dep": [
|
|
334
330
|
true,
|
|
@@ -338,8 +334,8 @@
|
|
|
338
334
|
true,
|
|
339
335
|
4
|
|
340
336
|
],
|
|
341
|
-
"core_score": 9.
|
|
342
|
-
"load_score": 8.
|
|
343
|
-
"version": "2.
|
|
337
|
+
"core_score": 9.25,
|
|
338
|
+
"load_score": 8.42,
|
|
339
|
+
"version": "2.20.0-0"
|
|
344
340
|
}
|
|
345
341
|
}
|
micrOS/source/Common.py
CHANGED
|
@@ -15,7 +15,7 @@ from Notify import Notify
|
|
|
15
15
|
#####################################################################################
|
|
16
16
|
# SYSTEM #
|
|
17
17
|
#####################################################################################
|
|
18
|
-
def micro_task(tag:
|
|
18
|
+
def micro_task(tag:str, task=None, _wrap=False):
|
|
19
19
|
"""
|
|
20
20
|
[LM] Async task manager.
|
|
21
21
|
Modes:
|
|
@@ -37,21 +37,14 @@ def micro_task(tag: str, task=None, _wrap=False):
|
|
|
37
37
|
"""
|
|
38
38
|
# --- CREATE (original) ---
|
|
39
39
|
if task is not None:
|
|
40
|
-
if TaskBase.is_busy(tag):
|
|
41
|
-
return None # task already running
|
|
42
40
|
return Manager().create_task(callback=task, tag=tag)
|
|
43
41
|
|
|
44
42
|
# --- CREATE WITH DECORATOR FACTORY (simplified) ---
|
|
45
43
|
if _wrap:
|
|
46
44
|
def _decorator(async_fn):
|
|
47
45
|
task_tag = f"{tag}._{async_fn.__name__}"
|
|
48
|
-
_launcher = (
|
|
49
|
-
|
|
50
|
-
None if TaskBase.is_busy(task_tag)
|
|
51
|
-
else Manager().create_task(
|
|
52
|
-
callback=async_fn(task_tag, *args, **kwargs),
|
|
53
|
-
tag=task_tag)
|
|
54
|
-
)
|
|
46
|
+
_launcher = (lambda *args, **kwargs: Manager().create_task(callback=async_fn(task_tag, *args, **kwargs),
|
|
47
|
+
tag=task_tag))
|
|
55
48
|
return _launcher
|
|
56
49
|
return _decorator
|
|
57
50
|
|
|
@@ -404,9 +397,8 @@ class AnimationPlayer:
|
|
|
404
397
|
# Ensure async loop set up correctly. (After stop operation, it is needed)
|
|
405
398
|
self.__running = True
|
|
406
399
|
# [!] ASYNC TASK CREATION
|
|
407
|
-
|
|
408
|
-
state =
|
|
409
|
-
settings["state"] = state
|
|
400
|
+
state:dict = micro_task(tag=self._task_tag, task=self._player())
|
|
401
|
+
settings["state"] = list(state.values())[0]
|
|
410
402
|
return settings
|
|
411
403
|
|
|
412
404
|
def stop(self):
|
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:
|
|
@@ -201,8 +201,7 @@ class ESPNowSS:
|
|
|
201
201
|
"""
|
|
202
202
|
# Create an asynchronous task with tag 'espnow.server'
|
|
203
203
|
tag = 'espnow.server'
|
|
204
|
-
|
|
205
|
-
return {tag: "Starting"} if state else {tag: "Already running"}
|
|
204
|
+
return NativeTask().create(callback=self._server(tag), tag=tag)
|
|
206
205
|
|
|
207
206
|
#----------- SEND METHODS --------------
|
|
208
207
|
async def __asend_raw(self, mac:bytes, msg:str):
|
|
@@ -249,8 +248,7 @@ class ESPNowSS:
|
|
|
249
248
|
peer_name = hexlify(peer, ':').decode() if peer_name is None else peer_name
|
|
250
249
|
task_id = f"con.espnow.{peer_name}"
|
|
251
250
|
# Create an asynchronous sending task.
|
|
252
|
-
|
|
253
|
-
return {task_id: "Starting"} if state else {task_id: "Already running"}
|
|
251
|
+
return NativeTask().create(callback=self._asend_task(peer, task_id, msg), tag=task_id)
|
|
254
252
|
|
|
255
253
|
def cluster_send(self, msg):
|
|
256
254
|
"""
|
|
@@ -262,6 +260,14 @@ class ESPNowSS:
|
|
|
262
260
|
return _tasks
|
|
263
261
|
|
|
264
262
|
# ----------- OTHER METHODS --------------
|
|
263
|
+
def save_peers(self):
|
|
264
|
+
try:
|
|
265
|
+
with open(self.peer_cache_path, "w") as f:
|
|
266
|
+
dump([[list(k), v] for k, v in self.devices.items()], f)
|
|
267
|
+
return True
|
|
268
|
+
except Exception as e:
|
|
269
|
+
syslog(f"[ERR][ESPNOW] Saving peers: {e}")
|
|
270
|
+
return False
|
|
265
271
|
|
|
266
272
|
async def _handshake(self, peer:bytes, tag:str):
|
|
267
273
|
"""
|
|
@@ -285,16 +291,10 @@ class ESPNowSS:
|
|
|
285
291
|
expected_response = f"hello {self.devfid}"
|
|
286
292
|
is_ok = False
|
|
287
293
|
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}")
|
|
294
|
+
is_ok = self.save_peers()
|
|
294
295
|
my_task.out = f"Handshake: {result} from {self.devices.get(peer)} [{'OK' if is_ok else 'NOK'}]"
|
|
295
296
|
sender_task.cancel() # Delete sender task (cleanup)
|
|
296
297
|
|
|
297
|
-
|
|
298
298
|
def handshake(self, peer:bytes|str):
|
|
299
299
|
task_id = f"con.espnow.handshake"
|
|
300
300
|
# Create an asynchronous sending task.
|
|
@@ -302,8 +302,7 @@ class ESPNowSS:
|
|
|
302
302
|
# Convert 50:02:91:86:34:28 format to b'P\x02\x91\x864(' bytes
|
|
303
303
|
peer = bytes(int(x, 16) for x in peer.split(":"))
|
|
304
304
|
if isinstance(peer, bytes):
|
|
305
|
-
|
|
306
|
-
return {task_id: "Starting"} if state else {task_id: "Already running"}
|
|
305
|
+
return NativeTask().create(callback=self._handshake(peer, task_id), tag=task_id)
|
|
307
306
|
else:
|
|
308
307
|
return {None: "Invalid MAC address format. Use 50:02:91:86:34:28 or b'P\\x02\\x91\\x864('"}
|
|
309
308
|
|
|
@@ -321,4 +320,19 @@ class ESPNowSS:
|
|
|
321
320
|
_peers = self.espnow.peers_table
|
|
322
321
|
except Exception as e:
|
|
323
322
|
_peers = str(e)
|
|
324
|
-
return {"stats": _stats, "peers": _peers, "
|
|
323
|
+
return {"stats": _stats, "peers": _peers, "ready": self.server_ready}
|
|
324
|
+
|
|
325
|
+
def members(self):
|
|
326
|
+
return self.devices
|
|
327
|
+
|
|
328
|
+
def remove_peer(self, peer:bytes) -> bool:
|
|
329
|
+
"""
|
|
330
|
+
Remove peer from ESPNow devices
|
|
331
|
+
:param peer: MAC address as bytes to remove
|
|
332
|
+
"""
|
|
333
|
+
if isinstance(peer, bytes):
|
|
334
|
+
if self.devices.pop(peer, None) is not None:
|
|
335
|
+
self.save_peers()
|
|
336
|
+
self.espnow.del_peer(peer)
|
|
337
|
+
return True
|
|
338
|
+
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,9 @@ 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 started:
|
|
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
|
+
return com_obj.task.create(callback=_send_cmd(host, cmd, com_obj), tag=task_id)
|
|
186
262
|
|
|
187
263
|
|
|
188
264
|
def host_cache() -> dict:
|
|
@@ -190,4 +266,3 @@ def host_cache() -> dict:
|
|
|
190
266
|
Dump InterCon connection cache
|
|
191
267
|
"""
|
|
192
268
|
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
|