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.
Files changed (368) hide show
  1. env/driver_cp210x/macOS_VCP_Driver/SiLabsUSBDriverDisk.dmg +0 -0
  2. env/driver_cp210x/macOS_VCP_Driver/macOS_VCP_Driver_Release_Notes.txt +17 -1
  3. micrOS/micropython/esp32-20251209-v1.27.0.bin +0 -0
  4. micrOS/micropython/esp32c3-20251209-v1.27.0.bin +0 -0
  5. micrOS/micropython/esp32c6-20251209-v1.27.0.bin +0 -0
  6. micrOS/micropython/esp32s2-20251209-v1.27.0.bin +0 -0
  7. micrOS/micropython/esp32s2-LOLIN_MINI-20251209-v1.27.0.bin +0 -0
  8. micrOS/micropython/{esp32s3-20241129-v1.24.1.bin → esp32s3-4MBflash-20241129-v1.24.1.bin} +0 -0
  9. micrOS/micropython/esp32s3-8MBflash-20251209-v1.27.0.bin +0 -0
  10. micrOS/micropython/esp32s3_spiram_oct-20251209-v1.27.0.bin +0 -0
  11. micrOS/micropython/rpi-pico-w-20251209-v1.27.0.uf2 +0 -0
  12. micrOS/micropython/tinypico-20251209-v1.27.0.bin +0 -0
  13. micrOS/release_info/micrOS_ReleaseInfo/system_analysis_sum.json +167 -163
  14. micrOS/source/Auth.py +37 -0
  15. micrOS/source/Common.py +361 -116
  16. micrOS/source/Config.py +32 -22
  17. micrOS/source/Debug.py +50 -94
  18. micrOS/source/Espnow.py +377 -100
  19. micrOS/source/Files.py +207 -0
  20. micrOS/source/Hooks.py +48 -20
  21. micrOS/source/InterConnect.py +126 -42
  22. micrOS/source/Interrupts.py +6 -6
  23. micrOS/source/Logger.py +63 -26
  24. micrOS/source/Network.py +41 -21
  25. micrOS/source/Notify.py +48 -22
  26. micrOS/source/Pacman.py +326 -0
  27. micrOS/source/Scheduler.py +14 -54
  28. micrOS/source/Server.py +67 -69
  29. micrOS/source/Shell.py +99 -91
  30. micrOS/source/Tasks.py +141 -95
  31. micrOS/source/Time.py +19 -18
  32. micrOS/source/Types.py +53 -9
  33. micrOS/source/Web.py +381 -76
  34. micrOS/source/__pycache__/Common.cpython-312.pyc +0 -0
  35. micrOS/source/__pycache__/Debug.cpython-312.pyc +0 -0
  36. micrOS/source/__pycache__/Files.cpython-312.pyc +0 -0
  37. micrOS/source/__pycache__/Logger.cpython-312.pyc +0 -0
  38. micrOS/source/__pycache__/Scheduler.cpython-312.pyc +0 -0
  39. micrOS/source/__pycache__/Server.cpython-312.pyc +0 -0
  40. micrOS/source/__pycache__/Shell.cpython-312.pyc +0 -0
  41. micrOS/source/__pycache__/replhelper.cpython-312.pyc +0 -0
  42. micrOS/source/config/_git.keep +0 -0
  43. micrOS/source/helpers.py +132 -0
  44. micrOS/source/micrOS.py +17 -7
  45. micrOS/source/micrOSloader.py +5 -12
  46. micrOS/source/microIO.py +44 -20
  47. micrOS/source/modules/IO_esp32c6.py +38 -0
  48. micrOS/source/{IO_esp32s3.py → modules/IO_esp32s3.py} +37 -1
  49. micrOS/source/{IO_m5stamp.py → modules/IO_m5stamp.py} +35 -1
  50. micrOS/source/{IO_qtpy.py → modules/IO_qtpy.py} +22 -17
  51. micrOS/source/{IO_tinypico.py → modules/IO_tinypico.py} +38 -0
  52. micrOS/source/modules/LM_L298N.py +161 -0
  53. {toolkit/workspace/precompiled → micrOS/source/modules}/LM_L9110_DCmotor.py +3 -3
  54. micrOS/source/{LM_OV2640.py → modules/LM_OV2640.py} +45 -27
  55. micrOS/source/{LM_VL53L0X.py → modules/LM_VL53L0X.py} +3 -3
  56. micrOS/source/{LM_aht10.py → modules/LM_aht10.py} +2 -2
  57. micrOS/source/{LM_bme280.py → modules/LM_bme280.py} +3 -3
  58. micrOS/source/{LM_buzzer.py → modules/LM_buzzer.py} +18 -25
  59. micrOS/source/{LM_cct.py → modules/LM_cct.py} +17 -21
  60. micrOS/source/modules/LM_cluster.py +255 -0
  61. micrOS/source/{LM_co2.py → modules/LM_co2.py} +3 -3
  62. micrOS/source/{LM_dht11.py → modules/LM_dht11.py} +2 -2
  63. micrOS/source/{LM_dht22.py → modules/LM_dht22.py} +2 -2
  64. micrOS/source/{LM_dimmer.py → modules/LM_dimmer.py} +9 -9
  65. micrOS/source/{LM_distance.py → modules/LM_distance.py} +4 -6
  66. micrOS/source/{LM_ds18.py → modules/LM_ds18.py} +2 -2
  67. micrOS/source/{LM_esp32.py → modules/LM_esp32.py} +5 -0
  68. micrOS/source/modules/LM_espnow.py +53 -0
  69. micrOS/source/modules/LM_fileserver.py +265 -0
  70. micrOS/source/{LM_genIO.py → modules/LM_genIO.py} +52 -37
  71. micrOS/source/{LM_haptic.py → modules/LM_haptic.py} +2 -2
  72. {toolkit/workspace/precompiled → micrOS/source/modules}/LM_i2c.py +5 -4
  73. micrOS/source/{LM_i2s_mic.py → modules/LM_i2s_mic.py} +6 -7
  74. micrOS/source/{LM_ld2410.py → modules/LM_ld2410.py} +2 -2
  75. micrOS/source/{LM_light_sensor.py → modules/LM_light_sensor.py} +10 -21
  76. micrOS/source/modules/LM_mh_z19c.py +198 -0
  77. micrOS/source/modules/LM_neoeffects.py +284 -0
  78. micrOS/source/{LM_neopixel.py → modules/LM_neopixel.py} +19 -23
  79. micrOS/source/{LM_oled.py → modules/LM_oled.py} +2 -2
  80. micrOS/source/{LM_oled_sh1106.py → modules/LM_oled_sh1106.py} +3 -3
  81. micrOS/source/{LM_oled_ui.py → modules/LM_oled_ui.py} +72 -64
  82. micrOS/source/modules/LM_pacman.py +320 -0
  83. micrOS/source/{LM_presence.py → modules/LM_presence.py} +11 -15
  84. micrOS/source/modules/LM_qmi8658.py +204 -0
  85. micrOS/source/{LM_rencoder.py → modules/LM_rencoder.py} +2 -2
  86. micrOS/source/{LM_rest.py → modules/LM_rest.py} +4 -6
  87. micrOS/source/{LM_rgb.py → modules/LM_rgb.py} +21 -29
  88. micrOS/source/{LM_roboarm.py → modules/LM_roboarm.py} +8 -8
  89. micrOS/source/modules/LM_robustness.py +137 -0
  90. micrOS/source/{LM_servo.py → modules/LM_servo.py} +3 -3
  91. micrOS/source/{LM_stepper.py → modules/LM_stepper.py} +5 -5
  92. micrOS/source/{LM_switch.py → modules/LM_switch.py} +11 -9
  93. micrOS/source/{LM_system.py → modules/LM_system.py} +38 -32
  94. micrOS/source/modules/LM_tcs3472.py +187 -0
  95. micrOS/source/{LM_telegram.py → modules/LM_telegram.py} +164 -116
  96. micrOS/source/{LM_trackball.py → modules/LM_trackball.py} +3 -3
  97. micrOS/source/{LM_veml7700.py → modules/LM_veml7700.py} +2 -2
  98. micrOS/source/modules/LM_web.py +38 -0
  99. micrOS/source/urequests.py +39 -15
  100. {toolkit/workspace/precompiled → micrOS/source/web}/dashboard.html +4 -0
  101. micrOS/source/web/editor.js +440 -0
  102. micrOS/source/web/filesui.html +178 -0
  103. micrOS/source/web/filesui.js +338 -0
  104. {toolkit/workspace/precompiled → micrOS/source/web}/index.html +44 -2
  105. micrOS/source/{uapi.js → web/uapi.js} +48 -7
  106. micrOS/source/{ustyle.css → web/ustyle.css} +6 -3
  107. micrOS/utests/__init__.py +0 -0
  108. micrOS/utests/test_scheduler.py +435 -0
  109. {micrOSDevToolKit-2.9.1.data → microsdevtoolkit-2.26.1.data}/scripts/devToolKit.py +33 -3
  110. {micrOSDevToolKit-2.9.1.dist-info → microsdevtoolkit-2.26.1.dist-info}/METADATA +327 -268
  111. microsdevtoolkit-2.26.1.dist-info/RECORD +396 -0
  112. {micrOSDevToolKit-2.9.1.dist-info → microsdevtoolkit-2.26.1.dist-info}/WHEEL +1 -1
  113. toolkit/DevEnvCompile.py +63 -33
  114. toolkit/DevEnvOTA.py +58 -22
  115. toolkit/DevEnvUSB.py +110 -55
  116. toolkit/Gateway.py +6 -6
  117. toolkit/LM_to_compile.dat +6 -4
  118. toolkit/MicrOSDevEnv.py +127 -57
  119. toolkit/WebRepl.py +73 -0
  120. toolkit/dashboard_apps/BackupRestore.py +20 -35
  121. toolkit/dashboard_apps/CCTDemo.py +12 -17
  122. toolkit/dashboard_apps/CCTTest.py +20 -24
  123. toolkit/dashboard_apps/CamStream.py +2 -6
  124. toolkit/dashboard_apps/CatGame.py +14 -16
  125. toolkit/dashboard_apps/Dimmer.py +11 -21
  126. toolkit/dashboard_apps/GetVersion.py +11 -19
  127. toolkit/dashboard_apps/MicrophoneTest.py +1 -6
  128. toolkit/dashboard_apps/NeoEffectsDemo.py +22 -35
  129. toolkit/dashboard_apps/NeopixelTest.py +20 -25
  130. toolkit/dashboard_apps/PresenceTest.py +2 -8
  131. toolkit/dashboard_apps/QMI8685_GYRO.py +68 -0
  132. toolkit/dashboard_apps/RGBTest.py +20 -24
  133. toolkit/dashboard_apps/RoboArm.py +24 -32
  134. toolkit/dashboard_apps/SED_test.py +10 -14
  135. toolkit/dashboard_apps/SensorsTest.py +61 -0
  136. toolkit/dashboard_apps/SystemTest.py +202 -105
  137. toolkit/dashboard_apps/Template_app.py +11 -23
  138. toolkit/dashboard_apps/_app_base.py +34 -0
  139. toolkit/dashboard_apps/_gyro_visualizer.py +78 -0
  140. toolkit/dashboard_apps/uLightDemo.py +15 -24
  141. toolkit/index.html +4 -4
  142. toolkit/lib/LocalMachine.py +6 -1
  143. toolkit/lib/MicrosFiles.py +46 -0
  144. toolkit/lib/Repository.py +64 -0
  145. toolkit/lib/TerminalColors.py +4 -0
  146. toolkit/lib/macroScript.py +6 -0
  147. toolkit/lib/micrOSClient.py +123 -50
  148. toolkit/lib/micrOSClientHistory.py +156 -0
  149. toolkit/lib/pip_package_installer.py +5 -2
  150. toolkit/micrOSdashboard.py +12 -17
  151. toolkit/micrOSlint.py +20 -8
  152. toolkit/simulator_lib/__pycache__/IO_darwin.cpython-312.pyc +0 -0
  153. toolkit/simulator_lib/__pycache__/aioespnow.cpython-312.pyc +0 -0
  154. toolkit/simulator_lib/__pycache__/framebuf.cpython-312.pyc +0 -0
  155. toolkit/simulator_lib/__pycache__/machine.cpython-312.pyc +0 -0
  156. toolkit/simulator_lib/__pycache__/micropython.cpython-312.pyc +0 -0
  157. toolkit/simulator_lib/__pycache__/mip.cpython-312.pyc +0 -0
  158. toolkit/simulator_lib/__pycache__/neopixel.cpython-312.pyc +0 -0
  159. toolkit/simulator_lib/__pycache__/network.cpython-312.pyc +0 -0
  160. toolkit/simulator_lib/__pycache__/sim_common.cpython-312.pyc +0 -0
  161. toolkit/simulator_lib/__pycache__/simgc.cpython-312.pyc +0 -0
  162. toolkit/simulator_lib/__pycache__/simulator.cpython-312.pyc +0 -0
  163. toolkit/simulator_lib/__pycache__/uasyncio.cpython-312.pyc +0 -0
  164. toolkit/simulator_lib/__pycache__/uos.cpython-312.pyc +0 -0
  165. toolkit/simulator_lib/__pycache__/urandom.cpython-312.pyc +0 -0
  166. toolkit/simulator_lib/__pycache__/usocket.cpython-312.pyc +0 -0
  167. toolkit/simulator_lib/__pycache__/ussl.cpython-312.pyc +0 -0
  168. toolkit/simulator_lib/aioespnow.py +28 -0
  169. toolkit/simulator_lib/dht.py +1 -1
  170. toolkit/simulator_lib/framebuf.py +49 -1
  171. toolkit/simulator_lib/machine.py +17 -2
  172. toolkit/simulator_lib/micropython.py +3 -3
  173. toolkit/simulator_lib/mip.py +165 -0
  174. toolkit/simulator_lib/neopixel.py +3 -2
  175. toolkit/simulator_lib/network.py +2 -1
  176. toolkit/simulator_lib/node_config.json +2 -3
  177. toolkit/simulator_lib/ntptime.py +1 -1
  178. toolkit/simulator_lib/{sim_console.py → sim_common.py} +2 -3
  179. toolkit/simulator_lib/simgc.py +6 -2
  180. toolkit/simulator_lib/simulator.py +137 -59
  181. toolkit/simulator_lib/uasyncio.py +33 -2
  182. toolkit/simulator_lib/uos.py +147 -0
  183. toolkit/simulator_lib/urandom.py +4 -0
  184. toolkit/socketClient.py +43 -23
  185. toolkit/user_data/webhooks/generic.py +1 -1
  186. toolkit/user_data/webhooks/macro.py +1 -1
  187. toolkit/user_data/webhooks/template.py +1 -1
  188. toolkit/workspace/precompiled/Auth.mpy +0 -0
  189. toolkit/workspace/precompiled/Common.mpy +0 -0
  190. toolkit/workspace/precompiled/Config.mpy +0 -0
  191. toolkit/workspace/precompiled/Debug.mpy +0 -0
  192. toolkit/workspace/precompiled/Espnow.mpy +0 -0
  193. toolkit/workspace/precompiled/Files.mpy +0 -0
  194. toolkit/workspace/precompiled/Hooks.mpy +0 -0
  195. toolkit/workspace/precompiled/InterConnect.mpy +0 -0
  196. toolkit/workspace/precompiled/Interrupts.mpy +0 -0
  197. toolkit/workspace/precompiled/Logger.mpy +0 -0
  198. toolkit/workspace/precompiled/Network.mpy +0 -0
  199. toolkit/workspace/precompiled/Notify.mpy +0 -0
  200. toolkit/workspace/precompiled/Pacman.mpy +0 -0
  201. toolkit/workspace/precompiled/Scheduler.mpy +0 -0
  202. toolkit/workspace/precompiled/Server.mpy +0 -0
  203. toolkit/workspace/precompiled/Shell.mpy +0 -0
  204. toolkit/workspace/precompiled/Tasks.mpy +0 -0
  205. toolkit/workspace/precompiled/Time.mpy +0 -0
  206. toolkit/workspace/precompiled/Types.mpy +0 -0
  207. toolkit/workspace/precompiled/Web.mpy +0 -0
  208. toolkit/workspace/precompiled/_mpy.version +1 -1
  209. toolkit/workspace/precompiled/config/_git.keep +0 -0
  210. toolkit/workspace/precompiled/helpers.mpy +0 -0
  211. toolkit/workspace/precompiled/micrOS.mpy +0 -0
  212. toolkit/workspace/precompiled/micrOSloader.mpy +0 -0
  213. toolkit/workspace/precompiled/microIO.mpy +0 -0
  214. toolkit/workspace/precompiled/{IO_esp32.mpy → modules/IO_esp32.mpy} +0 -0
  215. toolkit/workspace/precompiled/{IO_esp32c3.mpy → modules/IO_esp32c3.mpy} +0 -0
  216. toolkit/workspace/precompiled/modules/IO_esp32c6.mpy +0 -0
  217. toolkit/workspace/precompiled/{IO_esp32s2.mpy → modules/IO_esp32s2.mpy} +0 -0
  218. toolkit/workspace/precompiled/modules/IO_esp32s3.mpy +0 -0
  219. toolkit/workspace/precompiled/modules/IO_m5stamp.mpy +0 -0
  220. toolkit/workspace/precompiled/modules/IO_qtpy.mpy +0 -0
  221. toolkit/workspace/precompiled/modules/IO_rp2.mpy +0 -0
  222. toolkit/workspace/precompiled/modules/IO_tinypico.mpy +0 -0
  223. toolkit/workspace/precompiled/modules/LM_L298N.mpy +0 -0
  224. {micrOS/source → toolkit/workspace/precompiled/modules}/LM_L9110_DCmotor.py +3 -3
  225. toolkit/workspace/precompiled/modules/LM_OV2640.mpy +0 -0
  226. toolkit/workspace/precompiled/{LM_VL53L0X.py → modules/LM_VL53L0X.py} +3 -3
  227. toolkit/workspace/precompiled/{LM_aht10.mpy → modules/LM_aht10.mpy} +0 -0
  228. toolkit/workspace/precompiled/{LM_bme280.mpy → modules/LM_bme280.mpy} +0 -0
  229. toolkit/workspace/precompiled/{LM_buzzer.mpy → modules/LM_buzzer.mpy} +0 -0
  230. toolkit/workspace/precompiled/modules/LM_cct.mpy +0 -0
  231. toolkit/workspace/precompiled/modules/LM_cluster.mpy +0 -0
  232. toolkit/workspace/precompiled/{LM_co2.mpy → modules/LM_co2.mpy} +0 -0
  233. toolkit/workspace/precompiled/{LM_dht11.mpy → modules/LM_dht11.mpy} +0 -0
  234. toolkit/workspace/precompiled/{LM_dht22.mpy → modules/LM_dht22.mpy} +0 -0
  235. toolkit/workspace/precompiled/modules/LM_dimmer.mpy +0 -0
  236. toolkit/workspace/precompiled/modules/LM_distance.mpy +0 -0
  237. toolkit/workspace/precompiled/{LM_ds18.mpy → modules/LM_ds18.mpy} +0 -0
  238. toolkit/workspace/precompiled/{LM_esp32.py → modules/LM_esp32.py} +5 -0
  239. toolkit/workspace/precompiled/modules/LM_espnow.py +53 -0
  240. toolkit/workspace/precompiled/modules/LM_fileserver.mpy +0 -0
  241. toolkit/workspace/precompiled/{LM_gameOfLife.mpy → modules/LM_gameOfLife.mpy} +0 -0
  242. toolkit/workspace/precompiled/modules/LM_genIO.mpy +0 -0
  243. toolkit/workspace/precompiled/{LM_haptic.mpy → modules/LM_haptic.mpy} +0 -0
  244. {micrOS/source → toolkit/workspace/precompiled/modules}/LM_i2c.py +5 -4
  245. toolkit/workspace/precompiled/modules/LM_i2s_mic.mpy +0 -0
  246. toolkit/workspace/precompiled/{LM_ld2410.mpy → modules/LM_ld2410.mpy} +0 -0
  247. toolkit/workspace/precompiled/modules/LM_light_sensor.mpy +0 -0
  248. toolkit/workspace/precompiled/modules/LM_mh_z19c.py +198 -0
  249. toolkit/workspace/precompiled/modules/LM_neoeffects.mpy +0 -0
  250. toolkit/workspace/precompiled/modules/LM_neopixel.mpy +0 -0
  251. toolkit/workspace/precompiled/{LM_oled.mpy → modules/LM_oled.mpy} +0 -0
  252. toolkit/workspace/precompiled/{LM_oled_sh1106.mpy → modules/LM_oled_sh1106.mpy} +0 -0
  253. toolkit/workspace/precompiled/modules/LM_oled_ui.mpy +0 -0
  254. toolkit/workspace/precompiled/modules/LM_pacman.mpy +0 -0
  255. toolkit/workspace/precompiled/modules/LM_presence.mpy +0 -0
  256. toolkit/workspace/precompiled/modules/LM_qmi8658.py +204 -0
  257. toolkit/workspace/precompiled/{LM_rencoder.py → modules/LM_rencoder.py} +2 -2
  258. toolkit/workspace/precompiled/modules/LM_rest.mpy +0 -0
  259. toolkit/workspace/precompiled/modules/LM_rgb.mpy +0 -0
  260. toolkit/workspace/precompiled/{LM_rgbcct.mpy → modules/LM_rgbcct.mpy} +0 -0
  261. toolkit/workspace/precompiled/modules/LM_roboarm.mpy +0 -0
  262. toolkit/workspace/precompiled/modules/LM_robustness.py +137 -0
  263. toolkit/workspace/precompiled/{LM_servo.mpy → modules/LM_servo.mpy} +0 -0
  264. toolkit/workspace/precompiled/{LM_sound_event.mpy → modules/LM_sound_event.mpy} +0 -0
  265. toolkit/workspace/precompiled/{LM_stepper.mpy → modules/LM_stepper.mpy} +0 -0
  266. toolkit/workspace/precompiled/modules/LM_switch.mpy +0 -0
  267. toolkit/workspace/precompiled/modules/LM_system.mpy +0 -0
  268. toolkit/workspace/precompiled/modules/LM_tcs3472.py +187 -0
  269. toolkit/workspace/precompiled/modules/LM_telegram.mpy +0 -0
  270. toolkit/workspace/precompiled/{LM_tinyrgb.mpy → modules/LM_tinyrgb.mpy} +0 -0
  271. toolkit/workspace/precompiled/{LM_trackball.mpy → modules/LM_trackball.mpy} +0 -0
  272. toolkit/workspace/precompiled/{LM_veml7700.mpy → modules/LM_veml7700.mpy} +0 -0
  273. toolkit/workspace/precompiled/modules/LM_web.mpy +0 -0
  274. toolkit/workspace/precompiled/urequests.mpy +0 -0
  275. {micrOS/source → toolkit/workspace/precompiled/web}/dashboard.html +4 -0
  276. toolkit/workspace/precompiled/web/editor.js +440 -0
  277. toolkit/workspace/precompiled/web/filesui.html +178 -0
  278. toolkit/workspace/precompiled/web/filesui.js +338 -0
  279. {micrOS/source → toolkit/workspace/precompiled/web}/index.html +44 -2
  280. toolkit/workspace/precompiled/{uapi.js → web/uapi.js} +48 -7
  281. toolkit/workspace/precompiled/{ustyle.css → web/ustyle.css} +6 -3
  282. micrOS/micropython/esp32-20241129-v1.24.1.bin +0 -0
  283. micrOS/micropython/esp32c3-20240222-v1.22.2.bin +0 -0
  284. micrOS/micropython/esp32s2-20240602-v1.23.0.bin +0 -0
  285. micrOS/micropython/esp32s2-LOLIN_MINI-20220618-v1.19.1.bin +0 -0
  286. micrOS/micropython/esp32s2-LOLIN_MINI-20240602-v1.23.0.bin +0 -0
  287. micrOS/micropython/esp32s3-20240105-v1.22.1.bin +0 -0
  288. micrOS/micropython/esp32s3_spiram_oct-20231005-v1.21.0.bin +0 -0
  289. micrOS/micropython/esp32s3_spiram_oct-20241129-v1.24.1.bin +0 -0
  290. micrOS/micropython/rpi-pico-w-20241129-v1.24.1.uf2 +0 -0
  291. micrOS/micropython/tinypico-20241129-v1.24.1.bin +0 -0
  292. micrOS/source/LM_L298N_DCmotor.py +0 -86
  293. micrOS/source/LM_catgame.py +0 -75
  294. micrOS/source/LM_dashboard_be.py +0 -37
  295. micrOS/source/LM_demo.py +0 -97
  296. micrOS/source/LM_espnow.py +0 -23
  297. micrOS/source/LM_intercon.py +0 -57
  298. micrOS/source/LM_keychain.py +0 -322
  299. micrOS/source/LM_lmpacman.py +0 -126
  300. micrOS/source/LM_neoeffects.py +0 -331
  301. micrOS/source/LM_oledui.py +0 -972
  302. micrOS/source/LM_pet_feeder.py +0 -78
  303. micrOS/source/LM_ph_sensor.py +0 -51
  304. micrOS/source/LM_robustness.py +0 -74
  305. micrOS/source/reset.py +0 -11
  306. micrOSDevToolKit-2.9.1.dist-info/RECORD +0 -365
  307. toolkit/dashboard_apps/AirQualityBME280.py +0 -36
  308. toolkit/dashboard_apps/AirQualityDHT22_CO2.py +0 -36
  309. toolkit/lib/file_extensions.py +0 -16
  310. toolkit/simulator_lib/__pycache__/sim_console.cpython-312.pyc +0 -0
  311. toolkit/simulator_lib/__pycache__/sim_console.cpython-38.pyc +0 -0
  312. toolkit/simulator_lib/__pycache__/sim_console.cpython-39.pyc +0 -0
  313. toolkit/workspace/precompiled/IO_esp32s3.mpy +0 -0
  314. toolkit/workspace/precompiled/IO_m5stamp.mpy +0 -0
  315. toolkit/workspace/precompiled/IO_qtpy.mpy +0 -0
  316. toolkit/workspace/precompiled/IO_rp2.mpy +0 -0
  317. toolkit/workspace/precompiled/IO_tinypico.mpy +0 -0
  318. toolkit/workspace/precompiled/LM_L298N_DCmotor.mpy +0 -0
  319. toolkit/workspace/precompiled/LM_OV2640.mpy +0 -0
  320. toolkit/workspace/precompiled/LM_catgame.py +0 -75
  321. toolkit/workspace/precompiled/LM_cct.mpy +0 -0
  322. toolkit/workspace/precompiled/LM_dashboard_be.py +0 -37
  323. toolkit/workspace/precompiled/LM_demo.py +0 -97
  324. toolkit/workspace/precompiled/LM_dimmer.mpy +0 -0
  325. toolkit/workspace/precompiled/LM_distance.mpy +0 -0
  326. toolkit/workspace/precompiled/LM_espnow.py +0 -23
  327. toolkit/workspace/precompiled/LM_genIO.mpy +0 -0
  328. toolkit/workspace/precompiled/LM_i2s_mic.mpy +0 -0
  329. toolkit/workspace/precompiled/LM_intercon.mpy +0 -0
  330. toolkit/workspace/precompiled/LM_keychain.mpy +0 -0
  331. toolkit/workspace/precompiled/LM_light_sensor.mpy +0 -0
  332. toolkit/workspace/precompiled/LM_lmpacman.mpy +0 -0
  333. toolkit/workspace/precompiled/LM_neoeffects.mpy +0 -0
  334. toolkit/workspace/precompiled/LM_neopixel.mpy +0 -0
  335. toolkit/workspace/precompiled/LM_oled_ui.mpy +0 -0
  336. toolkit/workspace/precompiled/LM_oledui.mpy +0 -0
  337. toolkit/workspace/precompiled/LM_pet_feeder.py +0 -78
  338. toolkit/workspace/precompiled/LM_ph_sensor.py +0 -51
  339. toolkit/workspace/precompiled/LM_presence.mpy +0 -0
  340. toolkit/workspace/precompiled/LM_rest.mpy +0 -0
  341. toolkit/workspace/precompiled/LM_rgb.mpy +0 -0
  342. toolkit/workspace/precompiled/LM_roboarm.mpy +0 -0
  343. toolkit/workspace/precompiled/LM_robustness.py +0 -74
  344. toolkit/workspace/precompiled/LM_switch.mpy +0 -0
  345. toolkit/workspace/precompiled/LM_system.mpy +0 -0
  346. toolkit/workspace/precompiled/LM_telegram.mpy +0 -0
  347. toolkit/workspace/precompiled/node_config.json +0 -1
  348. toolkit/workspace/precompiled/reset.mpy +0 -0
  349. /micrOS/source/{IO_esp32.py → modules/IO_esp32.py} +0 -0
  350. /micrOS/source/{IO_esp32c3.py → modules/IO_esp32c3.py} +0 -0
  351. /micrOS/source/{IO_esp32s2.py → modules/IO_esp32s2.py} +0 -0
  352. /micrOS/source/{IO_rp2.py → modules/IO_rp2.py} +0 -0
  353. /micrOS/source/{LM_gameOfLife.py → modules/LM_gameOfLife.py} +0 -0
  354. /micrOS/source/{LM_rgbcct.py → modules/LM_rgbcct.py} +0 -0
  355. /micrOS/source/{LM_rp2w.py → modules/LM_rp2w.py} +0 -0
  356. /micrOS/source/{LM_sdcard.py → modules/LM_sdcard.py} +0 -0
  357. /micrOS/source/{LM_sound_event.py → modules/LM_sound_event.py} +0 -0
  358. /micrOS/source/{LM_tinyrgb.py → modules/LM_tinyrgb.py} +0 -0
  359. /micrOS/source/{udashboard.js → web/udashboard.js} +0 -0
  360. /micrOS/source/{uwidgets.js → web/uwidgets.js} +0 -0
  361. /micrOS/source/{uwidgets_pro.js → web/uwidgets_pro.js} +0 -0
  362. {micrOSDevToolKit-2.9.1.dist-info → microsdevtoolkit-2.26.1.dist-info/licenses}/LICENSE +0 -0
  363. {micrOSDevToolKit-2.9.1.dist-info → microsdevtoolkit-2.26.1.dist-info}/top_level.txt +0 -0
  364. /toolkit/workspace/precompiled/{LM_rp2w.py → modules/LM_rp2w.py} +0 -0
  365. /toolkit/workspace/precompiled/{LM_sdcard.py → modules/LM_sdcard.py} +0 -0
  366. /toolkit/workspace/precompiled/{udashboard.js → web/udashboard.js} +0 -0
  367. /toolkit/workspace/precompiled/{uwidgets.js → web/uwidgets.js} +0 -0
  368. /toolkit/workspace/precompiled/{uwidgets_pro.js → web/uwidgets_pro.js} +0 -0
micrOS/source/Espnow.py CHANGED
@@ -1,127 +1,404 @@
1
- import uasyncio as asyncio
2
- from aioespnow import AIOESPNow
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
- # https://docs.micropython.org/en/latest/library/espnow.html
9
- _INSTANCE = None
10
- _DEVFID = cfgget('devfid')
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
- def execute(msg, my_task):
20
- msg_in = msg.decode('utf-8')
21
- if msg_in.strip().endswith("$"):
22
- my_task.out = f"MSG-IN: {msg_in}"
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
- # Echo any received messages back to the sender
32
- async def _server(now):
33
- with TaskBase.TASKS.get('espnow.server', None) as my_task:
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
- Initialize ESPNow protocol
30
+ Render ESPNow custom message (protocol)
62
31
  """
63
- # Network module: WLAN interface must be active to send()/recv()
64
- global _INSTANCE
65
- if _INSTANCE is None:
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 add_peer(now, peer):
37
+ def parse_packet(msg: bytes) -> tuple[bool, dict | str]:
73
38
  """
74
- Add peer by mac address
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
- return now.add_peer(peer)
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 espnow_server():
82
- """
83
- Start async ESPNow receiver server
84
- """
85
- now = initialize()
86
- # [!] ASYNC TASK CREATION [1*] with async task callback + taskID (TAG) handling
87
- state = NativeTask().create(callback=_server(now), tag='espnow.server')
88
- return "Starting" if state else "Already running"
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 espnow_send(peer, msg):
68
+ def generate_tid() -> str:
92
69
  """
93
- Send message/command over ESPNow protocol
94
- :param peer: binary mac address of another device
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
- now = initialize()
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
- def stats():
76
+ # ----------- ESPNOW SESSION SERVER - LISTENER AND SENDER --------------
77
+ class ResponseRouter:
106
78
  """
107
- Return stats for ESPNow peers
108
- stats: tx_pkts, tx_responses, tx_failures, rx_packets, rx_dropped_packets
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
- now = initialize()
112
- try:
113
- _stats = now.stats()
114
- except Exception as e:
115
- _stats = str(e)
116
- try:
117
- _peers = now.peers_table
118
- except Exception as e:
119
- _peers = str(e)
120
- return {"stats": _stats, "peers": _peers}
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 mac_address():
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
- Get binary mac address
116
+ ESPNow Session Server
126
117
  """
127
- return get_mac()
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