micrOSDevToolKit 2.13.0__py3-none-any.whl → 2.17.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 +125 -121
- micrOS/source/Common.py +48 -26
- micrOS/source/Config.py +13 -5
- micrOS/source/Espnow.py +100 -58
- micrOS/source/Files.py +77 -41
- micrOS/source/Hooks.py +18 -34
- micrOS/source/Logger.py +2 -7
- micrOS/source/Network.py +36 -16
- micrOS/source/Server.py +22 -8
- micrOS/source/Shell.py +9 -6
- micrOS/source/Tasks.py +34 -13
- micrOS/source/Web.py +69 -31
- micrOS/source/__pycache__/Common.cpython-312.pyc +0 -0
- micrOS/source/__pycache__/Files.cpython-312.pyc +0 -0
- micrOS/source/__pycache__/Logger.cpython-312.pyc +0 -0
- micrOS/source/__pycache__/Server.cpython-312.pyc +0 -0
- micrOS/source/config/_git.keep +0 -0
- micrOS/source/micrOS.py +7 -0
- micrOS/source/micrOSloader.py +2 -10
- micrOS/source/microIO.py +2 -2
- micrOS/source/modules/IO_esp32c6.py +38 -0
- micrOS/source/modules/LM_L298N.py +161 -0
- micrOS/source/modules/LM_cluster.py +250 -0
- {toolkit/workspace/precompiled → micrOS/source/modules}/LM_esp32.py +5 -0
- micrOS/source/modules/LM_espnow.py +36 -0
- micrOS/source/{LM_i2c.py → modules/LM_i2c.py} +1 -1
- micrOS/source/{LM_light_sensor.py → modules/LM_light_sensor.py} +2 -2
- micrOS/source/modules/LM_mqtt_client.py +246 -0
- micrOS/source/{LM_neoeffects.py → modules/LM_neoeffects.py} +14 -4
- micrOS/source/{LM_neomatrix.py → modules/LM_neomatrix.py} +140 -38
- micrOS/source/{LM_oled_ui.py → modules/LM_oled_ui.py} +2 -2
- micrOS/source/{LM_oledui.py → modules/LM_oledui.py} +2 -2
- micrOS/source/{LM_pacman.py → modules/LM_pacman.py} +74 -29
- micrOS/source/{LM_presence.py → modules/LM_presence.py} +2 -2
- micrOS/source/{LM_robustness.py → modules/LM_robustness.py} +49 -2
- micrOS/source/{LM_tcs3472.py → modules/LM_tcs3472.py} +4 -6
- micrOS/source/web/dashboard.html +2 -0
- micrOS/source/web/matrix_draw.html +390 -0
- micrOS/source/web/uapi.js +9 -6
- {microsdevtoolkit-2.13.0.dist-info → microsdevtoolkit-2.17.0.dist-info}/METADATA +30 -37
- {microsdevtoolkit-2.13.0.dist-info → microsdevtoolkit-2.17.0.dist-info}/RECORD +201 -191
- toolkit/DevEnvCompile.py +21 -12
- toolkit/DevEnvOTA.py +27 -16
- toolkit/DevEnvUSB.py +35 -21
- toolkit/LM_to_compile.dat +3 -1
- toolkit/MicrOSDevEnv.py +37 -21
- toolkit/dashboard_apps/QMI8685_GYRO.py +1 -1
- toolkit/dashboard_apps/SystemTest.py +8 -5
- toolkit/{MicrosFiles.py → lib/MicrosFiles.py} +24 -4
- toolkit/lib/pip_package_installer.py +5 -2
- toolkit/micrOSdashboard.py +2 -2
- toolkit/micrOSlint.py +17 -7
- toolkit/simulator_lib/__pycache__/simulator.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/uos.cpython-312.pyc +0 -0
- toolkit/simulator_lib/mqtt_as/Note.md +15 -0
- toolkit/simulator_lib/mqtt_as/__init__.py +950 -0
- toolkit/simulator_lib/mqtt_as/__pycache__/__init__.cpython-312.pyc +0 -0
- toolkit/simulator_lib/mqtt_as/clean.py +69 -0
- toolkit/simulator_lib/mqtt_as/mqtt_v5_properties.py +239 -0
- toolkit/simulator_lib/mqtt_as/range.py +90 -0
- toolkit/simulator_lib/mqtt_as/range_ex.py +119 -0
- toolkit/simulator_lib/simulator.py +14 -1
- toolkit/simulator_lib/uos.py +2 -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/Hooks.mpy +0 -0
- toolkit/workspace/precompiled/Logger.mpy +0 -0
- toolkit/workspace/precompiled/Network.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/Web.mpy +0 -0
- toolkit/workspace/precompiled/config/_git.keep +0 -0
- toolkit/workspace/precompiled/micrOS.mpy +0 -0
- toolkit/workspace/precompiled/micrOSloader.mpy +0 -0
- toolkit/workspace/precompiled/microIO.mpy +0 -0
- toolkit/workspace/precompiled/{IO_esp32.mpy → modules/IO_esp32.mpy} +0 -0
- toolkit/workspace/precompiled/{IO_esp32c3.mpy → modules/IO_esp32c3.mpy} +0 -0
- toolkit/workspace/precompiled/modules/IO_esp32c6.mpy +0 -0
- toolkit/workspace/precompiled/{IO_esp32s2.mpy → modules/IO_esp32s2.mpy} +0 -0
- toolkit/workspace/precompiled/{IO_esp32s3.mpy → modules/IO_esp32s3.mpy} +0 -0
- toolkit/workspace/precompiled/{IO_m5stamp.mpy → modules/IO_m5stamp.mpy} +0 -0
- toolkit/workspace/precompiled/{IO_qtpy.mpy → modules/IO_qtpy.mpy} +0 -0
- toolkit/workspace/precompiled/modules/IO_rp2.mpy +0 -0
- toolkit/workspace/precompiled/modules/IO_s3matrix.mpy +0 -0
- toolkit/workspace/precompiled/{IO_tinypico.mpy → modules/IO_tinypico.mpy} +0 -0
- toolkit/workspace/precompiled/modules/LM_L298N.mpy +0 -0
- toolkit/workspace/precompiled/{LM_OV2640.mpy → modules/LM_OV2640.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_aht10.mpy → modules/LM_aht10.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_bme280.mpy → modules/LM_bme280.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_buzzer.mpy → modules/LM_buzzer.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_cct.mpy → modules/LM_cct.mpy} +0 -0
- toolkit/workspace/precompiled/modules/LM_cluster.mpy +0 -0
- toolkit/workspace/precompiled/{LM_co2.mpy → modules/LM_co2.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_dht11.mpy → modules/LM_dht11.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_dht22.mpy → modules/LM_dht22.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_dimmer.mpy → modules/LM_dimmer.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_distance.mpy → modules/LM_distance.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_ds18.mpy → modules/LM_ds18.mpy} +0 -0
- {micrOS/source → toolkit/workspace/precompiled/modules}/LM_esp32.py +5 -0
- toolkit/workspace/precompiled/modules/LM_espnow.py +36 -0
- toolkit/workspace/precompiled/{LM_gameOfLife.mpy → modules/LM_gameOfLife.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_genIO.mpy → modules/LM_genIO.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_haptic.mpy → modules/LM_haptic.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_i2c.py → modules/LM_i2c.py} +1 -1
- toolkit/workspace/precompiled/{LM_i2s_mic.mpy → modules/LM_i2s_mic.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_keychain.mpy → modules/LM_keychain.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_ld2410.mpy → modules/LM_ld2410.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/{LM_neoeffects.mpy → modules/LM_neoeffects.mpy} +0 -0
- toolkit/workspace/precompiled/modules/LM_neomatrix.mpy +0 -0
- toolkit/workspace/precompiled/{LM_neopixel.mpy → modules/LM_neopixel.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_oled.mpy → modules/LM_oled.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_oled_sh1106.mpy → modules/LM_oled_sh1106.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/{LM_rest.mpy → modules/LM_rest.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_rgb.mpy → modules/LM_rgb.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_rgbcct.mpy → modules/LM_rgbcct.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_roboarm.mpy → modules/LM_roboarm.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_robustness.py → modules/LM_robustness.py} +49 -2
- toolkit/workspace/precompiled/{LM_servo.mpy → modules/LM_servo.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_sound_event.mpy → modules/LM_sound_event.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_stepper.mpy → modules/LM_stepper.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_switch.mpy → modules/LM_switch.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_system.mpy → modules/LM_system.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_tcs3472.py → modules/LM_tcs3472.py} +4 -6
- toolkit/workspace/precompiled/{LM_telegram.mpy → modules/LM_telegram.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_tinyrgb.mpy → modules/LM_tinyrgb.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_trackball.mpy → modules/LM_trackball.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_veml7700.mpy → modules/LM_veml7700.mpy} +0 -0
- toolkit/workspace/precompiled/web/dashboard.html +2 -0
- toolkit/workspace/precompiled/web/matrix_draw.html +390 -0
- toolkit/workspace/precompiled/web/uapi.js +9 -6
- micrOS/source/IO_esp32c6.py +0 -16
- micrOS/source/LM_L298N_DCmotor.py +0 -86
- micrOS/source/LM_espnow.py +0 -57
- micrOS/source/LM_mqtt_pro.py +0 -211
- toolkit/lib/file_extensions.py +0 -22
- toolkit/workspace/precompiled/Common.cpython-312.pyc +0 -0
- toolkit/workspace/precompiled/IO_esp32c6.mpy +0 -0
- toolkit/workspace/precompiled/IO_rp2.mpy +0 -0
- toolkit/workspace/precompiled/IO_s3matrix.mpy +0 -0
- toolkit/workspace/precompiled/LM_L298N_DCmotor.mpy +0 -0
- toolkit/workspace/precompiled/LM_espnow.py +0 -57
- toolkit/workspace/precompiled/LM_light_sensor.mpy +0 -0
- toolkit/workspace/precompiled/LM_mqtt_pro.py +0 -211
- toolkit/workspace/precompiled/LM_neomatrix.mpy +0 -0
- toolkit/workspace/precompiled/LM_oled_ui.mpy +0 -0
- toolkit/workspace/precompiled/LM_oledui.mpy +0 -0
- toolkit/workspace/precompiled/LM_pacman.mpy +0 -0
- toolkit/workspace/precompiled/LM_presence.mpy +0 -0
- toolkit/workspace/precompiled/Logger.cpython-312.pyc +0 -0
- toolkit/workspace/precompiled/Server.cpython-312.pyc +0 -0
- /micrOS/source/{IO_esp32.py → modules/IO_esp32.py} +0 -0
- /micrOS/source/{IO_esp32c3.py → modules/IO_esp32c3.py} +0 -0
- /micrOS/source/{IO_esp32s2.py → modules/IO_esp32s2.py} +0 -0
- /micrOS/source/{IO_esp32s3.py → modules/IO_esp32s3.py} +0 -0
- /micrOS/source/{IO_m5stamp.py → modules/IO_m5stamp.py} +0 -0
- /micrOS/source/{IO_qtpy.py → modules/IO_qtpy.py} +0 -0
- /micrOS/source/{IO_rp2.py → modules/IO_rp2.py} +0 -0
- /micrOS/source/{IO_s3matrix.py → modules/IO_s3matrix.py} +0 -0
- /micrOS/source/{IO_tinypico.py → modules/IO_tinypico.py} +0 -0
- /micrOS/source/{LM_L9110_DCmotor.py → modules/LM_L9110_DCmotor.py} +0 -0
- /micrOS/source/{LM_OV2640.py → modules/LM_OV2640.py} +0 -0
- /micrOS/source/{LM_VL53L0X.py → modules/LM_VL53L0X.py} +0 -0
- /micrOS/source/{LM_aht10.py → modules/LM_aht10.py} +0 -0
- /micrOS/source/{LM_bme280.py → modules/LM_bme280.py} +0 -0
- /micrOS/source/{LM_buzzer.py → modules/LM_buzzer.py} +0 -0
- /micrOS/source/{LM_cct.py → modules/LM_cct.py} +0 -0
- /micrOS/source/{LM_co2.py → modules/LM_co2.py} +0 -0
- /micrOS/source/{LM_dashboard_be.py → modules/LM_dashboard_be.py} +0 -0
- /micrOS/source/{LM_dht11.py → modules/LM_dht11.py} +0 -0
- /micrOS/source/{LM_dht22.py → modules/LM_dht22.py} +0 -0
- /micrOS/source/{LM_dimmer.py → modules/LM_dimmer.py} +0 -0
- /micrOS/source/{LM_distance.py → modules/LM_distance.py} +0 -0
- /micrOS/source/{LM_ds18.py → modules/LM_ds18.py} +0 -0
- /micrOS/source/{LM_gameOfLife.py → modules/LM_gameOfLife.py} +0 -0
- /micrOS/source/{LM_genIO.py → modules/LM_genIO.py} +0 -0
- /micrOS/source/{LM_haptic.py → modules/LM_haptic.py} +0 -0
- /micrOS/source/{LM_i2s_mic.py → modules/LM_i2s_mic.py} +0 -0
- /micrOS/source/{LM_keychain.py → modules/LM_keychain.py} +0 -0
- /micrOS/source/{LM_ld2410.py → modules/LM_ld2410.py} +0 -0
- /micrOS/source/{LM_neopixel.py → modules/LM_neopixel.py} +0 -0
- /micrOS/source/{LM_oled.py → modules/LM_oled.py} +0 -0
- /micrOS/source/{LM_oled_sh1106.py → modules/LM_oled_sh1106.py} +0 -0
- /micrOS/source/{LM_pet_feeder.py → modules/LM_pet_feeder.py} +0 -0
- /micrOS/source/{LM_qmi8658.py → modules/LM_qmi8658.py} +0 -0
- /micrOS/source/{LM_rencoder.py → modules/LM_rencoder.py} +0 -0
- /micrOS/source/{LM_rest.py → modules/LM_rest.py} +0 -0
- /micrOS/source/{LM_rgb.py → modules/LM_rgb.py} +0 -0
- /micrOS/source/{LM_rgbcct.py → modules/LM_rgbcct.py} +0 -0
- /micrOS/source/{LM_roboarm.py → modules/LM_roboarm.py} +0 -0
- /micrOS/source/{LM_rp2w.py → modules/LM_rp2w.py} +0 -0
- /micrOS/source/{LM_sdcard.py → modules/LM_sdcard.py} +0 -0
- /micrOS/source/{LM_servo.py → modules/LM_servo.py} +0 -0
- /micrOS/source/{LM_sound_event.py → modules/LM_sound_event.py} +0 -0
- /micrOS/source/{LM_stepper.py → modules/LM_stepper.py} +0 -0
- /micrOS/source/{LM_switch.py → modules/LM_switch.py} +0 -0
- /micrOS/source/{LM_system.py → modules/LM_system.py} +0 -0
- /micrOS/source/{LM_telegram.py → modules/LM_telegram.py} +0 -0
- /micrOS/source/{LM_tinyrgb.py → modules/LM_tinyrgb.py} +0 -0
- /micrOS/source/{LM_trackball.py → modules/LM_trackball.py} +0 -0
- /micrOS/source/{LM_veml7700.py → modules/LM_veml7700.py} +0 -0
- {microsdevtoolkit-2.13.0.data → microsdevtoolkit-2.17.0.data}/scripts/devToolKit.py +0 -0
- {microsdevtoolkit-2.13.0.dist-info → microsdevtoolkit-2.17.0.dist-info}/WHEEL +0 -0
- {microsdevtoolkit-2.13.0.dist-info → microsdevtoolkit-2.17.0.dist-info}/licenses/LICENSE +0 -0
- {microsdevtoolkit-2.13.0.dist-info → microsdevtoolkit-2.17.0.dist-info}/top_level.txt +0 -0
- /toolkit/workspace/precompiled/{LM_L9110_DCmotor.py → modules/LM_L9110_DCmotor.py} +0 -0
- /toolkit/workspace/precompiled/{LM_VL53L0X.py → modules/LM_VL53L0X.py} +0 -0
- /toolkit/workspace/precompiled/{LM_dashboard_be.py → modules/LM_dashboard_be.py} +0 -0
- /toolkit/workspace/precompiled/{LM_pet_feeder.py → modules/LM_pet_feeder.py} +0 -0
- /toolkit/workspace/precompiled/{LM_qmi8658.py → modules/LM_qmi8658.py} +0 -0
- /toolkit/workspace/precompiled/{LM_rencoder.py → modules/LM_rencoder.py} +0 -0
- /toolkit/workspace/precompiled/{LM_rp2w.py → modules/LM_rp2w.py} +0 -0
- /toolkit/workspace/precompiled/{LM_sdcard.py → modules/LM_sdcard.py} +0 -0
micrOS/source/Espnow.py
CHANGED
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
from aioespnow import AIOESPNow
|
|
2
2
|
from binascii import hexlify
|
|
3
|
-
from
|
|
3
|
+
from json import load, dump
|
|
4
4
|
import uasyncio as asyncio
|
|
5
|
-
from
|
|
5
|
+
from Tasks import NativeTask, lm_exec, lm_is_loaded
|
|
6
6
|
from Config import cfgget
|
|
7
7
|
from Debug import syslog
|
|
8
|
+
from Files import OSPath, path_join, is_file
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
# ----------- PARSE AND RENDER MSG PROTOCOL --------------
|
|
11
12
|
|
|
12
|
-
def render_response(tid, oper, data, prompt) -> str:
|
|
13
|
+
def render_response(tid:str, oper:str, data:str, prompt:str) -> str:
|
|
13
14
|
"""
|
|
14
15
|
Render ESPNow custom message (protocol)
|
|
15
16
|
"""
|
|
16
17
|
if oper not in ("REQ", "RSP"):
|
|
17
18
|
syslog(f"[ERR] espnow render_response, unknown oper: {oper}")
|
|
18
19
|
tmp = "{tid}|{oper}|{data}|{prompt}$"
|
|
19
|
-
tmp = tmp.replace("{tid}",
|
|
20
|
-
|
|
21
|
-
tmp = tmp.replace("{data}", str(data))
|
|
22
|
-
tmp = tmp.replace("{prompt}", str(prompt))
|
|
20
|
+
tmp = (tmp.replace("{tid}", tid).replace("{oper}", oper)
|
|
21
|
+
.replace("{data}", str(data)).replace("{prompt}", prompt))
|
|
23
22
|
return tmp
|
|
24
23
|
|
|
25
24
|
def parse_request(msg:bytes) -> (bool, dict|str):
|
|
@@ -85,6 +84,7 @@ class ESPNowSS:
|
|
|
85
84
|
_instance = None
|
|
86
85
|
|
|
87
86
|
def __new__(cls, *args, **kwargs):
|
|
87
|
+
# SINGLETON PATTERN
|
|
88
88
|
if cls._instance is None:
|
|
89
89
|
# first time: actually create it
|
|
90
90
|
cls._instance = super().__new__(cls)
|
|
@@ -93,12 +93,27 @@ class ESPNowSS:
|
|
|
93
93
|
def __init__(self):
|
|
94
94
|
# __init__ still runs each time, so guard if needed
|
|
95
95
|
if not hasattr(self, '_initialized'):
|
|
96
|
+
self._initialized = True
|
|
96
97
|
self.espnow = AIOESPNow() # Instance with async support
|
|
97
98
|
self.espnow.active(True)
|
|
98
99
|
self.devfid = cfgget('devfid')
|
|
99
100
|
self.devices: dict[bytes, str] = {} # mapping for { "mac address": "devfid" } pairs
|
|
100
|
-
self._initialized = True
|
|
101
101
|
self.server_ready = False
|
|
102
|
+
self.peer_cache = path_join(OSPath.DATA, "espnow_peers.app_json")
|
|
103
|
+
self._load_peers()
|
|
104
|
+
|
|
105
|
+
def _load_peers(self):
|
|
106
|
+
if not is_file(self.peer_cache):
|
|
107
|
+
return
|
|
108
|
+
try:
|
|
109
|
+
with open(self.peer_cache, 'r') as f:
|
|
110
|
+
devices_map = load(f)
|
|
111
|
+
self.devices = {bytes(k): v for k, v in devices_map}
|
|
112
|
+
for mac in self.devices:
|
|
113
|
+
# PEER REGISTRATION
|
|
114
|
+
self.espnow.add_peer(mac)
|
|
115
|
+
except Exception as e:
|
|
116
|
+
syslog(f"[ERR][ESPNOW] Loading peers: {e}")
|
|
102
117
|
|
|
103
118
|
# ----------- SERVER METHODS --------------
|
|
104
119
|
def _request_handler(self, msg:bytes, my_task:NativeTask, mac:bytes):
|
|
@@ -126,6 +141,11 @@ class ESPNowSS:
|
|
|
126
141
|
if operation == "REQ":
|
|
127
142
|
command = request["data"].split()
|
|
128
143
|
module = command[0]
|
|
144
|
+
# Handle default hello - handshake message
|
|
145
|
+
if len(command) == 1 and module == "hello":
|
|
146
|
+
rendered_out = render_response(tid="?", oper="RSP", data=f"hello {prompt}", prompt=self.devfid)
|
|
147
|
+
return True, rendered_out
|
|
148
|
+
# COMMAND EXECUTION
|
|
129
149
|
if lm_is_loaded(module):
|
|
130
150
|
try:
|
|
131
151
|
state, out = lm_exec(command)
|
|
@@ -134,39 +154,43 @@ class ESPNowSS:
|
|
|
134
154
|
return state, rendered_out
|
|
135
155
|
except Exception as e:
|
|
136
156
|
# Optionally log the exception here.
|
|
137
|
-
|
|
157
|
+
syslog(f"[ERR][_ESPNOW] {command}: {e}")
|
|
158
|
+
state, out = False, ""
|
|
138
159
|
else:
|
|
139
|
-
|
|
160
|
+
warning_msg = f"[WARN][_ESPNOW] NotAllowed {module}"
|
|
161
|
+
syslog(warning_msg)
|
|
162
|
+
rendered_out = render_response(tid="?", oper="RSP", data=warning_msg,
|
|
163
|
+
prompt=self.devfid)
|
|
164
|
+
state, out = True, rendered_out
|
|
140
165
|
return state, out
|
|
141
166
|
if operation == "RSP":
|
|
142
167
|
resp_data = request["data"]
|
|
143
168
|
ResponseRouter.update_response(mac, resp_data) # USE <tid> for proper session response mapping
|
|
144
|
-
|
|
169
|
+
#syslog(f"[_ESPNOW] No action, {request}")
|
|
170
|
+
return False, ""
|
|
145
171
|
|
|
146
172
|
async def _server(self, tag:str):
|
|
147
173
|
"""
|
|
148
174
|
ESPnow async listener task
|
|
149
175
|
:param tag: micro_task tag for task access
|
|
150
176
|
"""
|
|
151
|
-
|
|
177
|
+
|
|
178
|
+
with NativeTask.TASKS.get(tag, None) as my_task:
|
|
152
179
|
self.server_ready = True
|
|
153
180
|
my_task.out = "ESPNow receiver running"
|
|
154
181
|
async for mac, msg in self.espnow:
|
|
155
182
|
try:
|
|
156
|
-
|
|
157
|
-
if
|
|
183
|
+
reply, response = self._request_handler(msg, my_task, mac)
|
|
184
|
+
if reply:
|
|
158
185
|
await self.__asend_raw(mac, response)
|
|
159
|
-
else:
|
|
160
|
-
syslog(response)
|
|
161
186
|
except OSError as err:
|
|
162
187
|
# If the peer is not yet added, add it and retry.
|
|
163
188
|
if len(err.args) > 1 and err.args[1] == 'ESP_ERR_ESPNOW_NOT_FOUND':
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
189
|
+
# AUTOMATIC PEER REGISTRATION
|
|
190
|
+
self.espnow.add_peer(mac)
|
|
191
|
+
reply, response = self._request_handler(msg, my_task, mac)
|
|
192
|
+
if reply:
|
|
167
193
|
await self.__asend_raw(mac, response)
|
|
168
|
-
else:
|
|
169
|
-
syslog(response)
|
|
170
194
|
else:
|
|
171
195
|
# Optionally handle or log other OSErrors here.
|
|
172
196
|
syslog(f"[ERR][NOW SERVER] {err}")
|
|
@@ -178,7 +202,7 @@ class ESPNowSS:
|
|
|
178
202
|
# Create an asynchronous task with tag 'espnow.server'
|
|
179
203
|
tag = 'espnow.server'
|
|
180
204
|
state = NativeTask().create(callback=self._server(tag), tag=tag)
|
|
181
|
-
return "Starting" if state else "Already running"
|
|
205
|
+
return {tag: "Starting"} if state else {tag: "Already running"}
|
|
182
206
|
|
|
183
207
|
#----------- SEND METHODS --------------
|
|
184
208
|
async def __asend_raw(self, mac:bytes, msg:str):
|
|
@@ -191,7 +215,7 @@ class ESPNowSS:
|
|
|
191
215
|
"""
|
|
192
216
|
ESPNow client task: send a command to a peer and update task status.
|
|
193
217
|
"""
|
|
194
|
-
with
|
|
218
|
+
with NativeTask.TASKS.get(tag, None) as my_task:
|
|
195
219
|
router = ResponseRouter(peer)
|
|
196
220
|
# rendered_output: "{tid}|{oper}|{data}|{prompt}$"
|
|
197
221
|
rendered_out = render_response(tid="?", oper="REQ", data=msg, prompt=self.devfid)
|
|
@@ -207,7 +231,7 @@ class ESPNowSS:
|
|
|
207
231
|
peer = matches[0] if matches else None
|
|
208
232
|
return peer
|
|
209
233
|
|
|
210
|
-
def send(self, peer:bytes|str, msg:str) ->
|
|
234
|
+
def send(self, peer:bytes|str, msg:str) -> dict:
|
|
211
235
|
"""
|
|
212
236
|
Send a command over ESPNow.
|
|
213
237
|
:param peer: Binary MAC address of another device.
|
|
@@ -218,7 +242,7 @@ class ESPNowSS:
|
|
|
218
242
|
# Peer as device name (prompt)
|
|
219
243
|
_peer = self.mac_by_peer_name(peer)
|
|
220
244
|
if _peer is None:
|
|
221
|
-
return
|
|
245
|
+
return {peer: "Unknown device"}
|
|
222
246
|
peer_name = peer
|
|
223
247
|
peer = _peer
|
|
224
248
|
|
|
@@ -226,24 +250,62 @@ class ESPNowSS:
|
|
|
226
250
|
task_id = f"con.espnow.{peer_name}"
|
|
227
251
|
# Create an asynchronous sending task.
|
|
228
252
|
state = NativeTask().create(callback=self._asend_task(peer, task_id, msg), tag=task_id)
|
|
229
|
-
return "Starting" if state else "Already running"
|
|
253
|
+
return {task_id: "Starting"} if state else {task_id: "Already running"}
|
|
254
|
+
|
|
255
|
+
def cluster_send(self, msg):
|
|
256
|
+
"""
|
|
257
|
+
Send message for all peers
|
|
258
|
+
"""
|
|
259
|
+
_tasks = []
|
|
260
|
+
for peer_name in self.devices.values():
|
|
261
|
+
_tasks.append(self.send(peer_name, msg))
|
|
262
|
+
return _tasks
|
|
230
263
|
|
|
231
264
|
# ----------- OTHER METHODS --------------
|
|
232
265
|
|
|
233
|
-
def
|
|
266
|
+
async def _handshake(self, peer:bytes, tag:str):
|
|
234
267
|
"""
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
:param devfid: optional parameter to register dev uid for mac address
|
|
268
|
+
Handshake with peer
|
|
269
|
+
- with device caching
|
|
238
270
|
"""
|
|
239
|
-
|
|
240
|
-
self.
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
271
|
+
with NativeTask.TASKS.get(tag, None) as my_task:
|
|
272
|
+
if self.devices.get(peer) is None:
|
|
273
|
+
my_task.out = "ESPNow Add Peer"
|
|
274
|
+
try:
|
|
275
|
+
# PEER REGISTRATION
|
|
276
|
+
self.espnow.add_peer(peer)
|
|
277
|
+
except Exception as e:
|
|
278
|
+
my_task.out = f"ESPNow Peer Error: {e}"
|
|
279
|
+
return
|
|
280
|
+
my_task.out = "Handshake In Progress..."
|
|
281
|
+
sender = self.send(peer, "hello")
|
|
282
|
+
task_key = list(sender.keys())[0]
|
|
283
|
+
sender_task = NativeTask.TASKS.get(task_key, None)
|
|
284
|
+
result = await sender_task.await_result(timeout=10)
|
|
285
|
+
expected_response = f"hello {self.devfid}"
|
|
286
|
+
is_ok = False
|
|
287
|
+
if result == expected_response:
|
|
288
|
+
try:
|
|
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
|
+
my_task.out = f"Handshake: {result} from {self.devices.get(peer)} [{'OK' if is_ok else 'NOK'}]"
|
|
295
|
+
sender_task.cancel() # Delete sender task (cleanup)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def handshake(self, peer:bytes|str):
|
|
299
|
+
task_id = f"con.espnow.handshake"
|
|
300
|
+
# Create an asynchronous sending task.
|
|
301
|
+
if isinstance(peer, str) and ":" in peer:
|
|
302
|
+
# Convert 50:02:91:86:34:28 format to b'P\x02\x91\x864(' bytes
|
|
303
|
+
peer = bytes(int(x, 16) for x in peer.split(":"))
|
|
304
|
+
if isinstance(peer, bytes):
|
|
305
|
+
state = NativeTask().create(callback=self._handshake(peer, task_id), tag=task_id)
|
|
306
|
+
return {task_id: "Starting"} if state else {task_id: "Already running"}
|
|
307
|
+
else:
|
|
308
|
+
return {None: "Invalid MAC address format. Use 50:02:91:86:34:28 or b'P\\x02\\x91\\x864('"}
|
|
247
309
|
|
|
248
310
|
def stats(self):
|
|
249
311
|
"""
|
|
@@ -260,23 +322,3 @@ class ESPNowSS:
|
|
|
260
322
|
except Exception as e:
|
|
261
323
|
_peers = str(e)
|
|
262
324
|
return {"stats": _stats, "peers": _peers, "map": self.devices, "ready": self.server_ready}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
###################################################
|
|
266
|
-
# Control functions #
|
|
267
|
-
###################################################
|
|
268
|
-
INSTANCE = ESPNowSS()
|
|
269
|
-
|
|
270
|
-
def initialize():
|
|
271
|
-
# TODO: remove, use ESPNowSS() class instead
|
|
272
|
-
global INSTANCE
|
|
273
|
-
if INSTANCE is None:
|
|
274
|
-
INSTANCE = ESPNowSS()
|
|
275
|
-
return INSTANCE
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
def mac_address():
|
|
279
|
-
"""
|
|
280
|
-
Get the binary MAC address.
|
|
281
|
-
"""
|
|
282
|
-
return get_mac()
|
micrOS/source/Files.py
CHANGED
|
@@ -1,26 +1,43 @@
|
|
|
1
|
-
|
|
1
|
+
"""
|
|
2
|
+
Module is responsible high level micropython file system opeartions
|
|
3
|
+
[IMPORTANT] This module must never use any micrOS specific functions or classes.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from uos import ilistdir, remove, stat, getcwd, mkdir
|
|
7
|
+
from sys import path as upath
|
|
2
8
|
|
|
3
9
|
################################ Helper functions #####################################
|
|
4
10
|
|
|
5
|
-
def
|
|
11
|
+
def _filter(path:str='/', ext:tuple=None, prefix:tuple=None, hide_core:bool=True) -> bool:
|
|
6
12
|
"""
|
|
7
|
-
Filter
|
|
13
|
+
Filter files
|
|
8
14
|
:param path: file to check
|
|
9
|
-
:param
|
|
15
|
+
:param ext: tuple of extensions to filter by, default: None (all)
|
|
16
|
+
:param hide_core: hide core files (mpy, py), default: True
|
|
10
17
|
"""
|
|
11
|
-
# micrOS file types
|
|
12
|
-
allowed_exts = ('html', 'js', 'css', 'log', 'cache', 'dat')
|
|
13
|
-
mod_prefixes = ('LM', "IO")
|
|
14
18
|
fname = path.split("/")[-1]
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
_ext = fname.split(".")[-1]
|
|
20
|
+
if hide_core and _ext in ("mpy", "py") and not (fname.startswith("LM_") or fname.startswith("IO_")):
|
|
21
|
+
return False
|
|
22
|
+
if ext is None and prefix is None:
|
|
23
|
+
return True
|
|
24
|
+
if isinstance(prefix, tuple) and fname.split("_")[0] in prefix:
|
|
25
|
+
return True
|
|
26
|
+
if isinstance(ext, tuple) and fname.split(".")[-1] in ext:
|
|
27
|
+
return True
|
|
22
28
|
return False
|
|
23
29
|
|
|
30
|
+
def is_protected(path:str='/') -> bool:
|
|
31
|
+
"""
|
|
32
|
+
Check is file protected
|
|
33
|
+
- deny deletion
|
|
34
|
+
"""
|
|
35
|
+
protected_entities = ("", "node_config.json", "modules", "config", "logs", "web", "data",
|
|
36
|
+
"LM_pacman.mpy", "LM_system.mpy")
|
|
37
|
+
entity = path.split("/")[-1].replace("/", "")
|
|
38
|
+
if entity in protected_entities:
|
|
39
|
+
return True
|
|
40
|
+
return False
|
|
24
41
|
|
|
25
42
|
def _type_mask_to_str(item_type:int=None) -> str:
|
|
26
43
|
# Map the raw bit-mask to a single character
|
|
@@ -33,6 +50,19 @@ def _type_mask_to_str(item_type:int=None) -> str:
|
|
|
33
50
|
return item_type
|
|
34
51
|
|
|
35
52
|
########################### Public functions #############################
|
|
53
|
+
def is_dir(path):
|
|
54
|
+
try:
|
|
55
|
+
return stat(path)[0] & 0x4000
|
|
56
|
+
except OSError:
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def is_file(path):
|
|
61
|
+
try:
|
|
62
|
+
return stat(path)[0] & 0x8000
|
|
63
|
+
except OSError:
|
|
64
|
+
return False
|
|
65
|
+
|
|
36
66
|
|
|
37
67
|
def ilist_fs(path:str="/", type_filter:str='*', select:str='*', core:bool=False):
|
|
38
68
|
"""
|
|
@@ -52,7 +82,8 @@ def ilist_fs(path:str="/", type_filter:str='*', select:str='*', core:bool=False)
|
|
|
52
82
|
item_type = _type_mask_to_str(item_type)
|
|
53
83
|
if type_filter in ("*", item_type):
|
|
54
84
|
# Mods only
|
|
55
|
-
|
|
85
|
+
_select = None if select == "*" else (select,)
|
|
86
|
+
if item_type == 'f' and not _filter(item, prefix=_select, hide_core=not core):
|
|
56
87
|
continue
|
|
57
88
|
if select != '*' and item_type == 'd':
|
|
58
89
|
continue
|
|
@@ -78,33 +109,19 @@ def remove_fs(path, allow_dir=False):
|
|
|
78
109
|
:param allow_dir: enable directory deletion, default: False
|
|
79
110
|
"""
|
|
80
111
|
# protect some resources
|
|
81
|
-
if
|
|
82
|
-
return f'
|
|
83
|
-
|
|
84
|
-
if
|
|
112
|
+
if is_protected(path):
|
|
113
|
+
return f'{path} is protected, skip deletion'
|
|
114
|
+
_is_file = is_file(path)
|
|
115
|
+
if _is_file or allow_dir:
|
|
85
116
|
remove(path)
|
|
86
|
-
return f"
|
|
87
|
-
return f"
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def is_dir(path):
|
|
91
|
-
try:
|
|
92
|
-
return stat(path)[0] & 0x4000
|
|
93
|
-
except OSError:
|
|
94
|
-
return False
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
def is_file(path):
|
|
98
|
-
try:
|
|
99
|
-
return stat(path)[0] & 0x8000
|
|
100
|
-
except OSError:
|
|
101
|
-
return False
|
|
117
|
+
return f"{path} deleted"
|
|
118
|
+
return f"Cannot delete {'file' if _is_file else 'dir'}: {path}"
|
|
102
119
|
|
|
103
120
|
|
|
104
121
|
def path_join(*parts):
|
|
105
122
|
path = "/".join(part.strip("/") for part in parts if part)
|
|
106
123
|
if parts and parts[0].startswith("/"):
|
|
107
|
-
path = "/" + path
|
|
124
|
+
path = path if path.startswith("/") else "/" + path
|
|
108
125
|
return path
|
|
109
126
|
|
|
110
127
|
|
|
@@ -114,9 +131,28 @@ class OSPath:
|
|
|
114
131
|
LOGS = path_join(_ROOT, '/logs') # Logs (.log)
|
|
115
132
|
DATA = path_join(_ROOT,'/data') # Application data (.dat, .cache, etc.)
|
|
116
133
|
WEB = path_join(_ROOT,'/web') # Web resources (.html, .css, .js, .json, etc.)
|
|
117
|
-
MODULES = path_join(_ROOT, '/modules') # Application modules (.mpy, .py)
|
|
118
|
-
CONFIG = path_join(_ROOT, '/config') # System configuration files (node_config.json, etc.)
|
|
134
|
+
MODULES = path_join(_ROOT, '/modules') # Application modules (.mpy, .py)
|
|
135
|
+
CONFIG = path_join(_ROOT, '/config') # System configuration files (node_config.json, etc.)
|
|
136
|
+
|
|
119
137
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
138
|
+
def init_micros_dirs():
|
|
139
|
+
"""
|
|
140
|
+
Init micrOS root file system directories
|
|
141
|
+
"""
|
|
142
|
+
# ENABLE MODULES ACCESS
|
|
143
|
+
if OSPath.MODULES not in upath:
|
|
144
|
+
upath.insert(0, OSPath.MODULES)
|
|
145
|
+
|
|
146
|
+
root_dirs = [
|
|
147
|
+
getattr(OSPath, key)
|
|
148
|
+
for key in dir(OSPath)
|
|
149
|
+
if not key.startswith("_") and isinstance(getattr(OSPath, key), str)
|
|
150
|
+
]
|
|
151
|
+
print(f"[BOOT] rootFS validation: {root_dirs}")
|
|
152
|
+
for dir_path in root_dirs:
|
|
153
|
+
if not is_dir(dir_path):
|
|
154
|
+
try:
|
|
155
|
+
mkdir(dir_path)
|
|
156
|
+
print(f"[BOOT] init dir: {dir_path}")
|
|
157
|
+
except Exception as e:
|
|
158
|
+
print(f"[ERR][BOOT] cannot init dir {dir_path}: {e}")
|
micrOS/source/Hooks.py
CHANGED
|
@@ -21,8 +21,6 @@ from Config import cfgget, cfgput
|
|
|
21
21
|
from microIO import detect_platform
|
|
22
22
|
from Debug import console_write, syslog
|
|
23
23
|
from Tasks import exec_lm_pipe
|
|
24
|
-
from Files import OSPath, is_dir
|
|
25
|
-
from uos import mkdir
|
|
26
24
|
from micropython import mem_info
|
|
27
25
|
from machine import freq
|
|
28
26
|
try:
|
|
@@ -45,7 +43,6 @@ def bootup():
|
|
|
45
43
|
"""
|
|
46
44
|
# Execute LMs from boothook config parameter
|
|
47
45
|
console_write("[BOOT] EXECUTION...")
|
|
48
|
-
_init_micros_dirs()
|
|
49
46
|
bootasks = cfgget('boothook')
|
|
50
47
|
if bootasks is not None and bootasks.lower() != 'n/a':
|
|
51
48
|
console_write(f"|-[BOOT] TASKS: {bootasks}")
|
|
@@ -62,25 +59,6 @@ def bootup():
|
|
|
62
59
|
_tune_performance()
|
|
63
60
|
|
|
64
61
|
|
|
65
|
-
def _init_micros_dirs():
|
|
66
|
-
"""
|
|
67
|
-
Init micrOS root file system directories
|
|
68
|
-
"""
|
|
69
|
-
root_dirs = [
|
|
70
|
-
getattr(OSPath, key)
|
|
71
|
-
for key in dir(OSPath)
|
|
72
|
-
if not key.startswith("_") and isinstance(getattr(OSPath, key), str)
|
|
73
|
-
]
|
|
74
|
-
console_write(f"|-[BOOT] rootFS validation: {root_dirs}")
|
|
75
|
-
for dir_path in root_dirs:
|
|
76
|
-
if not is_dir(dir_path):
|
|
77
|
-
try:
|
|
78
|
-
mkdir(dir_path)
|
|
79
|
-
syslog(f"[BOOT] init dir: {dir_path}")
|
|
80
|
-
except Exception as e:
|
|
81
|
-
syslog(f"[ERR][BOOT] cannot init dir {dir_path}: {e}")
|
|
82
|
-
|
|
83
|
-
|
|
84
62
|
def _tune_queue_size():
|
|
85
63
|
"""
|
|
86
64
|
Tune queue size based on available ram
|
|
@@ -96,20 +74,26 @@ def _tune_queue_size():
|
|
|
96
74
|
|
|
97
75
|
|
|
98
76
|
def _tune_performance():
|
|
99
|
-
#
|
|
77
|
+
# {(platforms, ...): (min_clock, max_clock), ...}
|
|
78
|
+
cpu_clocks = {
|
|
79
|
+
('esp32c3', 'esp32c6'): (80_000_000, 160_000_000),
|
|
80
|
+
('esp32',): (160_000_000, 240_000_000), # default
|
|
81
|
+
}
|
|
100
82
|
platform = detect_platform()
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if platform
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
83
|
+
cpu_min_max = cpu_clocks[('esp32',)]
|
|
84
|
+
for platforms, clocks in cpu_clocks.items():
|
|
85
|
+
if platform in platforms:
|
|
86
|
+
cpu_min_max = clocks
|
|
87
|
+
break
|
|
88
|
+
# Set boosted (boost mode)
|
|
89
|
+
if cfgget('boostmd'):
|
|
90
|
+
max_hz = cpu_min_max[1]
|
|
91
|
+
console_write(f"[BOOT HOOKS] CPU boost mode ON: {max_hz} Hz")
|
|
92
|
+
freq(max_hz)
|
|
107
93
|
else:
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
elif 'esp32' in platform:
|
|
112
|
-
freq(160_000_000) # 160 Mhz / Half the max CPU clock
|
|
94
|
+
min_hz = cpu_min_max[0]
|
|
95
|
+
console_write(f"[BOOT HOOKS] CPU boost mode OFF: {min_hz} Hz")
|
|
96
|
+
freq(min_hz)
|
|
113
97
|
|
|
114
98
|
|
|
115
99
|
def profiling_info(label=""):
|
micrOS/source/Logger.py
CHANGED
|
@@ -6,7 +6,7 @@ Designed by Marcell Ban aka BxNxM
|
|
|
6
6
|
"""
|
|
7
7
|
from time import localtime
|
|
8
8
|
from re import match
|
|
9
|
-
from uos import remove
|
|
9
|
+
from uos import remove
|
|
10
10
|
from Files import OSPath, path_join, ilist_fs, is_dir
|
|
11
11
|
|
|
12
12
|
#############################################
|
|
@@ -16,12 +16,7 @@ from Files import OSPath, path_join, ilist_fs, is_dir
|
|
|
16
16
|
def _init_logger():
|
|
17
17
|
""" Init /logs folder """
|
|
18
18
|
if not is_dir(OSPath.LOGS):
|
|
19
|
-
|
|
20
|
-
mkdir(OSPath.LOGS)
|
|
21
|
-
syslog(f"[BOOT] log dir {OSPath.LOGS} init")
|
|
22
|
-
except Exception as e:
|
|
23
|
-
OSPath.LOGS = OSPath.ROOT
|
|
24
|
-
syslog(f"[BOOT] log dir {OSPath.LOGS} fallback: {e}")
|
|
19
|
+
OSPath.LOGS = OSPath._ROOT
|
|
25
20
|
return OSPath.LOGS
|
|
26
21
|
|
|
27
22
|
|