micrOSDevToolKit 2.1.5__py3-none-any.whl → 2.26.1__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.
- env/driver_cp210x/.DS_Store +0 -0
- env/driver_cp210x/macOS_VCP_Driver/SiLabsUSBDriverDisk.dmg +0 -0
- env/driver_cp210x/macOS_VCP_Driver/macOS_VCP_Driver_Release_Notes.txt +17 -1
- micrOS/micropython/esp32-20251209-v1.27.0.bin +0 -0
- micrOS/micropython/esp32c3-20251209-v1.27.0.bin +0 -0
- micrOS/micropython/esp32c6-20251209-v1.27.0.bin +0 -0
- micrOS/micropython/esp32s2-20251209-v1.27.0.bin +0 -0
- micrOS/micropython/esp32s2-LOLIN_MINI-20251209-v1.27.0.bin +0 -0
- micrOS/micropython/esp32s3-4MBflash-20241129-v1.24.1.bin +0 -0
- micrOS/micropython/esp32s3-8MBflash-20251209-v1.27.0.bin +0 -0
- micrOS/micropython/esp32s3_spiram_oct-20251209-v1.27.0.bin +0 -0
- micrOS/micropython/rpi-pico-w-20251209-v1.27.0.uf2 +0 -0
- micrOS/micropython/tinypico-20251209-v1.27.0.bin +0 -0
- micrOS/release_info/micrOS_ReleaseInfo/system_analysis_sum.json +191 -151
- micrOS/source/Auth.py +37 -0
- micrOS/source/Common.py +376 -102
- micrOS/source/Config.py +55 -25
- micrOS/source/Debug.py +54 -193
- micrOS/source/Espnow.py +404 -0
- micrOS/source/Files.py +207 -0
- micrOS/source/Hooks.py +88 -16
- micrOS/source/InterConnect.py +130 -46
- micrOS/source/Interrupts.py +8 -8
- micrOS/source/Logger.py +131 -0
- micrOS/source/Network.py +41 -21
- micrOS/source/Notify.py +74 -198
- micrOS/source/Pacman.py +326 -0
- micrOS/source/Scheduler.py +18 -55
- micrOS/source/Server.py +84 -217
- micrOS/source/Shell.py +103 -93
- micrOS/source/Tasks.py +239 -173
- micrOS/source/Time.py +21 -22
- micrOS/source/Types.py +89 -54
- micrOS/source/Web.py +485 -0
- micrOS/source/__pycache__/Common.cpython-312.pyc +0 -0
- micrOS/source/__pycache__/Debug.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__/Scheduler.cpython-312.pyc +0 -0
- micrOS/source/__pycache__/Server.cpython-312.pyc +0 -0
- micrOS/source/__pycache__/Shell.cpython-312.pyc +0 -0
- micrOS/source/__pycache__/replhelper.cpython-312.pyc +0 -0
- micrOS/source/helpers.py +132 -0
- micrOS/source/micrOS.py +25 -21
- micrOS/source/micrOSloader.py +14 -23
- micrOS/source/microIO.py +94 -57
- toolkit/simulator_lib/LP_darwin.py → micrOS/source/modules/IO_esp32.py +22 -11
- micrOS/source/{IO_esp32c3.py → modules/IO_esp32c3.py} +6 -1
- micrOS/source/modules/IO_esp32c6.py +38 -0
- micrOS/source/{IO_esp32s2.py → modules/IO_esp32s2.py} +6 -1
- micrOS/source/{IO_esp32s3.py → modules/IO_esp32s3.py} +43 -2
- micrOS/source/modules/IO_m5stamp.py +86 -0
- micrOS/source/{IO_qtpy.py → modules/IO_qtpy.py} +28 -18
- micrOS/source/{IO_tinypico.py → modules/IO_tinypico.py} +48 -3
- micrOS/source/modules/LM_L298N.py +161 -0
- {toolkit/workspace/precompiled → micrOS/source/modules}/LM_L9110_DCmotor.py +4 -4
- micrOS/source/{LM_OV2640.py → modules/LM_OV2640.py} +53 -42
- micrOS/source/{LM_VL53L0X.py → modules/LM_VL53L0X.py} +5 -5
- micrOS/source/{LM_aht10.py → modules/LM_aht10.py} +12 -4
- micrOS/source/{LM_bme280.py → modules/LM_bme280.py} +13 -25
- micrOS/source/{LM_buzzer.py → modules/LM_buzzer.py} +42 -40
- micrOS/source/{LM_cct.py → modules/LM_cct.py} +22 -27
- micrOS/source/modules/LM_cluster.py +255 -0
- micrOS/source/{LM_co2.py → modules/LM_co2.py} +13 -6
- micrOS/source/{LM_dht11.py → modules/LM_dht11.py} +13 -29
- micrOS/source/{LM_dht22.py → modules/LM_dht22.py} +13 -28
- micrOS/source/{LM_dimmer.py → modules/LM_dimmer.py} +19 -16
- micrOS/source/modules/LM_distance.py +135 -0
- micrOS/source/{LM_ds18.py → modules/LM_ds18.py} +12 -4
- micrOS/source/{LM_esp32.py → modules/LM_esp32.py} +16 -4
- micrOS/source/modules/LM_espnow.py +53 -0
- micrOS/source/modules/LM_fileserver.py +265 -0
- micrOS/source/{LM_gameOfLife.py → modules/LM_gameOfLife.py} +5 -5
- micrOS/source/{LM_genIO.py → modules/LM_genIO.py} +49 -35
- micrOS/source/modules/LM_haptic.py +111 -0
- micrOS/source/modules/LM_i2c.py +61 -0
- micrOS/source/{LM_i2s_mic.py → modules/LM_i2s_mic.py} +20 -23
- micrOS/source/{LM_ld2410.py → modules/LM_ld2410.py} +3 -3
- micrOS/source/{LM_light_sensor.py → modules/LM_light_sensor.py} +22 -26
- micrOS/source/modules/LM_mh_z19c.py +198 -0
- micrOS/source/modules/LM_neoeffects.py +284 -0
- micrOS/source/{LM_neopixel.py → modules/LM_neopixel.py} +26 -31
- micrOS/source/{LM_oled.py → modules/LM_oled.py} +28 -20
- micrOS/source/{LM_oled_sh1106.py → modules/LM_oled_sh1106.py} +28 -24
- micrOS/source/{LM_oled_ui.py → modules/LM_oled_ui.py} +132 -174
- micrOS/source/modules/LM_pacman.py +320 -0
- micrOS/source/{LM_presence.py → modules/LM_presence.py} +24 -36
- micrOS/source/modules/LM_qmi8658.py +204 -0
- micrOS/source/{LM_rencoder.py → modules/LM_rencoder.py} +40 -11
- micrOS/source/modules/LM_rest.py +81 -0
- micrOS/source/{LM_rgb.py → modules/LM_rgb.py} +25 -34
- micrOS/source/{LM_rgbcct.py → modules/LM_rgbcct.py} +5 -5
- micrOS/source/{LM_roboarm.py → modules/LM_roboarm.py} +37 -45
- micrOS/source/modules/LM_robustness.py +137 -0
- micrOS/source/{LM_rp2w.py → modules/LM_rp2w.py} +3 -0
- micrOS/source/{LM_sdcard.py → modules/LM_sdcard.py} +3 -0
- micrOS/source/{LM_servo.py → modules/LM_servo.py} +4 -4
- micrOS/source/modules/LM_sound_event.py +751 -0
- micrOS/source/{LM_stepper.py → modules/LM_stepper.py} +8 -8
- micrOS/source/{LM_switch.py → modules/LM_switch.py} +21 -18
- micrOS/source/{LM_system.py → modules/LM_system.py} +96 -59
- micrOS/source/modules/LM_tcs3472.py +187 -0
- micrOS/source/modules/LM_telegram.py +388 -0
- micrOS/source/modules/LM_trackball.py +287 -0
- micrOS/source/modules/LM_veml7700.py +159 -0
- micrOS/source/modules/LM_web.py +38 -0
- micrOS/source/urequests.py +204 -91
- {toolkit/workspace/precompiled → micrOS/source/web}/dashboard.html +9 -4
- micrOS/source/web/editor.js +440 -0
- micrOS/source/web/filesui.html +178 -0
- micrOS/source/web/filesui.js +338 -0
- micrOS/source/{index.html → web/index.html} +44 -2
- micrOS/source/web/uapi.js +103 -0
- micrOS/source/web/udashboard.js +129 -0
- micrOS/source/web/ustyle.css +55 -0
- micrOS/source/web/uwidgets.js +172 -0
- micrOS/source/web/uwidgets_pro.js +99 -0
- micrOS/utests/__init__.py +0 -0
- micrOS/utests/test_scheduler.py +435 -0
- {micrOSDevToolKit-2.1.5.data → microsdevtoolkit-2.26.1.data}/scripts/devToolKit.py +47 -4
- {micrOSDevToolKit-2.1.5.dist-info → microsdevtoolkit-2.26.1.dist-info}/METADATA +392 -279
- microsdevtoolkit-2.26.1.dist-info/RECORD +396 -0
- {micrOSDevToolKit-2.1.5.dist-info → microsdevtoolkit-2.26.1.dist-info}/WHEEL +1 -1
- toolkit/DevEnvCompile.py +63 -33
- toolkit/DevEnvOTA.py +72 -22
- toolkit/DevEnvUSB.py +147 -77
- toolkit/Gateway.py +9 -9
- toolkit/LM_to_compile.dat +12 -4
- toolkit/MicrOSDevEnv.py +129 -51
- toolkit/WebRepl.py +73 -0
- toolkit/dashboard_apps/BackupRestore.py +171 -0
- toolkit/dashboard_apps/CCTDemo.py +12 -17
- toolkit/dashboard_apps/CCTTest.py +20 -24
- toolkit/dashboard_apps/CamStream.py +2 -6
- toolkit/dashboard_apps/CatGame.py +14 -16
- toolkit/dashboard_apps/Dimmer.py +11 -21
- toolkit/dashboard_apps/GetVersion.py +11 -19
- toolkit/dashboard_apps/MicrophoneTest.py +2 -7
- toolkit/dashboard_apps/NeoEffectsDemo.py +22 -35
- toolkit/dashboard_apps/NeopixelTest.py +20 -25
- toolkit/dashboard_apps/PresenceTest.py +2 -8
- toolkit/dashboard_apps/QMI8685_GYRO.py +68 -0
- toolkit/dashboard_apps/RGBTest.py +20 -24
- toolkit/dashboard_apps/RoboArm.py +24 -32
- toolkit/dashboard_apps/SED_test.py +10 -14
- toolkit/dashboard_apps/SensorsTest.py +61 -0
- toolkit/dashboard_apps/SystemTest.py +219 -117
- toolkit/dashboard_apps/Template_app.py +12 -19
- toolkit/dashboard_apps/_app_base.py +34 -0
- toolkit/dashboard_apps/_gyro_visualizer.py +78 -0
- toolkit/dashboard_apps/uLightDemo.py +15 -24
- toolkit/index.html +6 -5
- toolkit/lib/LocalMachine.py +6 -1
- toolkit/lib/MicrosFiles.py +46 -0
- toolkit/lib/Repository.py +64 -0
- toolkit/lib/TerminalColors.py +4 -0
- toolkit/lib/macroScript.py +371 -0
- toolkit/lib/micrOSClient.py +124 -51
- toolkit/lib/micrOSClientHistory.py +156 -0
- toolkit/lib/pip_package_installer.py +31 -4
- toolkit/micrOSdashboard.py +16 -21
- toolkit/micrOSlint.py +28 -10
- toolkit/simulator_lib/.DS_Store +0 -0
- micrOS/source/IO_esp32.py → toolkit/simulator_lib/IO_darwin.py +3 -0
- toolkit/simulator_lib/__pycache__/IO_darwin.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/aioespnow.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/camera.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/framebuf.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/machine.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/micropython.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/mip.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/neopixel.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/network.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/sim_common.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/simgc.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/simulator.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/uasyncio.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/uos.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/urandom.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/usocket.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/ussl.cpython-312.pyc +0 -0
- toolkit/simulator_lib/aioespnow.py +28 -0
- toolkit/simulator_lib/camera.py +84 -0
- toolkit/simulator_lib/dht.py +1 -1
- toolkit/simulator_lib/framebuf.py +49 -1
- toolkit/simulator_lib/machine.py +32 -2
- toolkit/simulator_lib/micropython.py +3 -3
- toolkit/simulator_lib/mip.py +165 -0
- toolkit/simulator_lib/neopixel.py +3 -2
- toolkit/simulator_lib/network.py +2 -1
- toolkit/simulator_lib/node_config.json +2 -3
- toolkit/simulator_lib/ntptime.py +1 -1
- toolkit/simulator_lib/{sim_console.py → sim_common.py} +2 -3
- toolkit/simulator_lib/simgc.py +6 -2
- toolkit/simulator_lib/simulator.py +138 -46
- toolkit/simulator_lib/uasyncio.py +34 -3
- toolkit/simulator_lib/uos.py +147 -0
- toolkit/simulator_lib/urandom.py +4 -0
- toolkit/simulator_lib/usocket.py +5 -1
- toolkit/simulator_lib/view01.jpg +0 -0
- toolkit/simulator_lib/view02.jpg +0 -0
- toolkit/socketClient.py +43 -23
- toolkit/user_data/webhooks/generic.py +1 -1
- toolkit/user_data/webhooks/macro.py +44 -0
- toolkit/user_data/webhooks/template.macro +20 -0
- toolkit/user_data/webhooks/template.py +1 -1
- toolkit/workspace/precompiled/Auth.mpy +0 -0
- toolkit/workspace/precompiled/Common.mpy +0 -0
- toolkit/workspace/precompiled/Config.mpy +0 -0
- toolkit/workspace/precompiled/Debug.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/InterConnect.mpy +0 -0
- toolkit/workspace/precompiled/Interrupts.mpy +0 -0
- toolkit/workspace/precompiled/Logger.mpy +0 -0
- toolkit/workspace/precompiled/Network.mpy +0 -0
- toolkit/workspace/precompiled/Notify.mpy +0 -0
- toolkit/workspace/precompiled/Pacman.mpy +0 -0
- toolkit/workspace/precompiled/Scheduler.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/Time.mpy +0 -0
- toolkit/workspace/precompiled/Types.mpy +0 -0
- toolkit/workspace/precompiled/Web.mpy +0 -0
- toolkit/workspace/precompiled/_mpy.version +1 -1
- toolkit/workspace/precompiled/config/_git.keep +0 -0
- toolkit/workspace/precompiled/helpers.mpy +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/modules/IO_esp32.mpy +0 -0
- toolkit/workspace/precompiled/modules/IO_esp32c3.mpy +0 -0
- toolkit/workspace/precompiled/modules/IO_esp32c6.mpy +0 -0
- toolkit/workspace/precompiled/modules/IO_esp32s2.mpy +0 -0
- toolkit/workspace/precompiled/modules/IO_esp32s3.mpy +0 -0
- toolkit/workspace/precompiled/modules/IO_m5stamp.mpy +0 -0
- toolkit/workspace/precompiled/modules/IO_qtpy.mpy +0 -0
- toolkit/workspace/precompiled/modules/IO_rp2.mpy +0 -0
- toolkit/workspace/precompiled/modules/IO_tinypico.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_L298N.mpy +0 -0
- {micrOS/source → toolkit/workspace/precompiled/modules}/LM_L9110_DCmotor.py +4 -4
- toolkit/workspace/precompiled/modules/LM_OV2640.mpy +0 -0
- toolkit/workspace/precompiled/{LM_VL53L0X.py → modules/LM_VL53L0X.py} +5 -5
- toolkit/workspace/precompiled/modules/LM_aht10.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_bme280.mpy +0 -0
- toolkit/workspace/precompiled/{LM_buzzer.mpy → modules/LM_buzzer.mpy} +0 -0
- toolkit/workspace/precompiled/modules/LM_cct.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_cluster.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_co2.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_dht11.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_dht22.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_ds18.mpy +0 -0
- toolkit/workspace/precompiled/{LM_esp32.py → modules/LM_esp32.py} +16 -4
- toolkit/workspace/precompiled/modules/LM_espnow.py +53 -0
- toolkit/workspace/precompiled/modules/LM_fileserver.mpy +0 -0
- toolkit/workspace/precompiled/{LM_gameOfLife.mpy → modules/LM_gameOfLife.mpy} +0 -0
- toolkit/workspace/precompiled/modules/LM_genIO.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_haptic.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_i2c.py +61 -0
- toolkit/workspace/precompiled/modules/LM_i2s_mic.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_mh_z19c.py +198 -0
- toolkit/workspace/precompiled/modules/LM_neoeffects.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_neopixel.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_oled.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_oled_sh1106.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_oled_ui.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_qmi8658.py +204 -0
- toolkit/workspace/precompiled/{LM_rencoder.py → modules/LM_rencoder.py} +40 -11
- toolkit/workspace/precompiled/modules/LM_rest.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_rgb.mpy +0 -0
- toolkit/workspace/precompiled/{LM_rgbcct.mpy → modules/LM_rgbcct.mpy} +0 -0
- toolkit/workspace/precompiled/modules/LM_roboarm.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_robustness.py +137 -0
- toolkit/workspace/precompiled/{LM_rp2w.py → modules/LM_rp2w.py} +3 -0
- toolkit/workspace/precompiled/{LM_sdcard.py → modules/LM_sdcard.py} +3 -0
- toolkit/workspace/precompiled/{LM_servo.mpy → modules/LM_servo.mpy} +0 -0
- toolkit/workspace/precompiled/modules/LM_sound_event.mpy +0 -0
- toolkit/workspace/precompiled/{LM_stepper.mpy → modules/LM_stepper.mpy} +0 -0
- toolkit/workspace/precompiled/modules/LM_switch.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_system.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_tcs3472.py +187 -0
- toolkit/workspace/precompiled/modules/LM_telegram.mpy +0 -0
- toolkit/workspace/precompiled/{LM_tinyrgb.mpy → modules/LM_tinyrgb.mpy} +0 -0
- toolkit/workspace/precompiled/modules/LM_trackball.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_veml7700.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_web.mpy +0 -0
- toolkit/workspace/precompiled/urequests.mpy +0 -0
- {micrOS/source → toolkit/workspace/precompiled/web}/dashboard.html +9 -4
- toolkit/workspace/precompiled/web/editor.js +440 -0
- toolkit/workspace/precompiled/web/filesui.html +178 -0
- toolkit/workspace/precompiled/web/filesui.js +338 -0
- toolkit/workspace/precompiled/{index.html → web/index.html} +44 -2
- toolkit/workspace/precompiled/web/uapi.js +103 -0
- toolkit/workspace/precompiled/web/udashboard.js +129 -0
- toolkit/workspace/precompiled/web/ustyle.css +55 -0
- toolkit/workspace/precompiled/web/uwidgets.js +172 -0
- toolkit/workspace/precompiled/web/uwidgets_pro.js +99 -0
- env/driver_cp210x/CH34XSER_MAC/CH34X_DRV_INSTALL_INSTRUCTIONS.pdf +0 -0
- env/driver_cp210x/CH34XSER_MAC/CH34xVCPDriver.pkg +0 -0
- micrOS/micropython/esp32-20231005-v1.21.0.bin +0 -0
- micrOS/micropython/esp32c3-GENERIC-20240105-v1.22.1.bin +0 -0
- micrOS/micropython/esp32c3-GENERIC-20240222-v1.22.2.bin +0 -0
- micrOS/micropython/esp32s2-GENERIC-20240105-v1.22.1.bin +0 -0
- micrOS/micropython/esp32s2-LOLIN_MINI-20220618-v1.19.1.bin +0 -0
- micrOS/micropython/esp32s3-GENERIC-20240105-v1.22.1.bin +0 -0
- micrOS/micropython/esp32s3_spiram_oct-20231005-v1.21.0.bin +0 -0
- micrOS/micropython/rpi-pico-w-20231005-v1.21.0.uf2 +0 -0
- micrOS/micropython/tinypico-20231005-v1.21.0.bin +0 -0
- micrOS/micropython/tinypico-usbc-UM-20240105-v1.22.1.bin +0 -0
- micrOS/source/LM_L298N_DCmotor.py +0 -86
- micrOS/source/LM_catgame.py +0 -74
- micrOS/source/LM_dashboard_be.py +0 -37
- micrOS/source/LM_demo.py +0 -85
- micrOS/source/LM_distance.py +0 -88
- micrOS/source/LM_i2c.py +0 -44
- micrOS/source/LM_intercon.py +0 -57
- micrOS/source/LM_keychain.py +0 -318
- micrOS/source/LM_lmpacman.py +0 -126
- micrOS/source/LM_neoeffects.py +0 -327
- micrOS/source/LM_pet_feeder.py +0 -76
- micrOS/source/LM_ph_sensor.py +0 -51
- micrOS/source/LM_rest.py +0 -40
- micrOS/source/LM_robustness.py +0 -73
- micrOS/source/LM_telegram.py +0 -96
- micrOS/source/reset.py +0 -11
- micrOS/source/uapi.js +0 -76
- micrOS/source/udashboard.js +0 -137
- micrOS/source/ustyle.css +0 -28
- micrOS/source/uwidgets.js +0 -179
- micrOSDevToolKit-2.1.5.dist-info/RECORD +0 -337
- toolkit/dashboard_apps/AirQualityBME280.py +0 -36
- toolkit/dashboard_apps/AirQualityDHT22_CO2.py +0 -36
- toolkit/lib/file_extensions.py +0 -16
- toolkit/simulator_lib/__pycache__/LP_darwin.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/LP_darwin.cpython-38.pyc +0 -0
- toolkit/simulator_lib/__pycache__/LP_darwin.cpython-39.pyc +0 -0
- toolkit/simulator_lib/__pycache__/sim_console.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/sim_console.cpython-38.pyc +0 -0
- toolkit/simulator_lib/__pycache__/sim_console.cpython-39.pyc +0 -0
- toolkit/workspace/precompiled/IO_esp32.mpy +0 -0
- toolkit/workspace/precompiled/IO_esp32c3.mpy +0 -0
- toolkit/workspace/precompiled/IO_esp32s2.mpy +0 -0
- toolkit/workspace/precompiled/IO_esp32s3.mpy +0 -0
- toolkit/workspace/precompiled/IO_qtpy.mpy +0 -0
- toolkit/workspace/precompiled/IO_rp2.mpy +0 -0
- toolkit/workspace/precompiled/IO_tinypico.mpy +0 -0
- toolkit/workspace/precompiled/LM_L298N_DCmotor.mpy +0 -0
- toolkit/workspace/precompiled/LM_OV2640.mpy +0 -0
- toolkit/workspace/precompiled/LM_aht10.mpy +0 -0
- toolkit/workspace/precompiled/LM_bme280.mpy +0 -0
- toolkit/workspace/precompiled/LM_catgame.py +0 -74
- toolkit/workspace/precompiled/LM_cct.mpy +0 -0
- toolkit/workspace/precompiled/LM_co2.mpy +0 -0
- toolkit/workspace/precompiled/LM_dashboard_be.py +0 -37
- toolkit/workspace/precompiled/LM_demo.py +0 -85
- toolkit/workspace/precompiled/LM_dht11.mpy +0 -0
- toolkit/workspace/precompiled/LM_dht22.mpy +0 -0
- toolkit/workspace/precompiled/LM_dimmer.mpy +0 -0
- toolkit/workspace/precompiled/LM_distance.py +0 -88
- toolkit/workspace/precompiled/LM_ds18.mpy +0 -0
- toolkit/workspace/precompiled/LM_genIO.mpy +0 -0
- toolkit/workspace/precompiled/LM_i2c.py +0 -44
- toolkit/workspace/precompiled/LM_i2s_mic.mpy +0 -0
- toolkit/workspace/precompiled/LM_intercon.mpy +0 -0
- toolkit/workspace/precompiled/LM_keychain.mpy +0 -0
- toolkit/workspace/precompiled/LM_light_sensor.mpy +0 -0
- toolkit/workspace/precompiled/LM_lmpacman.mpy +0 -0
- toolkit/workspace/precompiled/LM_neoeffects.mpy +0 -0
- toolkit/workspace/precompiled/LM_neopixel.mpy +0 -0
- toolkit/workspace/precompiled/LM_oled.mpy +0 -0
- toolkit/workspace/precompiled/LM_oled_sh1106.mpy +0 -0
- toolkit/workspace/precompiled/LM_oled_ui.mpy +0 -0
- toolkit/workspace/precompiled/LM_pet_feeder.py +0 -76
- toolkit/workspace/precompiled/LM_ph_sensor.py +0 -51
- toolkit/workspace/precompiled/LM_presence.mpy +0 -0
- toolkit/workspace/precompiled/LM_rest.mpy +0 -0
- toolkit/workspace/precompiled/LM_rgb.mpy +0 -0
- toolkit/workspace/precompiled/LM_roboarm.mpy +0 -0
- toolkit/workspace/precompiled/LM_robustness.py +0 -73
- toolkit/workspace/precompiled/LM_switch.mpy +0 -0
- toolkit/workspace/precompiled/LM_system.mpy +0 -0
- toolkit/workspace/precompiled/LM_telegram.mpy +0 -0
- toolkit/workspace/precompiled/reset.mpy +0 -0
- toolkit/workspace/precompiled/uapi.js +0 -76
- toolkit/workspace/precompiled/udashboard.js +0 -137
- toolkit/workspace/precompiled/ustyle.css +0 -28
- toolkit/workspace/precompiled/uwidgets.js +0 -179
- /toolkit/user_data/node_config_archive/.include → /micrOS/source/config/_git.keep +0 -0
- /micrOS/source/{IO_rp2.py → modules/IO_rp2.py} +0 -0
- /micrOS/source/{LM_tinyrgb.py → modules/LM_tinyrgb.py} +0 -0
- {micrOSDevToolKit-2.1.5.dist-info → microsdevtoolkit-2.26.1.dist-info/licenses}/LICENSE +0 -0
- {micrOSDevToolKit-2.1.5.dist-info → microsdevtoolkit-2.26.1.dist-info}/top_level.txt +0 -0
micrOS/source/Espnow.py
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
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
|
+
|
|
14
|
+
from binascii import hexlify
|
|
15
|
+
from json import load, dump
|
|
16
|
+
import uasyncio as asyncio
|
|
17
|
+
from urandom import getrandbits
|
|
18
|
+
from aioespnow import AIOESPNow
|
|
19
|
+
|
|
20
|
+
from Tasks import NativeTask, lm_exec, lm_is_loaded
|
|
21
|
+
from Config import cfgget
|
|
22
|
+
from Debug import syslog
|
|
23
|
+
from Files import OSPath, path_join, is_file
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# ----------- PARSE AND RENDER MSG PROTOCOL --------------
|
|
27
|
+
|
|
28
|
+
def render_packet(tid: str, oper: str, data: str, prompt: str) -> str:
|
|
29
|
+
"""
|
|
30
|
+
Render ESPNow custom message (protocol)
|
|
31
|
+
"""
|
|
32
|
+
if oper not in ("REQ", "RSP"):
|
|
33
|
+
syslog(f"[ERR] espnow render_response, unknown oper: {oper}")
|
|
34
|
+
return f"{tid}|{oper}|{str(data)}|{prompt}$"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def parse_packet(msg: bytes) -> tuple[bool, dict | str]:
|
|
38
|
+
"""
|
|
39
|
+
Parse ESPNow custom message protocol
|
|
40
|
+
"""
|
|
41
|
+
try:
|
|
42
|
+
msg = msg.decode('utf-8').strip()
|
|
43
|
+
except UnicodeError:
|
|
44
|
+
return False, "Invalid encoding"
|
|
45
|
+
# strip the trailing '$' then split on '|'
|
|
46
|
+
parts = msg.rstrip("$").split("|")
|
|
47
|
+
if len(parts) == 4:
|
|
48
|
+
return True, {"tid": parts[0],
|
|
49
|
+
"oper": parts[1],
|
|
50
|
+
"data": parts[2],
|
|
51
|
+
"prompt": parts[3]}
|
|
52
|
+
return False, f"Missing 4 args: {msg}"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_command_module(request):
|
|
56
|
+
if isinstance(request, dict):
|
|
57
|
+
command = request["data"].split()
|
|
58
|
+
module = command[0]
|
|
59
|
+
elif isinstance(request, str):
|
|
60
|
+
command = request.split()
|
|
61
|
+
module = command[0]
|
|
62
|
+
else:
|
|
63
|
+
command = []
|
|
64
|
+
module = ""
|
|
65
|
+
return command, module
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def generate_tid() -> str:
|
|
69
|
+
"""
|
|
70
|
+
Generate a secure, random transaction ID (tid).
|
|
71
|
+
Returns an 8-byte hex string.
|
|
72
|
+
"""
|
|
73
|
+
return hexlify(bytes([getrandbits(8) for _ in range(8)])).decode()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# ----------- ESPNOW SESSION SERVER - LISTENER AND SENDER --------------
|
|
77
|
+
class ResponseRouter:
|
|
78
|
+
"""
|
|
79
|
+
Response Router (by mac address)
|
|
80
|
+
to connect sender task with receiver loop (aka server)
|
|
81
|
+
"""
|
|
82
|
+
_routes: dict[tuple[bytes, str], "ResponseRouter"] = {}
|
|
83
|
+
|
|
84
|
+
def __init__(self, mac: bytes, tid: str):
|
|
85
|
+
self.mac = mac
|
|
86
|
+
self.tid = tid
|
|
87
|
+
self.response = None
|
|
88
|
+
self._event = asyncio.Event()
|
|
89
|
+
ResponseRouter._routes[(mac, tid)] = self
|
|
90
|
+
|
|
91
|
+
async def get_response(self, timeout: int=10) -> str|dict:
|
|
92
|
+
"""Wait for one response, then clear the event for reuse."""
|
|
93
|
+
try:
|
|
94
|
+
await asyncio.wait_for(self._event.wait(), timeout)
|
|
95
|
+
except asyncio.TimeoutError:
|
|
96
|
+
return "Timeout has beed exceeded"
|
|
97
|
+
self._event.clear()
|
|
98
|
+
return self.response
|
|
99
|
+
|
|
100
|
+
@staticmethod
|
|
101
|
+
def update_response(mac: bytes, tid: str, response: str) -> None:
|
|
102
|
+
router = ResponseRouter._routes.get((mac, tid), None)
|
|
103
|
+
if router is None:
|
|
104
|
+
syslog(f"[WARN][ESPNOW] No response route for {(mac, tid)}")
|
|
105
|
+
return
|
|
106
|
+
router.response = response
|
|
107
|
+
router._event.set()
|
|
108
|
+
|
|
109
|
+
def close(self) -> None:
|
|
110
|
+
"""Remove routing entry when done."""
|
|
111
|
+
ResponseRouter._routes.pop((self.mac, self.tid), None)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class ESPNowSS:
|
|
115
|
+
"""
|
|
116
|
+
ESPNow Session Server
|
|
117
|
+
"""
|
|
118
|
+
_instance = None
|
|
119
|
+
|
|
120
|
+
def __new__(cls, *args, **kwargs):
|
|
121
|
+
# SINGLETON PATTERN
|
|
122
|
+
if cls._instance is None:
|
|
123
|
+
# first time: actually create it
|
|
124
|
+
cls._instance = super().__new__(cls)
|
|
125
|
+
return cls._instance
|
|
126
|
+
|
|
127
|
+
def __init__(self):
|
|
128
|
+
# __init__ still runs each time, so guard if needed
|
|
129
|
+
if not hasattr(self, '_initialized'):
|
|
130
|
+
self._initialized = True
|
|
131
|
+
self.espnow = AIOESPNow() # Instance with async support
|
|
132
|
+
self.espnow.active(True)
|
|
133
|
+
self.devfid = cfgget('devfid')
|
|
134
|
+
self.__auth = cfgget('auth')
|
|
135
|
+
self.devices: dict[bytes, str] = {} # mapping for { "mac address": "devfid" } pairs
|
|
136
|
+
self.server_ready = False
|
|
137
|
+
self.peer_cache_path = path_join(OSPath.DATA, "espnow_peers.app_json")
|
|
138
|
+
self._load_peers()
|
|
139
|
+
|
|
140
|
+
def _load_peers(self):
|
|
141
|
+
if not is_file(self.peer_cache_path):
|
|
142
|
+
return
|
|
143
|
+
try:
|
|
144
|
+
with open(self.peer_cache_path, 'r', encoding='utf-8') as f:
|
|
145
|
+
devices_map = load(f)
|
|
146
|
+
self.devices = {bytes(k): v for k, v in devices_map}
|
|
147
|
+
for mac in self.devices:
|
|
148
|
+
# PEER REGISTRATION
|
|
149
|
+
self.espnow.add_peer(mac)
|
|
150
|
+
except Exception as e:
|
|
151
|
+
syslog(f"[ERR][ESPNOW] Loading peers: {e}")
|
|
152
|
+
|
|
153
|
+
# ----------- SERVER METHODS --------------
|
|
154
|
+
def _request_handler(self, msg: bytes, my_task: NativeTask, mac: bytes):
|
|
155
|
+
"""
|
|
156
|
+
Handle server input message (request), with REQ/RSP types (oper)
|
|
157
|
+
oper==REQ - command execution
|
|
158
|
+
oper==RSP - command response
|
|
159
|
+
:param msg: valid message format> "{tid}|{oper}|{data}|{prompt}$", {data} is the user input
|
|
160
|
+
:param my_task: Server task instance, for my_task.out update
|
|
161
|
+
:param mac: sender binary mac address
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
state, request = parse_packet(msg)
|
|
165
|
+
if not state:
|
|
166
|
+
my_task.out = f"[_ESPNOW] {request}"
|
|
167
|
+
return state, request
|
|
168
|
+
|
|
169
|
+
# parsed request: {"tid": "n/a", "oper": "n/a", "data": "n/a", "prompt": "n/a"}
|
|
170
|
+
operation, prompt, tid = request["oper"], request["prompt"], request["tid"]
|
|
171
|
+
my_task.out = f"[{tid}] {operation} from {prompt}"
|
|
172
|
+
# Update known devices
|
|
173
|
+
self.devices[mac] = request["prompt"]
|
|
174
|
+
|
|
175
|
+
# Check if the module/command is allowed., check oper==REQ/RSP
|
|
176
|
+
if operation == "REQ":
|
|
177
|
+
command, module = get_command_module(request)
|
|
178
|
+
# Handle default hello - handshake message
|
|
179
|
+
if len(command) == 1 and module == "hello":
|
|
180
|
+
rendered_out = render_packet(tid=tid, oper="RSP", data=f"hello {prompt}", prompt=self.devfid)
|
|
181
|
+
return True, rendered_out
|
|
182
|
+
# COMMAND EXECUTION
|
|
183
|
+
# Allow ALL module access same as Shell due to homogeneous intercon behaviour
|
|
184
|
+
# When Auth enabled allow only loaded modules to access (same as WebUI adn Shell(without pwd))
|
|
185
|
+
if not self.__auth or (self.__auth and lm_is_loaded(module)):
|
|
186
|
+
try:
|
|
187
|
+
state, out = lm_exec(command)
|
|
188
|
+
# rendered_output: "{tid}|{oper}|{data}|{prompt}$"
|
|
189
|
+
rendered_out = render_packet(tid=tid, oper="RSP", data=out, prompt=self.devfid)
|
|
190
|
+
return state, rendered_out
|
|
191
|
+
except Exception as e:
|
|
192
|
+
# Optionally log the exception here.
|
|
193
|
+
syslog(f"[ERR][_ESPNOW] {command}: {e}")
|
|
194
|
+
state, out = False, ""
|
|
195
|
+
else:
|
|
196
|
+
warning_msg = f"[WARN][_ESPNOW] NotAllowed {module}"
|
|
197
|
+
syslog(warning_msg)
|
|
198
|
+
rendered_out = render_packet(tid=tid, oper="RSP", data=warning_msg,
|
|
199
|
+
prompt=self.devfid)
|
|
200
|
+
state, out = True, rendered_out
|
|
201
|
+
return state, out
|
|
202
|
+
if operation == "RSP":
|
|
203
|
+
resp_data = request["data"]
|
|
204
|
+
ResponseRouter.update_response(mac, tid, resp_data) # USE <tid> for proper session response mapping
|
|
205
|
+
#syslog(f"[_ESPNOW] No action, {request}")
|
|
206
|
+
return False, ""
|
|
207
|
+
|
|
208
|
+
async def _server(self, tag: str):
|
|
209
|
+
"""
|
|
210
|
+
ESPnow async listener task
|
|
211
|
+
:param tag: micro_task tag for task access
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
with NativeTask.TASKS.get(tag, None) as my_task:
|
|
215
|
+
self.server_ready = True
|
|
216
|
+
my_task.out = "ESPNow receiver running"
|
|
217
|
+
async for mac, msg in self.espnow:
|
|
218
|
+
reply, response = False, ""
|
|
219
|
+
try:
|
|
220
|
+
reply, response = self._request_handler(msg, my_task, mac)
|
|
221
|
+
if reply:
|
|
222
|
+
await self.__asend_raw(mac, response)
|
|
223
|
+
except OSError as err:
|
|
224
|
+
# If the peer is not yet added, add it and retry.
|
|
225
|
+
if len(err.args) > 1 and err.args[1] == 'ESP_ERR_ESPNOW_NOT_FOUND':
|
|
226
|
+
# AUTOMATIC PEER REGISTRATION
|
|
227
|
+
self.espnow.add_peer(mac)
|
|
228
|
+
if reply:
|
|
229
|
+
await self.__asend_raw(mac, response)
|
|
230
|
+
else:
|
|
231
|
+
# Optionally handle or log other OSErrors here.
|
|
232
|
+
syslog(f"[ERR][NOW SERVER] {err}")
|
|
233
|
+
|
|
234
|
+
def start_server(self):
|
|
235
|
+
"""
|
|
236
|
+
Start the async ESPNow receiver server.
|
|
237
|
+
"""
|
|
238
|
+
# Create an asynchronous task with tag 'espnow.server'
|
|
239
|
+
tag = 'espnow.server'
|
|
240
|
+
return NativeTask().create(callback=self._server(tag), tag=tag)
|
|
241
|
+
|
|
242
|
+
#----------- SEND METHODS --------------
|
|
243
|
+
async def __asend_raw(self, mac: bytes, msg: str):
|
|
244
|
+
"""
|
|
245
|
+
ESPnow send message to mac address
|
|
246
|
+
"""
|
|
247
|
+
return await self.espnow.asend(mac, msg.encode("utf-8"))
|
|
248
|
+
|
|
249
|
+
async def _asend_task(self, tid: str, peer: bytes, tag: str, msg: str):
|
|
250
|
+
"""
|
|
251
|
+
ESPNow client task: send a command to a peer and update task status.
|
|
252
|
+
"""
|
|
253
|
+
with NativeTask.TASKS.get(tag, None) as my_task:
|
|
254
|
+
try:
|
|
255
|
+
router = ResponseRouter(peer, tid)
|
|
256
|
+
# rendered_output: "{tid}|{oper}|{data}|{prompt}$"
|
|
257
|
+
rendered_out = render_packet(tid=tid, oper="REQ", data=msg, prompt=self.devfid)
|
|
258
|
+
if await self.__asend_raw(peer, rendered_out):
|
|
259
|
+
my_task.out = f"[ESPNOW SEND] {rendered_out}"
|
|
260
|
+
my_task.out = await router.get_response()
|
|
261
|
+
else:
|
|
262
|
+
my_task.out = "[ESPNOW SEND] Peer not responding"
|
|
263
|
+
except Exception as e:
|
|
264
|
+
my_task.out = f"[ERR][ESPNOW SEND] {e}"
|
|
265
|
+
finally:
|
|
266
|
+
router.close()
|
|
267
|
+
|
|
268
|
+
def mac_by_peer_name(self, peer_name: str) -> bytes | None:
|
|
269
|
+
matches = [k for k, v in self.devices.items() if v == peer_name]
|
|
270
|
+
peer = matches[0] if matches else None
|
|
271
|
+
return peer
|
|
272
|
+
|
|
273
|
+
def send(self, peer: bytes | str, msg: str) -> dict:
|
|
274
|
+
"""
|
|
275
|
+
Send a command over ESPNow.
|
|
276
|
+
:param peer: Binary MAC address of another device.
|
|
277
|
+
:param msg: String command message to send.
|
|
278
|
+
"""
|
|
279
|
+
peer_name = None
|
|
280
|
+
if isinstance(peer, str):
|
|
281
|
+
# Peer as device name (prompt)
|
|
282
|
+
_peer = self.mac_by_peer_name(peer)
|
|
283
|
+
if _peer is None:
|
|
284
|
+
return {peer: "Unknown device"}
|
|
285
|
+
peer_name = peer
|
|
286
|
+
peer = _peer
|
|
287
|
+
|
|
288
|
+
peer_name = hexlify(peer, ':').decode() if peer_name is None else peer_name
|
|
289
|
+
_, module_name = get_command_module(msg)
|
|
290
|
+
task_id = f"con.espnow.{peer_name}.{module_name}"
|
|
291
|
+
tid = generate_tid()
|
|
292
|
+
# Create an asynchronous sending task.
|
|
293
|
+
return NativeTask().create(callback=self._asend_task(tid, peer, task_id, msg), tag=task_id)
|
|
294
|
+
|
|
295
|
+
def cluster_send(self, msg):
|
|
296
|
+
"""
|
|
297
|
+
Send message for all peers
|
|
298
|
+
"""
|
|
299
|
+
_tasks = []
|
|
300
|
+
for peer_name in self.devices.values():
|
|
301
|
+
_tasks.append(self.send(peer_name, msg))
|
|
302
|
+
return _tasks
|
|
303
|
+
|
|
304
|
+
# ----------- OTHER METHODS --------------
|
|
305
|
+
def save_peers(self):
|
|
306
|
+
try:
|
|
307
|
+
with open(self.peer_cache_path, "w", encoding="utf-8") as f:
|
|
308
|
+
dump([[list(k), v] for k, v in self.devices.items()], f)
|
|
309
|
+
return True
|
|
310
|
+
except Exception as e:
|
|
311
|
+
syslog(f"[ERR][ESPNOW] Saving peers: {e}")
|
|
312
|
+
return False
|
|
313
|
+
|
|
314
|
+
async def _handshake(self, peer: bytes, tag: str):
|
|
315
|
+
"""
|
|
316
|
+
Handshake with peer
|
|
317
|
+
- with device caching
|
|
318
|
+
"""
|
|
319
|
+
with NativeTask.TASKS.get(tag, None) as my_task:
|
|
320
|
+
if self.devices.get(peer) is None:
|
|
321
|
+
my_task.out = "ESPNow Add Peer"
|
|
322
|
+
try:
|
|
323
|
+
# PEER REGISTRATION
|
|
324
|
+
self.espnow.add_peer(peer)
|
|
325
|
+
except Exception as e:
|
|
326
|
+
my_task.out = f"ESPNow Peer Error: {e}"
|
|
327
|
+
return
|
|
328
|
+
my_task.out = "Handshake In Progress..."
|
|
329
|
+
sender = self.send(peer, "hello")
|
|
330
|
+
task_key = list(sender.keys())[0]
|
|
331
|
+
sender_task = NativeTask.TASKS.get(task_key, None)
|
|
332
|
+
if sender_task is None:
|
|
333
|
+
my_task.out = "Handshake Error: No sender task"
|
|
334
|
+
return
|
|
335
|
+
result = await sender_task.await_result(timeout=10)
|
|
336
|
+
expected_response = f"hello {self.devfid}"
|
|
337
|
+
is_ok = False
|
|
338
|
+
if result == expected_response:
|
|
339
|
+
is_ok = self.save_peers()
|
|
340
|
+
my_task.out = f"Handshake: {result} from {self.devices.get(peer)} [{'OK' if is_ok else 'NOK'}]"
|
|
341
|
+
sender_task.cancel() # Delete sender task (cleanup)
|
|
342
|
+
|
|
343
|
+
def _mac_str_to_bytes(self, mac_str: str) -> bytes|None:
|
|
344
|
+
"""
|
|
345
|
+
Convert MAC address string (e.g., '50:02:91:86:34:28') to bytes.
|
|
346
|
+
"""
|
|
347
|
+
try:
|
|
348
|
+
mac_bytes = bytes(int(x, 16) for x in mac_str.split(":"))
|
|
349
|
+
if len(mac_bytes) != 6:
|
|
350
|
+
return None
|
|
351
|
+
return mac_bytes
|
|
352
|
+
except Exception:
|
|
353
|
+
return None
|
|
354
|
+
|
|
355
|
+
def handshake(self, peer: bytes | str):
|
|
356
|
+
"""
|
|
357
|
+
Initiate a handshake with a peer device over ESPNow.
|
|
358
|
+
|
|
359
|
+
:param peer: The peer device's MAC address as bytes or a string in the format '50:02:91:86:34:28'.
|
|
360
|
+
:return: A dictionary with error information or a NativeTask instance for the handshake operation.
|
|
361
|
+
"""
|
|
362
|
+
task_id = "con.espnow.handshake"
|
|
363
|
+
# Create an asynchronous sending task.
|
|
364
|
+
if isinstance(peer, str) and ":" in peer:
|
|
365
|
+
peer_bytes = self._mac_str_to_bytes(peer)
|
|
366
|
+
if peer_bytes is not None:
|
|
367
|
+
peer = peer_bytes
|
|
368
|
+
if isinstance(peer, bytes):
|
|
369
|
+
return NativeTask().create(callback=self._handshake(peer, task_id), tag=task_id)
|
|
370
|
+
return {None: "Invalid MAC address format. Use 50:02:91:86:34:28 or b'P\\x02\\x91\\x864('"}
|
|
371
|
+
|
|
372
|
+
def stats(self):
|
|
373
|
+
"""
|
|
374
|
+
Return stats for ESPNow peers.
|
|
375
|
+
stats: tx_pkts, tx_responses, tx_failures, rx_packets, rx_dropped_packets.
|
|
376
|
+
peers: peer, rssi, time_ms.
|
|
377
|
+
"""
|
|
378
|
+
try:
|
|
379
|
+
_stats = self.espnow.stats()
|
|
380
|
+
except Exception as e:
|
|
381
|
+
_stats = str(e)
|
|
382
|
+
try:
|
|
383
|
+
_peers = self.espnow.peers_table
|
|
384
|
+
except Exception as e:
|
|
385
|
+
_peers = str(e)
|
|
386
|
+
return {"stats": _stats, "peers": _peers, "ready": self.server_ready}
|
|
387
|
+
|
|
388
|
+
def members(self):
|
|
389
|
+
"""
|
|
390
|
+
Returns the list of devices that are members of the current group.
|
|
391
|
+
"""
|
|
392
|
+
return self.devices
|
|
393
|
+
|
|
394
|
+
def remove_peer(self, peer: bytes) -> bool:
|
|
395
|
+
"""
|
|
396
|
+
Remove peer from ESPNow devices
|
|
397
|
+
:param peer: MAC address as bytes to remove
|
|
398
|
+
"""
|
|
399
|
+
if isinstance(peer, bytes):
|
|
400
|
+
if self.devices.pop(peer, None) is not None:
|
|
401
|
+
self.save_peers()
|
|
402
|
+
self.espnow.del_peer(peer)
|
|
403
|
+
return True
|
|
404
|
+
return False
|
micrOS/source/Files.py
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
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, rmdir
|
|
7
|
+
from sys import path as upath
|
|
8
|
+
|
|
9
|
+
################################ Helper functions #####################################
|
|
10
|
+
|
|
11
|
+
def _filter(path:str='/', ext:tuple=None, prefix:tuple=None, hide_core:bool=True) -> bool:
|
|
12
|
+
"""
|
|
13
|
+
Filter files in the micrOS filesystem.
|
|
14
|
+
|
|
15
|
+
:param path: file path to check
|
|
16
|
+
:param ext: tuple of extensions to filter by, default: None (all)
|
|
17
|
+
:param prefix: tuple of prefixes to match (e.g. ('LM', 'IO')), default: None
|
|
18
|
+
:param hide_core: if True, hides core .py/.mpy files in the root (current) directory
|
|
19
|
+
:return: bool, whether the file passes the filter
|
|
20
|
+
"""
|
|
21
|
+
parent = "/".join(path.split("/")[:-1]) or "/"
|
|
22
|
+
fname = path.split("/")[-1]
|
|
23
|
+
_ext = fname.split(".")[-1]
|
|
24
|
+
|
|
25
|
+
# --- Hide core logic ---
|
|
26
|
+
# Core = any .py/.mpy in the current (root) working directory
|
|
27
|
+
if hide_core and _ext in ("mpy", "py") and parent in ('/', ""):
|
|
28
|
+
return False
|
|
29
|
+
|
|
30
|
+
# --- General matching rules ---
|
|
31
|
+
if ext is None and prefix is None:
|
|
32
|
+
return True
|
|
33
|
+
if isinstance(prefix, tuple) and fname.split("_")[0] in prefix:
|
|
34
|
+
return True
|
|
35
|
+
if isinstance(ext, tuple) and _ext in ext:
|
|
36
|
+
return True
|
|
37
|
+
return False
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def is_protected(path:str='/') -> bool:
|
|
41
|
+
"""
|
|
42
|
+
Check is file/dir protected
|
|
43
|
+
- protected root folders and files: /*
|
|
44
|
+
- protected system files by name
|
|
45
|
+
"""
|
|
46
|
+
protected_files = ("node_config.json", "LM_system.mpy", "LM_pacman.mpy", "LM_cluster.mpy")
|
|
47
|
+
# Detect parent directory
|
|
48
|
+
parent = "/".join(path.split("/")[:-1]) or "/"
|
|
49
|
+
# Get file/folder name
|
|
50
|
+
fname = path.split("/")[-1]
|
|
51
|
+
# Disallow: root folders and protected files
|
|
52
|
+
return parent in ("/", "") or fname in protected_files
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _type_mask_to_str(item_type:int=None) -> str:
|
|
56
|
+
# Map the raw bit-mask to a single character
|
|
57
|
+
if item_type & 0x4000: # Dir bit-mask
|
|
58
|
+
item_type = 'd'
|
|
59
|
+
elif item_type & 0x8000: # File bit-mask
|
|
60
|
+
item_type = 'f'
|
|
61
|
+
else:
|
|
62
|
+
item_type = 'o'
|
|
63
|
+
return item_type
|
|
64
|
+
|
|
65
|
+
########################### Public functions #############################
|
|
66
|
+
def is_dir(path):
|
|
67
|
+
try:
|
|
68
|
+
return stat(path)[0] & 0x4000
|
|
69
|
+
except OSError:
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def is_file(path):
|
|
74
|
+
try:
|
|
75
|
+
return stat(path)[0] & 0x8000
|
|
76
|
+
except OSError:
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def ilist_fs(path:str="/", type_filter:str='*', select:str='*', core:bool=False):
|
|
81
|
+
"""
|
|
82
|
+
Linux like ls command - list app resources and app folders
|
|
83
|
+
:param path: path to list, default: /
|
|
84
|
+
:param type_filter: content type, default all (*), f-file, d-dir can be selected
|
|
85
|
+
:param select: select specific application resource type by prefix: LM or IO
|
|
86
|
+
:param core: list core files resources as well, default: False
|
|
87
|
+
return iterator:
|
|
88
|
+
when content is all (*) output: [(item_type, item), ...]
|
|
89
|
+
OR
|
|
90
|
+
content type was selected (not *) output: [item, ...]
|
|
91
|
+
"""
|
|
92
|
+
path = path if path.endswith('/') else f"{path}/"
|
|
93
|
+
# Info: uos.ilistdir: (name, type, inode[, size])
|
|
94
|
+
for item, item_type, *_ in ilistdir(path):
|
|
95
|
+
item_type = _type_mask_to_str(item_type)
|
|
96
|
+
if type_filter in ("*", item_type):
|
|
97
|
+
# Mods only
|
|
98
|
+
_select = None if select == "*" else (select,)
|
|
99
|
+
if item_type == 'f' and not _filter(path_join(path, item), prefix=_select, hide_core=not core):
|
|
100
|
+
continue
|
|
101
|
+
if select != '*' and item_type == 'd':
|
|
102
|
+
continue
|
|
103
|
+
# Create result
|
|
104
|
+
if type_filter == "*":
|
|
105
|
+
yield item_type, item
|
|
106
|
+
else:
|
|
107
|
+
yield item
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def list_fs(path:str="/", type_filter:str='*', select:str='*', core:bool=False) -> list[str,] | list[tuple[str, str],]:
|
|
111
|
+
"""
|
|
112
|
+
Wrapper of ilist_fs
|
|
113
|
+
Return list
|
|
114
|
+
"""
|
|
115
|
+
return list(ilist_fs(path, type_filter, select, core))
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def remove_file(path, force=False):
|
|
119
|
+
"""
|
|
120
|
+
Linux like rm command - delete app resources and folders
|
|
121
|
+
:param path: file to delete
|
|
122
|
+
:param force: pypass file protection check - sudo mode
|
|
123
|
+
"""
|
|
124
|
+
# protect some resources
|
|
125
|
+
if not force and is_protected(path):
|
|
126
|
+
return f'Protected resource, skip deletion: {path}'
|
|
127
|
+
if is_file(path):
|
|
128
|
+
remove(path)
|
|
129
|
+
return f"{path} deleted"
|
|
130
|
+
return f"Cannot delete (not a file): {path}"
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def remove_dir(path, force=False):
|
|
134
|
+
"""
|
|
135
|
+
Recursively delete a folder and all its contents.
|
|
136
|
+
:param path: folder to delete
|
|
137
|
+
:param force: pypass dir protection check - sudo mode
|
|
138
|
+
"""
|
|
139
|
+
# protect some resources
|
|
140
|
+
if not force and is_protected(path):
|
|
141
|
+
return f'Protected resource, skip deletion: {path}'
|
|
142
|
+
for entry in ilistdir(path):
|
|
143
|
+
content_path = path_join(path, entry[0])
|
|
144
|
+
if is_dir(content_path): # directory flag
|
|
145
|
+
remove_dir(content_path)
|
|
146
|
+
else:
|
|
147
|
+
remove(content_path)
|
|
148
|
+
rmdir(path)
|
|
149
|
+
return f"{path} deleted"
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def path_join(*parts):
|
|
153
|
+
path = "/".join(part.strip("/") for part in parts if part)
|
|
154
|
+
if parts and parts[0].startswith("/"):
|
|
155
|
+
path = path if path.startswith("/") else "/" + path
|
|
156
|
+
return path
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def abs_path(path):
|
|
160
|
+
parts = path.split("/")
|
|
161
|
+
stack = []
|
|
162
|
+
for p in parts:
|
|
163
|
+
if not p or p == ".":
|
|
164
|
+
continue
|
|
165
|
+
if p == "..":
|
|
166
|
+
if stack:
|
|
167
|
+
stack.pop() # prevent escaping root
|
|
168
|
+
else:
|
|
169
|
+
stack.append(p)
|
|
170
|
+
return "/" + "/".join(stack)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# micrOS system file structure
|
|
174
|
+
class OSPath:
|
|
175
|
+
_ROOT = getcwd()
|
|
176
|
+
LOGS = path_join(_ROOT, '/logs') # Logs (.log)
|
|
177
|
+
DATA = path_join(_ROOT,'/data') # Application data (.dat, .cache, etc.)
|
|
178
|
+
WEB = path_join(_ROOT,'/web') # Web resources (.html, .css, .js, .json, etc.)
|
|
179
|
+
MODULES = path_join(_ROOT, '/modules') # Application modules (.mpy, .py)
|
|
180
|
+
CONFIG = path_join(_ROOT, '/config') # System configuration files (node_config.json, etc.)
|
|
181
|
+
LIB = path_join(_ROOT, '/lib') # Official and Custom package installation target path
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def init_micros_dirs():
|
|
185
|
+
"""
|
|
186
|
+
Init micrOS root file system directories
|
|
187
|
+
"""
|
|
188
|
+
# ENABLE MODULES ACCESS
|
|
189
|
+
if OSPath.MODULES not in upath:
|
|
190
|
+
upath.insert(0, OSPath.MODULES)
|
|
191
|
+
# ENABLE LIB ACCESS
|
|
192
|
+
if OSPath.LIB not in upath:
|
|
193
|
+
upath.insert(0, OSPath.LIB)
|
|
194
|
+
|
|
195
|
+
root_dirs = [
|
|
196
|
+
getattr(OSPath, key)
|
|
197
|
+
for key in dir(OSPath)
|
|
198
|
+
if not key.startswith("_") and isinstance(getattr(OSPath, key), str)
|
|
199
|
+
]
|
|
200
|
+
print(f"[BOOT] rootFS validation: {root_dirs}")
|
|
201
|
+
for dir_path in root_dirs:
|
|
202
|
+
if not is_dir(dir_path):
|
|
203
|
+
try:
|
|
204
|
+
mkdir(dir_path)
|
|
205
|
+
print(f"[BOOT] init dir: {dir_path}")
|
|
206
|
+
except Exception as e:
|
|
207
|
+
print(f"[ERR][BOOT] cannot init dir {dir_path}: {e}")
|