micrOSDevToolKit 2.9.1__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/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-20241129-v1.24.1.bin → 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 +167 -163
- micrOS/source/Auth.py +37 -0
- micrOS/source/Common.py +361 -116
- micrOS/source/Config.py +32 -22
- micrOS/source/Debug.py +50 -94
- micrOS/source/Espnow.py +377 -100
- micrOS/source/Files.py +207 -0
- micrOS/source/Hooks.py +48 -20
- micrOS/source/InterConnect.py +126 -42
- micrOS/source/Interrupts.py +6 -6
- micrOS/source/Logger.py +63 -26
- micrOS/source/Network.py +41 -21
- micrOS/source/Notify.py +48 -22
- micrOS/source/Pacman.py +326 -0
- micrOS/source/Scheduler.py +14 -54
- micrOS/source/Server.py +67 -69
- micrOS/source/Shell.py +99 -91
- micrOS/source/Tasks.py +141 -95
- micrOS/source/Time.py +19 -18
- micrOS/source/Types.py +53 -9
- micrOS/source/Web.py +381 -76
- 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/config/_git.keep +0 -0
- micrOS/source/helpers.py +132 -0
- micrOS/source/micrOS.py +17 -7
- micrOS/source/micrOSloader.py +5 -12
- micrOS/source/microIO.py +44 -20
- micrOS/source/modules/IO_esp32c6.py +38 -0
- micrOS/source/{IO_esp32s3.py → modules/IO_esp32s3.py} +37 -1
- micrOS/source/{IO_m5stamp.py → modules/IO_m5stamp.py} +35 -1
- micrOS/source/{IO_qtpy.py → modules/IO_qtpy.py} +22 -17
- micrOS/source/{IO_tinypico.py → modules/IO_tinypico.py} +38 -0
- micrOS/source/modules/LM_L298N.py +161 -0
- {toolkit/workspace/precompiled → micrOS/source/modules}/LM_L9110_DCmotor.py +3 -3
- micrOS/source/{LM_OV2640.py → modules/LM_OV2640.py} +45 -27
- micrOS/source/{LM_VL53L0X.py → modules/LM_VL53L0X.py} +3 -3
- micrOS/source/{LM_aht10.py → modules/LM_aht10.py} +2 -2
- micrOS/source/{LM_bme280.py → modules/LM_bme280.py} +3 -3
- micrOS/source/{LM_buzzer.py → modules/LM_buzzer.py} +18 -25
- micrOS/source/{LM_cct.py → modules/LM_cct.py} +17 -21
- micrOS/source/modules/LM_cluster.py +255 -0
- micrOS/source/{LM_co2.py → modules/LM_co2.py} +3 -3
- micrOS/source/{LM_dht11.py → modules/LM_dht11.py} +2 -2
- micrOS/source/{LM_dht22.py → modules/LM_dht22.py} +2 -2
- micrOS/source/{LM_dimmer.py → modules/LM_dimmer.py} +9 -9
- micrOS/source/{LM_distance.py → modules/LM_distance.py} +4 -6
- micrOS/source/{LM_ds18.py → modules/LM_ds18.py} +2 -2
- micrOS/source/{LM_esp32.py → modules/LM_esp32.py} +5 -0
- micrOS/source/modules/LM_espnow.py +53 -0
- micrOS/source/modules/LM_fileserver.py +265 -0
- micrOS/source/{LM_genIO.py → modules/LM_genIO.py} +52 -37
- micrOS/source/{LM_haptic.py → modules/LM_haptic.py} +2 -2
- {toolkit/workspace/precompiled → micrOS/source/modules}/LM_i2c.py +5 -4
- micrOS/source/{LM_i2s_mic.py → modules/LM_i2s_mic.py} +6 -7
- micrOS/source/{LM_ld2410.py → modules/LM_ld2410.py} +2 -2
- micrOS/source/{LM_light_sensor.py → modules/LM_light_sensor.py} +10 -21
- 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} +19 -23
- micrOS/source/{LM_oled.py → modules/LM_oled.py} +2 -2
- micrOS/source/{LM_oled_sh1106.py → modules/LM_oled_sh1106.py} +3 -3
- micrOS/source/{LM_oled_ui.py → modules/LM_oled_ui.py} +72 -64
- micrOS/source/modules/LM_pacman.py +320 -0
- micrOS/source/{LM_presence.py → modules/LM_presence.py} +11 -15
- micrOS/source/modules/LM_qmi8658.py +204 -0
- micrOS/source/{LM_rencoder.py → modules/LM_rencoder.py} +2 -2
- micrOS/source/{LM_rest.py → modules/LM_rest.py} +4 -6
- micrOS/source/{LM_rgb.py → modules/LM_rgb.py} +21 -29
- micrOS/source/{LM_roboarm.py → modules/LM_roboarm.py} +8 -8
- micrOS/source/modules/LM_robustness.py +137 -0
- micrOS/source/{LM_servo.py → modules/LM_servo.py} +3 -3
- micrOS/source/{LM_stepper.py → modules/LM_stepper.py} +5 -5
- micrOS/source/{LM_switch.py → modules/LM_switch.py} +11 -9
- micrOS/source/{LM_system.py → modules/LM_system.py} +38 -32
- micrOS/source/modules/LM_tcs3472.py +187 -0
- micrOS/source/{LM_telegram.py → modules/LM_telegram.py} +164 -116
- micrOS/source/{LM_trackball.py → modules/LM_trackball.py} +3 -3
- micrOS/source/{LM_veml7700.py → modules/LM_veml7700.py} +2 -2
- micrOS/source/modules/LM_web.py +38 -0
- micrOS/source/urequests.py +39 -15
- {toolkit/workspace/precompiled → micrOS/source/web}/dashboard.html +4 -0
- micrOS/source/web/editor.js +440 -0
- micrOS/source/web/filesui.html +178 -0
- micrOS/source/web/filesui.js +338 -0
- {toolkit/workspace/precompiled → micrOS/source/web}/index.html +44 -2
- micrOS/source/{uapi.js → web/uapi.js} +48 -7
- micrOS/source/{ustyle.css → web/ustyle.css} +6 -3
- micrOS/utests/__init__.py +0 -0
- micrOS/utests/test_scheduler.py +435 -0
- {micrOSDevToolKit-2.9.1.data → microsdevtoolkit-2.26.1.data}/scripts/devToolKit.py +33 -3
- {micrOSDevToolKit-2.9.1.dist-info → microsdevtoolkit-2.26.1.dist-info}/METADATA +327 -268
- microsdevtoolkit-2.26.1.dist-info/RECORD +396 -0
- {micrOSDevToolKit-2.9.1.dist-info → microsdevtoolkit-2.26.1.dist-info}/WHEEL +1 -1
- toolkit/DevEnvCompile.py +63 -33
- toolkit/DevEnvOTA.py +58 -22
- toolkit/DevEnvUSB.py +110 -55
- toolkit/Gateway.py +6 -6
- toolkit/LM_to_compile.dat +6 -4
- toolkit/MicrOSDevEnv.py +127 -57
- toolkit/WebRepl.py +73 -0
- toolkit/dashboard_apps/BackupRestore.py +20 -35
- 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 +1 -6
- 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 +202 -105
- toolkit/dashboard_apps/Template_app.py +11 -23
- 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 +4 -4
- 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 +6 -0
- toolkit/lib/micrOSClient.py +123 -50
- toolkit/lib/micrOSClientHistory.py +156 -0
- toolkit/lib/pip_package_installer.py +5 -2
- toolkit/micrOSdashboard.py +12 -17
- toolkit/micrOSlint.py +20 -8
- 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__/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/dht.py +1 -1
- toolkit/simulator_lib/framebuf.py +49 -1
- toolkit/simulator_lib/machine.py +17 -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 +137 -59
- toolkit/simulator_lib/uasyncio.py +33 -2
- toolkit/simulator_lib/uos.py +147 -0
- toolkit/simulator_lib/urandom.py +4 -0
- toolkit/socketClient.py +43 -23
- toolkit/user_data/webhooks/generic.py +1 -1
- toolkit/user_data/webhooks/macro.py +1 -1
- 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/{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/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 +3 -3
- toolkit/workspace/precompiled/modules/LM_OV2640.mpy +0 -0
- toolkit/workspace/precompiled/{LM_VL53L0X.py → modules/LM_VL53L0X.py} +3 -3
- 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/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/modules/LM_dimmer.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_distance.mpy +0 -0
- toolkit/workspace/precompiled/{LM_ds18.mpy → modules/LM_ds18.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_esp32.py → modules/LM_esp32.py} +5 -0
- 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/{LM_haptic.mpy → modules/LM_haptic.mpy} +0 -0
- {micrOS/source → toolkit/workspace/precompiled/modules}/LM_i2c.py +5 -4
- 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/{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_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} +2 -2
- 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_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/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/{LM_trackball.mpy → modules/LM_trackball.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_veml7700.mpy → 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 +4 -0
- toolkit/workspace/precompiled/web/editor.js +440 -0
- toolkit/workspace/precompiled/web/filesui.html +178 -0
- toolkit/workspace/precompiled/web/filesui.js +338 -0
- {micrOS/source → toolkit/workspace/precompiled/web}/index.html +44 -2
- toolkit/workspace/precompiled/{uapi.js → web/uapi.js} +48 -7
- toolkit/workspace/precompiled/{ustyle.css → web/ustyle.css} +6 -3
- micrOS/micropython/esp32-20241129-v1.24.1.bin +0 -0
- micrOS/micropython/esp32c3-20240222-v1.22.2.bin +0 -0
- micrOS/micropython/esp32s2-20240602-v1.23.0.bin +0 -0
- micrOS/micropython/esp32s2-LOLIN_MINI-20220618-v1.19.1.bin +0 -0
- micrOS/micropython/esp32s2-LOLIN_MINI-20240602-v1.23.0.bin +0 -0
- micrOS/micropython/esp32s3-20240105-v1.22.1.bin +0 -0
- micrOS/micropython/esp32s3_spiram_oct-20231005-v1.21.0.bin +0 -0
- micrOS/micropython/esp32s3_spiram_oct-20241129-v1.24.1.bin +0 -0
- micrOS/micropython/rpi-pico-w-20241129-v1.24.1.uf2 +0 -0
- micrOS/micropython/tinypico-20241129-v1.24.1.bin +0 -0
- micrOS/source/LM_L298N_DCmotor.py +0 -86
- micrOS/source/LM_catgame.py +0 -75
- micrOS/source/LM_dashboard_be.py +0 -37
- micrOS/source/LM_demo.py +0 -97
- micrOS/source/LM_espnow.py +0 -23
- micrOS/source/LM_intercon.py +0 -57
- micrOS/source/LM_keychain.py +0 -322
- micrOS/source/LM_lmpacman.py +0 -126
- micrOS/source/LM_neoeffects.py +0 -331
- micrOS/source/LM_oledui.py +0 -972
- micrOS/source/LM_pet_feeder.py +0 -78
- micrOS/source/LM_ph_sensor.py +0 -51
- micrOS/source/LM_robustness.py +0 -74
- micrOS/source/reset.py +0 -11
- micrOSDevToolKit-2.9.1.dist-info/RECORD +0 -365
- 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__/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_esp32s3.mpy +0 -0
- toolkit/workspace/precompiled/IO_m5stamp.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_catgame.py +0 -75
- toolkit/workspace/precompiled/LM_cct.mpy +0 -0
- toolkit/workspace/precompiled/LM_dashboard_be.py +0 -37
- toolkit/workspace/precompiled/LM_demo.py +0 -97
- toolkit/workspace/precompiled/LM_dimmer.mpy +0 -0
- toolkit/workspace/precompiled/LM_distance.mpy +0 -0
- toolkit/workspace/precompiled/LM_espnow.py +0 -23
- toolkit/workspace/precompiled/LM_genIO.mpy +0 -0
- 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_ui.mpy +0 -0
- toolkit/workspace/precompiled/LM_oledui.mpy +0 -0
- toolkit/workspace/precompiled/LM_pet_feeder.py +0 -78
- 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 -74
- 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/node_config.json +0 -1
- toolkit/workspace/precompiled/reset.mpy +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_rp2.py → modules/IO_rp2.py} +0 -0
- /micrOS/source/{LM_gameOfLife.py → modules/LM_gameOfLife.py} +0 -0
- /micrOS/source/{LM_rgbcct.py → modules/LM_rgbcct.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_sound_event.py → modules/LM_sound_event.py} +0 -0
- /micrOS/source/{LM_tinyrgb.py → modules/LM_tinyrgb.py} +0 -0
- /micrOS/source/{udashboard.js → web/udashboard.js} +0 -0
- /micrOS/source/{uwidgets.js → web/uwidgets.js} +0 -0
- /micrOS/source/{uwidgets_pro.js → web/uwidgets_pro.js} +0 -0
- {micrOSDevToolKit-2.9.1.dist-info → microsdevtoolkit-2.26.1.dist-info/licenses}/LICENSE +0 -0
- {micrOSDevToolKit-2.9.1.dist-info → microsdevtoolkit-2.26.1.dist-info}/top_level.txt +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
- /toolkit/workspace/precompiled/{udashboard.js → web/udashboard.js} +0 -0
- /toolkit/workspace/precompiled/{uwidgets.js → web/uwidgets.js} +0 -0
- /toolkit/workspace/precompiled/{uwidgets_pro.js → web/uwidgets_pro.js} +0 -0
micrOS/source/Espnow.py
CHANGED
|
@@ -1,127 +1,404 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
from binascii import hexlify
|
|
4
|
-
from Tasks import NativeTask, TaskBase
|
|
5
|
-
from Network import get_mac
|
|
6
|
-
from Config import cfgget
|
|
1
|
+
"""
|
|
2
|
+
ESPNow Session Server and Protocol Utilities
|
|
7
3
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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.
|
|
11
9
|
|
|
10
|
+
Designed for MicroPython environments with async support.
|
|
11
|
+
"""
|
|
12
12
|
|
|
13
|
-
async def asend(now, mac, msg):
|
|
14
|
-
prompt = f"{_DEVFID}$"
|
|
15
|
-
msg = f"{msg}\n{prompt}".encode("utf-8")
|
|
16
|
-
return await now.asend(mac, msg)
|
|
17
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
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
data = msg_in.split("\n")
|
|
24
|
-
prompt, command = data[0], data[1]
|
|
25
|
-
msg_out = f"RUN CMD: {command} ON {prompt.replace('$', '')}"
|
|
26
|
-
# TODO: execute command
|
|
27
|
-
return True, msg_out
|
|
28
|
-
return False, ""
|
|
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
|
|
29
24
|
|
|
30
25
|
|
|
31
|
-
#
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
async for mac, msg in now:
|
|
35
|
-
try:
|
|
36
|
-
msg_ready, msg = execute(msg, my_task)
|
|
37
|
-
if msg_ready:
|
|
38
|
-
await asend(now, mac, msg)
|
|
39
|
-
except OSError as err:
|
|
40
|
-
if len(err.args) > 1 and err.args[1] == 'ESP_ERR_ESPNOW_NOT_FOUND':
|
|
41
|
-
now.add_peer(mac)
|
|
42
|
-
msg_ready, msg = execute(msg, my_task)
|
|
43
|
-
if msg_ready:
|
|
44
|
-
await asend(now, mac, msg)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
# Send a periodic ping to a peer
|
|
48
|
-
async def _send(now, peer, tag, msg):
|
|
49
|
-
with TaskBase.TASKS.get(tag) as my_task:
|
|
50
|
-
if not await asend(now, peer, msg):
|
|
51
|
-
my_task.out = "Peer not responding"
|
|
52
|
-
else:
|
|
53
|
-
my_task.out = f"send msg: {msg}"
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
###################################################
|
|
57
|
-
# Control functions #
|
|
58
|
-
###################################################
|
|
59
|
-
def initialize():
|
|
26
|
+
# ----------- PARSE AND RENDER MSG PROTOCOL --------------
|
|
27
|
+
|
|
28
|
+
def render_packet(tid: str, oper: str, data: str, prompt: str) -> str:
|
|
60
29
|
"""
|
|
61
|
-
|
|
30
|
+
Render ESPNow custom message (protocol)
|
|
62
31
|
"""
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
now = AIOESPNow() # Returns AIOESPNow enhanced with async support
|
|
67
|
-
now.active(True)
|
|
68
|
-
_INSTANCE = now
|
|
69
|
-
return _INSTANCE
|
|
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}$"
|
|
70
35
|
|
|
71
36
|
|
|
72
|
-
def
|
|
37
|
+
def parse_packet(msg: bytes) -> tuple[bool, dict | str]:
|
|
73
38
|
"""
|
|
74
|
-
|
|
75
|
-
:param now: espnow instance
|
|
76
|
-
:param peer: binary mac address of a peer, like b'\xbb\xbb\xbb\xbb\xbb\xbb'
|
|
39
|
+
Parse ESPNow custom message protocol
|
|
77
40
|
"""
|
|
78
|
-
|
|
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}"
|
|
79
53
|
|
|
80
54
|
|
|
81
|
-
def
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
|
89
66
|
|
|
90
67
|
|
|
91
|
-
def
|
|
68
|
+
def generate_tid() -> str:
|
|
92
69
|
"""
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
:param msg: string message to send
|
|
70
|
+
Generate a secure, random transaction ID (tid).
|
|
71
|
+
Returns an 8-byte hex string.
|
|
96
72
|
"""
|
|
97
|
-
|
|
98
|
-
mac = hexlify(peer, ':').decode()
|
|
99
|
-
task_id = f"espnow.cli.{mac}"
|
|
100
|
-
# [!] ASYNC TASK CREATION [1*] with async task callback + taskID (TAG) handling
|
|
101
|
-
state = NativeTask().create(callback=_send(now, peer, task_id, msg), tag=task_id)
|
|
102
|
-
return "Starting" if state else "Already running"
|
|
73
|
+
return hexlify(bytes([getrandbits(8) for _ in range(8)])).decode()
|
|
103
74
|
|
|
104
75
|
|
|
105
|
-
|
|
76
|
+
# ----------- ESPNOW SESSION SERVER - LISTENER AND SENDER --------------
|
|
77
|
+
class ResponseRouter:
|
|
106
78
|
"""
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
peers: peer, rssi, time_ms
|
|
79
|
+
Response Router (by mac address)
|
|
80
|
+
to connect sender task with receiver loop (aka server)
|
|
110
81
|
"""
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
|
121
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()
|
|
122
108
|
|
|
123
|
-
def
|
|
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:
|
|
124
115
|
"""
|
|
125
|
-
|
|
116
|
+
ESPNow Session Server
|
|
126
117
|
"""
|
|
127
|
-
|
|
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
|