micrOSDevToolKit 2.19.0__py3-none-any.whl → 2.21.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 +35 -35
- micrOS/source/Common.py +5 -13
- micrOS/source/Config.py +5 -2
- micrOS/source/Espnow.py +101 -45
- micrOS/source/Files.py +50 -23
- micrOS/source/InterConnect.py +10 -5
- micrOS/source/Pacman.py +141 -0
- micrOS/source/Shell.py +1 -1
- micrOS/source/Tasks.py +59 -54
- 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_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_neopixel.py +1 -2
- micrOS/source/modules/LM_oled_ui.py +39 -41
- micrOS/source/modules/LM_oledui.py +58 -89
- micrOS/source/modules/LM_pacman.py +36 -44
- 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_telegram.py +1 -2
- micrOS/source/urequests.py +1 -1
- {microsdevtoolkit-2.19.0.dist-info → microsdevtoolkit-2.21.0.dist-info}/METADATA +153 -214
- {microsdevtoolkit-2.19.0.dist-info → microsdevtoolkit-2.21.0.dist-info}/RECORD +67 -67
- toolkit/DevEnvOTA.py +2 -2
- toolkit/DevEnvUSB.py +1 -1
- toolkit/dashboard_apps/SystemTest.py +17 -14
- toolkit/lib/micrOSClient.py +37 -15
- toolkit/lib/micrOSClientHistory.py +35 -1
- toolkit/simulator_lib/__pycache__/uos.cpython-312.pyc +0 -0
- toolkit/simulator_lib/uos.py +1 -0
- toolkit/workspace/precompiled/Common.mpy +0 -0
- toolkit/workspace/precompiled/Config.mpy +0 -0
- toolkit/workspace/precompiled/Espnow.mpy +0 -0
- toolkit/workspace/precompiled/Files.mpy +0 -0
- toolkit/workspace/precompiled/InterConnect.mpy +0 -0
- toolkit/workspace/precompiled/Pacman.mpy +0 -0
- toolkit/workspace/precompiled/Shell.mpy +0 -0
- toolkit/workspace/precompiled/Tasks.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_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_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_pacman.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_telegram.mpy +0 -0
- micrOS/source/modules/LM_pet_feeder.py +0 -78
- toolkit/workspace/precompiled/modules/LM_pet_feeder.py +0 -78
- {microsdevtoolkit-2.19.0.data → microsdevtoolkit-2.21.0.data}/scripts/devToolKit.py +0 -0
- {microsdevtoolkit-2.19.0.dist-info → microsdevtoolkit-2.21.0.dist-info}/WHEEL +0 -0
- {microsdevtoolkit-2.19.0.dist-info → microsdevtoolkit-2.21.0.dist-info}/licenses/LICENSE +0 -0
- {microsdevtoolkit-2.19.0.dist-info → microsdevtoolkit-2.21.0.dist-info}/top_level.txt +0 -0
|
@@ -4,9 +4,13 @@
|
|
|
4
4
|
9.34,
|
|
5
5
|
6
|
|
6
6
|
],
|
|
7
|
+
"Pacman.py": [
|
|
8
|
+
9.75,
|
|
9
|
+
1
|
|
10
|
+
],
|
|
7
11
|
"Files.py": [
|
|
8
|
-
9.
|
|
9
|
-
|
|
12
|
+
9.68,
|
|
13
|
+
11
|
|
10
14
|
],
|
|
11
15
|
"micrOSloader.py": [
|
|
12
16
|
7.06,
|
|
@@ -25,11 +29,11 @@
|
|
|
25
29
|
1
|
|
26
30
|
],
|
|
27
31
|
"Tasks.py": [
|
|
28
|
-
9.
|
|
32
|
+
9.48,
|
|
29
33
|
12
|
|
30
34
|
],
|
|
31
35
|
"Config.py": [
|
|
32
|
-
9.
|
|
36
|
+
9.44,
|
|
33
37
|
19
|
|
34
38
|
],
|
|
35
39
|
"reset.py": [
|
|
@@ -54,22 +58,22 @@
|
|
|
54
58
|
],
|
|
55
59
|
"Common.py": [
|
|
56
60
|
9.85,
|
|
57
|
-
|
|
61
|
+
34
|
|
58
62
|
],
|
|
59
63
|
"InterConnect.py": [
|
|
60
|
-
9.
|
|
64
|
+
9.63,
|
|
61
65
|
2
|
|
62
66
|
],
|
|
63
67
|
"Debug.py": [
|
|
64
68
|
7.37,
|
|
65
|
-
|
|
69
|
+
18
|
|
66
70
|
],
|
|
67
71
|
"Network.py": [
|
|
68
72
|
9.81,
|
|
69
73
|
8
|
|
70
74
|
],
|
|
71
75
|
"Espnow.py": [
|
|
72
|
-
9.
|
|
76
|
+
9.87,
|
|
73
77
|
4
|
|
74
78
|
],
|
|
75
79
|
"Scheduler.py": [
|
|
@@ -97,7 +101,7 @@
|
|
|
97
101
|
5
|
|
98
102
|
],
|
|
99
103
|
"modules/LM_roboarm.py": [
|
|
100
|
-
7.
|
|
104
|
+
7.83,
|
|
101
105
|
0
|
|
102
106
|
],
|
|
103
107
|
"modules/LM_stepper.py": [
|
|
@@ -109,7 +113,7 @@
|
|
|
109
113
|
0
|
|
110
114
|
],
|
|
111
115
|
"modules/LM_pacman.py": [
|
|
112
|
-
7.
|
|
116
|
+
7.61,
|
|
113
117
|
0
|
|
114
118
|
],
|
|
115
119
|
"modules/LM_genIO.py": [
|
|
@@ -117,7 +121,7 @@
|
|
|
117
121
|
0
|
|
118
122
|
],
|
|
119
123
|
"modules/LM_oled_ui.py": [
|
|
120
|
-
9.
|
|
124
|
+
9.22,
|
|
121
125
|
0
|
|
122
126
|
],
|
|
123
127
|
"modules/LM_system.py": [
|
|
@@ -125,7 +129,7 @@
|
|
|
125
129
|
0
|
|
126
130
|
],
|
|
127
131
|
"modules/LM_robustness.py": [
|
|
128
|
-
8.
|
|
132
|
+
8.33,
|
|
129
133
|
0
|
|
130
134
|
],
|
|
131
135
|
"modules/LM_qmi8658.py": [
|
|
@@ -137,7 +141,7 @@
|
|
|
137
141
|
0
|
|
138
142
|
],
|
|
139
143
|
"modules/LM_rest.py": [
|
|
140
|
-
8.
|
|
144
|
+
8.6,
|
|
141
145
|
0
|
|
142
146
|
],
|
|
143
147
|
"modules/LM_tcs3472.py": [
|
|
@@ -161,7 +165,7 @@
|
|
|
161
165
|
0
|
|
162
166
|
],
|
|
163
167
|
"modules/LM_buzzer.py": [
|
|
164
|
-
9.
|
|
168
|
+
9.09,
|
|
165
169
|
0
|
|
166
170
|
],
|
|
167
171
|
"modules/LM_switch.py": [
|
|
@@ -189,11 +193,11 @@
|
|
|
189
193
|
0
|
|
190
194
|
],
|
|
191
195
|
"modules/LM_neopixel.py": [
|
|
192
|
-
7.
|
|
196
|
+
7.77,
|
|
193
197
|
0
|
|
194
198
|
],
|
|
195
199
|
"modules/LM_cct.py": [
|
|
196
|
-
9.
|
|
200
|
+
9.03,
|
|
197
201
|
0
|
|
198
202
|
],
|
|
199
203
|
"modules/LM_L9110_DCmotor.py": [
|
|
@@ -220,10 +224,6 @@
|
|
|
220
224
|
7.62,
|
|
221
225
|
0
|
|
222
226
|
],
|
|
223
|
-
"modules/LM_pet_feeder.py": [
|
|
224
|
-
7.94,
|
|
225
|
-
0
|
|
226
|
-
],
|
|
227
227
|
"modules/LM_rencoder.py": [
|
|
228
228
|
8.65,
|
|
229
229
|
0
|
|
@@ -245,7 +245,7 @@
|
|
|
245
245
|
0
|
|
246
246
|
],
|
|
247
247
|
"modules/LM_oledui.py": [
|
|
248
|
-
|
|
248
|
+
9.05,
|
|
249
249
|
0
|
|
250
250
|
],
|
|
251
251
|
"modules/LM_espnow.py": [
|
|
@@ -253,7 +253,7 @@
|
|
|
253
253
|
0
|
|
254
254
|
],
|
|
255
255
|
"modules/LM_telegram.py": [
|
|
256
|
-
9.
|
|
256
|
+
9.62,
|
|
257
257
|
0
|
|
258
258
|
],
|
|
259
259
|
"modules/LM_OV2640.py": [
|
|
@@ -265,7 +265,7 @@
|
|
|
265
265
|
0
|
|
266
266
|
],
|
|
267
267
|
"modules/LM_distance.py": [
|
|
268
|
-
8.
|
|
268
|
+
8.86,
|
|
269
269
|
0
|
|
270
270
|
],
|
|
271
271
|
"modules/LM_VL53L0X.py": [
|
|
@@ -273,7 +273,7 @@
|
|
|
273
273
|
0
|
|
274
274
|
],
|
|
275
275
|
"modules/LM_light_sensor.py": [
|
|
276
|
-
9.
|
|
276
|
+
9.12,
|
|
277
277
|
0
|
|
278
278
|
],
|
|
279
279
|
"modules/LM_rp2w.py": [
|
|
@@ -281,7 +281,7 @@
|
|
|
281
281
|
0
|
|
282
282
|
],
|
|
283
283
|
"modules/LM_presence.py": [
|
|
284
|
-
8.
|
|
284
|
+
8.89,
|
|
285
285
|
0
|
|
286
286
|
],
|
|
287
287
|
"modules/LM_trackball.py": [
|
|
@@ -289,7 +289,7 @@
|
|
|
289
289
|
0
|
|
290
290
|
],
|
|
291
291
|
"modules/LM_mqtt_client.py": [
|
|
292
|
-
8.
|
|
292
|
+
8.68,
|
|
293
293
|
0
|
|
294
294
|
],
|
|
295
295
|
"modules/LM_dashboard_be.py": [
|
|
@@ -297,7 +297,7 @@
|
|
|
297
297
|
0
|
|
298
298
|
],
|
|
299
299
|
"modules/LM_dimmer.py": [
|
|
300
|
-
8.
|
|
300
|
+
8.32,
|
|
301
301
|
0
|
|
302
302
|
],
|
|
303
303
|
"modules/LM_gameOfLife.py": [
|
|
@@ -309,7 +309,7 @@
|
|
|
309
309
|
0
|
|
310
310
|
],
|
|
311
311
|
"modules/LM_i2s_mic.py": [
|
|
312
|
-
8.
|
|
312
|
+
8.42,
|
|
313
313
|
0
|
|
314
314
|
],
|
|
315
315
|
"modules/LM_sdcard.py": [
|
|
@@ -323,12 +323,12 @@
|
|
|
323
323
|
},
|
|
324
324
|
"summary": {
|
|
325
325
|
"core": [
|
|
326
|
-
|
|
327
|
-
|
|
326
|
+
4171,
|
|
327
|
+
25
|
|
328
328
|
],
|
|
329
329
|
"load": [
|
|
330
|
-
|
|
331
|
-
|
|
330
|
+
9869,
|
|
331
|
+
55
|
|
332
332
|
],
|
|
333
333
|
"core_dep": [
|
|
334
334
|
true,
|
|
@@ -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.29,
|
|
342
|
+
"load_score": 8.41,
|
|
343
|
+
"version": "2.21.0-0"
|
|
344
344
|
}
|
|
345
345
|
}
|
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
|
@@ -84,8 +84,11 @@ class Data:
|
|
|
84
84
|
Data.__inject_default_conf()
|
|
85
85
|
# [!!!] Init selected pinmap - default pinmap calculated by platform
|
|
86
86
|
if callable(set_pinmap):
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
try:
|
|
88
|
+
pinmap = set_pinmap(Data.CONFIG_CACHE['cstmpmap'])
|
|
89
|
+
console_write(f"[PIN MAP] {pinmap}")
|
|
90
|
+
except Exception as e:
|
|
91
|
+
console_write(f"\n[PIN MAP] !!! SETUP ERROR !!!: {e}\n")
|
|
89
92
|
# SET dbg based on config settings (inject user conf)
|
|
90
93
|
DebugCfg.DEBUG = Data.CONFIG_CACHE['dbg']
|
|
91
94
|
if DebugCfg.DEBUG:
|
micrOS/source/Espnow.py
CHANGED
|
@@ -1,7 +1,23 @@
|
|
|
1
|
-
|
|
1
|
+
"""
|
|
2
|
+
ESPNow Session Server and Protocol Utilities
|
|
3
|
+
|
|
4
|
+
This module implements:
|
|
5
|
+
- Custom ESPNow message protocol with transaction IDs (tid) for secure, session-aware communication.
|
|
6
|
+
- Asynchronous server and client logic for sending and receiving ESPNow messages.
|
|
7
|
+
- Response routing using both MAC address and transaction ID to ensure correct delivery to tasks.
|
|
8
|
+
- Peer management, handshake routines, and statistics reporting for ESPNow devices.
|
|
9
|
+
|
|
10
|
+
Designed for MicroPython environments with async support.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
|
|
2
14
|
from binascii import hexlify
|
|
3
15
|
from json import load, dump
|
|
4
16
|
import uasyncio as asyncio
|
|
17
|
+
import urandom
|
|
18
|
+
|
|
19
|
+
from aioespnow import AIOESPNow
|
|
20
|
+
|
|
5
21
|
from Tasks import NativeTask, lm_exec, lm_is_loaded
|
|
6
22
|
from Config import cfgget
|
|
7
23
|
from Debug import syslog
|
|
@@ -10,7 +26,7 @@ from Files import OSPath, path_join, is_file
|
|
|
10
26
|
|
|
11
27
|
# ----------- PARSE AND RENDER MSG PROTOCOL --------------
|
|
12
28
|
|
|
13
|
-
def
|
|
29
|
+
def render_packet(tid: str, oper: str, data: str, prompt: str) -> str:
|
|
14
30
|
"""
|
|
15
31
|
Render ESPNow custom message (protocol)
|
|
16
32
|
"""
|
|
@@ -21,7 +37,7 @@ def render_response(tid:str, oper:str, data:str, prompt:str) -> str:
|
|
|
21
37
|
.replace("{data}", str(data)).replace("{prompt}", prompt))
|
|
22
38
|
return tmp
|
|
23
39
|
|
|
24
|
-
def
|
|
40
|
+
def parse_packet(msg: bytes) -> tuple[bool, dict | str]:
|
|
25
41
|
"""
|
|
26
42
|
Parse ESPNow custom message protocol
|
|
27
43
|
"""
|
|
@@ -39,21 +55,43 @@ def parse_request(msg:bytes) -> (bool, dict|str):
|
|
|
39
55
|
return False, f"Missing 4 args: {msg}"
|
|
40
56
|
|
|
41
57
|
|
|
58
|
+
def get_command_module(request):
|
|
59
|
+
if isinstance(request, dict):
|
|
60
|
+
command = request["data"].split()
|
|
61
|
+
module = command[0]
|
|
62
|
+
elif isinstance(request, str):
|
|
63
|
+
command = request.split()
|
|
64
|
+
module = command[0]
|
|
65
|
+
else:
|
|
66
|
+
command = []
|
|
67
|
+
module = ""
|
|
68
|
+
return command, module
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def generate_tid() -> str:
|
|
72
|
+
"""
|
|
73
|
+
Generate a secure, random transaction ID (tid).
|
|
74
|
+
Returns an 8-byte hex string.
|
|
75
|
+
"""
|
|
76
|
+
return hexlify(bytes([urandom.getrandbits(8) for _ in range(8)])).decode()
|
|
77
|
+
|
|
78
|
+
|
|
42
79
|
# ----------- ESPNOW SESSION SERVER - LISTENER AND SENDER --------------
|
|
43
80
|
class ResponseRouter:
|
|
44
81
|
"""
|
|
45
82
|
Response Router (by mac address)
|
|
46
83
|
to connect sender task with receiver loop (aka server)
|
|
47
84
|
"""
|
|
48
|
-
_routes: dict[bytes, "ResponseRouter"] = {}
|
|
85
|
+
_routes: dict[tuple[bytes, str], "ResponseRouter"] = {}
|
|
49
86
|
|
|
50
|
-
def __init__(self, mac: bytes):
|
|
87
|
+
def __init__(self, mac: bytes, tid: str):
|
|
51
88
|
self.mac = mac
|
|
89
|
+
self.tid = tid
|
|
52
90
|
self.response = None
|
|
53
91
|
self._event = asyncio.Event()
|
|
54
|
-
ResponseRouter._routes[mac] = self
|
|
92
|
+
ResponseRouter._routes[(mac, tid)] = self
|
|
55
93
|
|
|
56
|
-
async def get_response(self, timeout:int=10) -> str|dict:
|
|
94
|
+
async def get_response(self, timeout: int=10) -> str|dict:
|
|
57
95
|
"""Wait for one response, then clear the event for reuse."""
|
|
58
96
|
try:
|
|
59
97
|
await asyncio.wait_for(self._event.wait(), timeout)
|
|
@@ -63,18 +101,17 @@ class ResponseRouter:
|
|
|
63
101
|
return self.response
|
|
64
102
|
|
|
65
103
|
@staticmethod
|
|
66
|
-
def update_response(mac: bytes, response: str) -> None:
|
|
67
|
-
|
|
68
|
-
router = ResponseRouter._routes.get(mac)
|
|
104
|
+
def update_response(mac: bytes, tid: str, response: str) -> None:
|
|
105
|
+
router = ResponseRouter._routes.get((mac, tid), None)
|
|
69
106
|
if router is None:
|
|
70
|
-
syslog(f"[WARN][ESPNOW] No response route for {mac}")
|
|
107
|
+
syslog(f"[WARN][ESPNOW] No response route for {(mac, tid)}")
|
|
71
108
|
return
|
|
72
109
|
router.response = response
|
|
73
110
|
router._event.set()
|
|
74
111
|
|
|
75
112
|
def close(self) -> None:
|
|
76
113
|
"""Remove routing entry when done."""
|
|
77
|
-
ResponseRouter._routes.pop(self.mac, None)
|
|
114
|
+
ResponseRouter._routes.pop((self.mac, self.tid), None)
|
|
78
115
|
|
|
79
116
|
|
|
80
117
|
class ESPNowSS:
|
|
@@ -106,7 +143,7 @@ class ESPNowSS:
|
|
|
106
143
|
if not is_file(self.peer_cache_path):
|
|
107
144
|
return
|
|
108
145
|
try:
|
|
109
|
-
with open(self.peer_cache_path, 'r') as f:
|
|
146
|
+
with open(self.peer_cache_path, 'r', encoding='utf-8') as f:
|
|
110
147
|
devices_map = load(f)
|
|
111
148
|
self.devices = {bytes(k): v for k, v in devices_map}
|
|
112
149
|
for mac in self.devices:
|
|
@@ -116,7 +153,7 @@ class ESPNowSS:
|
|
|
116
153
|
syslog(f"[ERR][ESPNOW] Loading peers: {e}")
|
|
117
154
|
|
|
118
155
|
# ----------- SERVER METHODS --------------
|
|
119
|
-
def _request_handler(self, msg:bytes, my_task:NativeTask, mac:bytes):
|
|
156
|
+
def _request_handler(self, msg: bytes, my_task: NativeTask, mac: bytes):
|
|
120
157
|
"""
|
|
121
158
|
Handle server input message (request), with REQ/RSP types (oper)
|
|
122
159
|
oper==REQ - command execution
|
|
@@ -126,7 +163,7 @@ class ESPNowSS:
|
|
|
126
163
|
:param mac: sender binary mac address
|
|
127
164
|
"""
|
|
128
165
|
|
|
129
|
-
state, request =
|
|
166
|
+
state, request = parse_packet(msg)
|
|
130
167
|
if not state:
|
|
131
168
|
my_task.out = f"[_ESPNOW] {request}"
|
|
132
169
|
return state, request
|
|
@@ -139,18 +176,17 @@ class ESPNowSS:
|
|
|
139
176
|
|
|
140
177
|
# Check if the module/command is allowed., check oper==REQ/RSP
|
|
141
178
|
if operation == "REQ":
|
|
142
|
-
command = request
|
|
143
|
-
module = command[0]
|
|
179
|
+
command, module = get_command_module(request)
|
|
144
180
|
# Handle default hello - handshake message
|
|
145
181
|
if len(command) == 1 and module == "hello":
|
|
146
|
-
rendered_out =
|
|
182
|
+
rendered_out = render_packet(tid=tid, oper="RSP", data=f"hello {prompt}", prompt=self.devfid)
|
|
147
183
|
return True, rendered_out
|
|
148
184
|
# COMMAND EXECUTION
|
|
149
185
|
if lm_is_loaded(module):
|
|
150
186
|
try:
|
|
151
187
|
state, out = lm_exec(command)
|
|
152
188
|
# rendered_output: "{tid}|{oper}|{data}|{prompt}$"
|
|
153
|
-
rendered_out =
|
|
189
|
+
rendered_out = render_packet(tid=tid, oper="RSP", data=out, prompt=self.devfid)
|
|
154
190
|
return state, rendered_out
|
|
155
191
|
except Exception as e:
|
|
156
192
|
# Optionally log the exception here.
|
|
@@ -159,17 +195,17 @@ class ESPNowSS:
|
|
|
159
195
|
else:
|
|
160
196
|
warning_msg = f"[WARN][_ESPNOW] NotAllowed {module}"
|
|
161
197
|
syslog(warning_msg)
|
|
162
|
-
rendered_out =
|
|
198
|
+
rendered_out = render_packet(tid=tid, oper="RSP", data=warning_msg,
|
|
163
199
|
prompt=self.devfid)
|
|
164
200
|
state, out = True, rendered_out
|
|
165
201
|
return state, out
|
|
166
202
|
if operation == "RSP":
|
|
167
203
|
resp_data = request["data"]
|
|
168
|
-
ResponseRouter.update_response(mac, resp_data) # USE <tid> for proper session response mapping
|
|
204
|
+
ResponseRouter.update_response(mac, tid, resp_data) # USE <tid> for proper session response mapping
|
|
169
205
|
#syslog(f"[_ESPNOW] No action, {request}")
|
|
170
206
|
return False, ""
|
|
171
207
|
|
|
172
|
-
async def _server(self, tag:str):
|
|
208
|
+
async def _server(self, tag: str):
|
|
173
209
|
"""
|
|
174
210
|
ESPnow async listener task
|
|
175
211
|
:param tag: micro_task tag for task access
|
|
@@ -201,24 +237,23 @@ class ESPNowSS:
|
|
|
201
237
|
"""
|
|
202
238
|
# Create an asynchronous task with tag 'espnow.server'
|
|
203
239
|
tag = 'espnow.server'
|
|
204
|
-
|
|
205
|
-
return {tag: "Starting"} if state else {tag: "Already running"}
|
|
240
|
+
return NativeTask().create(callback=self._server(tag), tag=tag)
|
|
206
241
|
|
|
207
242
|
#----------- SEND METHODS --------------
|
|
208
|
-
async def __asend_raw(self, mac:bytes, msg:str):
|
|
243
|
+
async def __asend_raw(self, mac: bytes, msg: str):
|
|
209
244
|
"""
|
|
210
245
|
ESPnow send message to mac address
|
|
211
246
|
"""
|
|
212
247
|
return await self.espnow.asend(mac, msg.encode("utf-8"))
|
|
213
248
|
|
|
214
|
-
async def _asend_task(self, peer:bytes, tag:str, msg:str):
|
|
249
|
+
async def _asend_task(self, tid: str, peer: bytes, tag: str, msg: str):
|
|
215
250
|
"""
|
|
216
251
|
ESPNow client task: send a command to a peer and update task status.
|
|
217
252
|
"""
|
|
218
253
|
with NativeTask.TASKS.get(tag, None) as my_task:
|
|
219
|
-
router = ResponseRouter(peer)
|
|
254
|
+
router = ResponseRouter(peer, tid)
|
|
220
255
|
# rendered_output: "{tid}|{oper}|{data}|{prompt}$"
|
|
221
|
-
rendered_out =
|
|
256
|
+
rendered_out = render_packet(tid=tid, oper="REQ", data=msg, prompt=self.devfid)
|
|
222
257
|
if await self.__asend_raw(peer, rendered_out):
|
|
223
258
|
my_task.out = f"[ESPNOW SEND] {rendered_out}"
|
|
224
259
|
my_task.out = await router.get_response()
|
|
@@ -226,12 +261,12 @@ class ESPNowSS:
|
|
|
226
261
|
my_task.out = "[ESPNOW SEND] Peer not responding"
|
|
227
262
|
router.close()
|
|
228
263
|
|
|
229
|
-
def mac_by_peer_name(self, peer_name:str) -> bytes|None:
|
|
264
|
+
def mac_by_peer_name(self, peer_name: str) -> bytes | None:
|
|
230
265
|
matches = [k for k, v in self.devices.items() if v == peer_name]
|
|
231
266
|
peer = matches[0] if matches else None
|
|
232
267
|
return peer
|
|
233
268
|
|
|
234
|
-
def send(self, peer:bytes|str, msg:str) -> dict:
|
|
269
|
+
def send(self, peer: bytes | str, msg: str) -> dict:
|
|
235
270
|
"""
|
|
236
271
|
Send a command over ESPNow.
|
|
237
272
|
:param peer: Binary MAC address of another device.
|
|
@@ -247,10 +282,11 @@ class ESPNowSS:
|
|
|
247
282
|
peer = _peer
|
|
248
283
|
|
|
249
284
|
peer_name = hexlify(peer, ':').decode() if peer_name is None else peer_name
|
|
250
|
-
|
|
285
|
+
_, module_name = get_command_module(msg)
|
|
286
|
+
task_id = f"con.espnow.{peer_name}.{module_name}"
|
|
287
|
+
tid = generate_tid()
|
|
251
288
|
# Create an asynchronous sending task.
|
|
252
|
-
|
|
253
|
-
return {task_id: "Starting"} if state else {task_id: "Already running"}
|
|
289
|
+
return NativeTask().create(callback=self._asend_task(tid, peer, task_id, msg), tag=task_id)
|
|
254
290
|
|
|
255
291
|
def cluster_send(self, msg):
|
|
256
292
|
"""
|
|
@@ -264,14 +300,14 @@ class ESPNowSS:
|
|
|
264
300
|
# ----------- OTHER METHODS --------------
|
|
265
301
|
def save_peers(self):
|
|
266
302
|
try:
|
|
267
|
-
with open(self.peer_cache_path, "w") as f:
|
|
303
|
+
with open(self.peer_cache_path, "w", encoding="utf-8") as f:
|
|
268
304
|
dump([[list(k), v] for k, v in self.devices.items()], f)
|
|
269
305
|
return True
|
|
270
306
|
except Exception as e:
|
|
271
307
|
syslog(f"[ERR][ESPNOW] Saving peers: {e}")
|
|
272
308
|
return False
|
|
273
309
|
|
|
274
|
-
async def _handshake(self, peer:bytes, tag:str):
|
|
310
|
+
async def _handshake(self, peer: bytes, tag: str):
|
|
275
311
|
"""
|
|
276
312
|
Handshake with peer
|
|
277
313
|
- with device caching
|
|
@@ -297,17 +333,34 @@ class ESPNowSS:
|
|
|
297
333
|
my_task.out = f"Handshake: {result} from {self.devices.get(peer)} [{'OK' if is_ok else 'NOK'}]"
|
|
298
334
|
sender_task.cancel() # Delete sender task (cleanup)
|
|
299
335
|
|
|
300
|
-
def
|
|
301
|
-
|
|
336
|
+
def _mac_str_to_bytes(self, mac_str: str) -> bytes|None:
|
|
337
|
+
"""
|
|
338
|
+
Convert MAC address string (e.g., '50:02:91:86:34:28') to bytes.
|
|
339
|
+
"""
|
|
340
|
+
try:
|
|
341
|
+
mac_bytes = bytes(int(x, 16) for x in mac_str.split(":"))
|
|
342
|
+
if len(mac_bytes) != 6:
|
|
343
|
+
return None
|
|
344
|
+
return mac_bytes
|
|
345
|
+
except Exception:
|
|
346
|
+
return None
|
|
347
|
+
|
|
348
|
+
def handshake(self, peer: bytes | str):
|
|
349
|
+
"""
|
|
350
|
+
Initiate a handshake with a peer device over ESPNow.
|
|
351
|
+
|
|
352
|
+
:param peer: The peer device's MAC address as bytes or a string in the format '50:02:91:86:34:28'.
|
|
353
|
+
:return: A dictionary with error information or a NativeTask instance for the handshake operation.
|
|
354
|
+
"""
|
|
355
|
+
task_id = "con.espnow.handshake"
|
|
302
356
|
# Create an asynchronous sending task.
|
|
303
357
|
if isinstance(peer, str) and ":" in peer:
|
|
304
|
-
|
|
305
|
-
|
|
358
|
+
peer_bytes = self._mac_str_to_bytes(peer)
|
|
359
|
+
if peer_bytes is not None:
|
|
360
|
+
peer = peer_bytes
|
|
306
361
|
if isinstance(peer, bytes):
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
else:
|
|
310
|
-
return {None: "Invalid MAC address format. Use 50:02:91:86:34:28 or b'P\\x02\\x91\\x864('"}
|
|
362
|
+
return NativeTask().create(callback=self._handshake(peer, task_id), tag=task_id)
|
|
363
|
+
return {None: "Invalid MAC address format. Use 50:02:91:86:34:28 or b'P\\x02\\x91\\x864('"}
|
|
311
364
|
|
|
312
365
|
def stats(self):
|
|
313
366
|
"""
|
|
@@ -324,11 +377,14 @@ class ESPNowSS:
|
|
|
324
377
|
except Exception as e:
|
|
325
378
|
_peers = str(e)
|
|
326
379
|
return {"stats": _stats, "peers": _peers, "ready": self.server_ready}
|
|
327
|
-
|
|
380
|
+
|
|
328
381
|
def members(self):
|
|
382
|
+
"""
|
|
383
|
+
Returns the list of devices that are members of the current group.
|
|
384
|
+
"""
|
|
329
385
|
return self.devices
|
|
330
386
|
|
|
331
|
-
def remove_peer(self, peer:bytes) -> bool:
|
|
387
|
+
def remove_peer(self, peer: bytes) -> bool:
|
|
332
388
|
"""
|
|
333
389
|
Remove peer from ESPNow devices
|
|
334
390
|
:param peer: MAC address as bytes to remove
|