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.
Files changed (400) hide show
  1. env/driver_cp210x/.DS_Store +0 -0
  2. env/driver_cp210x/macOS_VCP_Driver/SiLabsUSBDriverDisk.dmg +0 -0
  3. env/driver_cp210x/macOS_VCP_Driver/macOS_VCP_Driver_Release_Notes.txt +17 -1
  4. micrOS/micropython/esp32-20251209-v1.27.0.bin +0 -0
  5. micrOS/micropython/esp32c3-20251209-v1.27.0.bin +0 -0
  6. micrOS/micropython/esp32c6-20251209-v1.27.0.bin +0 -0
  7. micrOS/micropython/esp32s2-20251209-v1.27.0.bin +0 -0
  8. micrOS/micropython/esp32s2-LOLIN_MINI-20251209-v1.27.0.bin +0 -0
  9. micrOS/micropython/esp32s3-4MBflash-20241129-v1.24.1.bin +0 -0
  10. micrOS/micropython/esp32s3-8MBflash-20251209-v1.27.0.bin +0 -0
  11. micrOS/micropython/esp32s3_spiram_oct-20251209-v1.27.0.bin +0 -0
  12. micrOS/micropython/rpi-pico-w-20251209-v1.27.0.uf2 +0 -0
  13. micrOS/micropython/tinypico-20251209-v1.27.0.bin +0 -0
  14. micrOS/release_info/micrOS_ReleaseInfo/system_analysis_sum.json +191 -151
  15. micrOS/source/Auth.py +37 -0
  16. micrOS/source/Common.py +376 -102
  17. micrOS/source/Config.py +55 -25
  18. micrOS/source/Debug.py +54 -193
  19. micrOS/source/Espnow.py +404 -0
  20. micrOS/source/Files.py +207 -0
  21. micrOS/source/Hooks.py +88 -16
  22. micrOS/source/InterConnect.py +130 -46
  23. micrOS/source/Interrupts.py +8 -8
  24. micrOS/source/Logger.py +131 -0
  25. micrOS/source/Network.py +41 -21
  26. micrOS/source/Notify.py +74 -198
  27. micrOS/source/Pacman.py +326 -0
  28. micrOS/source/Scheduler.py +18 -55
  29. micrOS/source/Server.py +84 -217
  30. micrOS/source/Shell.py +103 -93
  31. micrOS/source/Tasks.py +239 -173
  32. micrOS/source/Time.py +21 -22
  33. micrOS/source/Types.py +89 -54
  34. micrOS/source/Web.py +485 -0
  35. micrOS/source/__pycache__/Common.cpython-312.pyc +0 -0
  36. micrOS/source/__pycache__/Debug.cpython-312.pyc +0 -0
  37. micrOS/source/__pycache__/Files.cpython-312.pyc +0 -0
  38. micrOS/source/__pycache__/Logger.cpython-312.pyc +0 -0
  39. micrOS/source/__pycache__/Scheduler.cpython-312.pyc +0 -0
  40. micrOS/source/__pycache__/Server.cpython-312.pyc +0 -0
  41. micrOS/source/__pycache__/Shell.cpython-312.pyc +0 -0
  42. micrOS/source/__pycache__/replhelper.cpython-312.pyc +0 -0
  43. micrOS/source/helpers.py +132 -0
  44. micrOS/source/micrOS.py +25 -21
  45. micrOS/source/micrOSloader.py +14 -23
  46. micrOS/source/microIO.py +94 -57
  47. toolkit/simulator_lib/LP_darwin.py → micrOS/source/modules/IO_esp32.py +22 -11
  48. micrOS/source/{IO_esp32c3.py → modules/IO_esp32c3.py} +6 -1
  49. micrOS/source/modules/IO_esp32c6.py +38 -0
  50. micrOS/source/{IO_esp32s2.py → modules/IO_esp32s2.py} +6 -1
  51. micrOS/source/{IO_esp32s3.py → modules/IO_esp32s3.py} +43 -2
  52. micrOS/source/modules/IO_m5stamp.py +86 -0
  53. micrOS/source/{IO_qtpy.py → modules/IO_qtpy.py} +28 -18
  54. micrOS/source/{IO_tinypico.py → modules/IO_tinypico.py} +48 -3
  55. micrOS/source/modules/LM_L298N.py +161 -0
  56. {toolkit/workspace/precompiled → micrOS/source/modules}/LM_L9110_DCmotor.py +4 -4
  57. micrOS/source/{LM_OV2640.py → modules/LM_OV2640.py} +53 -42
  58. micrOS/source/{LM_VL53L0X.py → modules/LM_VL53L0X.py} +5 -5
  59. micrOS/source/{LM_aht10.py → modules/LM_aht10.py} +12 -4
  60. micrOS/source/{LM_bme280.py → modules/LM_bme280.py} +13 -25
  61. micrOS/source/{LM_buzzer.py → modules/LM_buzzer.py} +42 -40
  62. micrOS/source/{LM_cct.py → modules/LM_cct.py} +22 -27
  63. micrOS/source/modules/LM_cluster.py +255 -0
  64. micrOS/source/{LM_co2.py → modules/LM_co2.py} +13 -6
  65. micrOS/source/{LM_dht11.py → modules/LM_dht11.py} +13 -29
  66. micrOS/source/{LM_dht22.py → modules/LM_dht22.py} +13 -28
  67. micrOS/source/{LM_dimmer.py → modules/LM_dimmer.py} +19 -16
  68. micrOS/source/modules/LM_distance.py +135 -0
  69. micrOS/source/{LM_ds18.py → modules/LM_ds18.py} +12 -4
  70. micrOS/source/{LM_esp32.py → modules/LM_esp32.py} +16 -4
  71. micrOS/source/modules/LM_espnow.py +53 -0
  72. micrOS/source/modules/LM_fileserver.py +265 -0
  73. micrOS/source/{LM_gameOfLife.py → modules/LM_gameOfLife.py} +5 -5
  74. micrOS/source/{LM_genIO.py → modules/LM_genIO.py} +49 -35
  75. micrOS/source/modules/LM_haptic.py +111 -0
  76. micrOS/source/modules/LM_i2c.py +61 -0
  77. micrOS/source/{LM_i2s_mic.py → modules/LM_i2s_mic.py} +20 -23
  78. micrOS/source/{LM_ld2410.py → modules/LM_ld2410.py} +3 -3
  79. micrOS/source/{LM_light_sensor.py → modules/LM_light_sensor.py} +22 -26
  80. micrOS/source/modules/LM_mh_z19c.py +198 -0
  81. micrOS/source/modules/LM_neoeffects.py +284 -0
  82. micrOS/source/{LM_neopixel.py → modules/LM_neopixel.py} +26 -31
  83. micrOS/source/{LM_oled.py → modules/LM_oled.py} +28 -20
  84. micrOS/source/{LM_oled_sh1106.py → modules/LM_oled_sh1106.py} +28 -24
  85. micrOS/source/{LM_oled_ui.py → modules/LM_oled_ui.py} +132 -174
  86. micrOS/source/modules/LM_pacman.py +320 -0
  87. micrOS/source/{LM_presence.py → modules/LM_presence.py} +24 -36
  88. micrOS/source/modules/LM_qmi8658.py +204 -0
  89. micrOS/source/{LM_rencoder.py → modules/LM_rencoder.py} +40 -11
  90. micrOS/source/modules/LM_rest.py +81 -0
  91. micrOS/source/{LM_rgb.py → modules/LM_rgb.py} +25 -34
  92. micrOS/source/{LM_rgbcct.py → modules/LM_rgbcct.py} +5 -5
  93. micrOS/source/{LM_roboarm.py → modules/LM_roboarm.py} +37 -45
  94. micrOS/source/modules/LM_robustness.py +137 -0
  95. micrOS/source/{LM_rp2w.py → modules/LM_rp2w.py} +3 -0
  96. micrOS/source/{LM_sdcard.py → modules/LM_sdcard.py} +3 -0
  97. micrOS/source/{LM_servo.py → modules/LM_servo.py} +4 -4
  98. micrOS/source/modules/LM_sound_event.py +751 -0
  99. micrOS/source/{LM_stepper.py → modules/LM_stepper.py} +8 -8
  100. micrOS/source/{LM_switch.py → modules/LM_switch.py} +21 -18
  101. micrOS/source/{LM_system.py → modules/LM_system.py} +96 -59
  102. micrOS/source/modules/LM_tcs3472.py +187 -0
  103. micrOS/source/modules/LM_telegram.py +388 -0
  104. micrOS/source/modules/LM_trackball.py +287 -0
  105. micrOS/source/modules/LM_veml7700.py +159 -0
  106. micrOS/source/modules/LM_web.py +38 -0
  107. micrOS/source/urequests.py +204 -91
  108. {toolkit/workspace/precompiled → micrOS/source/web}/dashboard.html +9 -4
  109. micrOS/source/web/editor.js +440 -0
  110. micrOS/source/web/filesui.html +178 -0
  111. micrOS/source/web/filesui.js +338 -0
  112. micrOS/source/{index.html → web/index.html} +44 -2
  113. micrOS/source/web/uapi.js +103 -0
  114. micrOS/source/web/udashboard.js +129 -0
  115. micrOS/source/web/ustyle.css +55 -0
  116. micrOS/source/web/uwidgets.js +172 -0
  117. micrOS/source/web/uwidgets_pro.js +99 -0
  118. micrOS/utests/__init__.py +0 -0
  119. micrOS/utests/test_scheduler.py +435 -0
  120. {micrOSDevToolKit-2.1.5.data → microsdevtoolkit-2.26.1.data}/scripts/devToolKit.py +47 -4
  121. {micrOSDevToolKit-2.1.5.dist-info → microsdevtoolkit-2.26.1.dist-info}/METADATA +392 -279
  122. microsdevtoolkit-2.26.1.dist-info/RECORD +396 -0
  123. {micrOSDevToolKit-2.1.5.dist-info → microsdevtoolkit-2.26.1.dist-info}/WHEEL +1 -1
  124. toolkit/DevEnvCompile.py +63 -33
  125. toolkit/DevEnvOTA.py +72 -22
  126. toolkit/DevEnvUSB.py +147 -77
  127. toolkit/Gateway.py +9 -9
  128. toolkit/LM_to_compile.dat +12 -4
  129. toolkit/MicrOSDevEnv.py +129 -51
  130. toolkit/WebRepl.py +73 -0
  131. toolkit/dashboard_apps/BackupRestore.py +171 -0
  132. toolkit/dashboard_apps/CCTDemo.py +12 -17
  133. toolkit/dashboard_apps/CCTTest.py +20 -24
  134. toolkit/dashboard_apps/CamStream.py +2 -6
  135. toolkit/dashboard_apps/CatGame.py +14 -16
  136. toolkit/dashboard_apps/Dimmer.py +11 -21
  137. toolkit/dashboard_apps/GetVersion.py +11 -19
  138. toolkit/dashboard_apps/MicrophoneTest.py +2 -7
  139. toolkit/dashboard_apps/NeoEffectsDemo.py +22 -35
  140. toolkit/dashboard_apps/NeopixelTest.py +20 -25
  141. toolkit/dashboard_apps/PresenceTest.py +2 -8
  142. toolkit/dashboard_apps/QMI8685_GYRO.py +68 -0
  143. toolkit/dashboard_apps/RGBTest.py +20 -24
  144. toolkit/dashboard_apps/RoboArm.py +24 -32
  145. toolkit/dashboard_apps/SED_test.py +10 -14
  146. toolkit/dashboard_apps/SensorsTest.py +61 -0
  147. toolkit/dashboard_apps/SystemTest.py +219 -117
  148. toolkit/dashboard_apps/Template_app.py +12 -19
  149. toolkit/dashboard_apps/_app_base.py +34 -0
  150. toolkit/dashboard_apps/_gyro_visualizer.py +78 -0
  151. toolkit/dashboard_apps/uLightDemo.py +15 -24
  152. toolkit/index.html +6 -5
  153. toolkit/lib/LocalMachine.py +6 -1
  154. toolkit/lib/MicrosFiles.py +46 -0
  155. toolkit/lib/Repository.py +64 -0
  156. toolkit/lib/TerminalColors.py +4 -0
  157. toolkit/lib/macroScript.py +371 -0
  158. toolkit/lib/micrOSClient.py +124 -51
  159. toolkit/lib/micrOSClientHistory.py +156 -0
  160. toolkit/lib/pip_package_installer.py +31 -4
  161. toolkit/micrOSdashboard.py +16 -21
  162. toolkit/micrOSlint.py +28 -10
  163. toolkit/simulator_lib/.DS_Store +0 -0
  164. micrOS/source/IO_esp32.py → toolkit/simulator_lib/IO_darwin.py +3 -0
  165. toolkit/simulator_lib/__pycache__/IO_darwin.cpython-312.pyc +0 -0
  166. toolkit/simulator_lib/__pycache__/aioespnow.cpython-312.pyc +0 -0
  167. toolkit/simulator_lib/__pycache__/camera.cpython-312.pyc +0 -0
  168. toolkit/simulator_lib/__pycache__/framebuf.cpython-312.pyc +0 -0
  169. toolkit/simulator_lib/__pycache__/machine.cpython-312.pyc +0 -0
  170. toolkit/simulator_lib/__pycache__/micropython.cpython-312.pyc +0 -0
  171. toolkit/simulator_lib/__pycache__/mip.cpython-312.pyc +0 -0
  172. toolkit/simulator_lib/__pycache__/neopixel.cpython-312.pyc +0 -0
  173. toolkit/simulator_lib/__pycache__/network.cpython-312.pyc +0 -0
  174. toolkit/simulator_lib/__pycache__/sim_common.cpython-312.pyc +0 -0
  175. toolkit/simulator_lib/__pycache__/simgc.cpython-312.pyc +0 -0
  176. toolkit/simulator_lib/__pycache__/simulator.cpython-312.pyc +0 -0
  177. toolkit/simulator_lib/__pycache__/uasyncio.cpython-312.pyc +0 -0
  178. toolkit/simulator_lib/__pycache__/uos.cpython-312.pyc +0 -0
  179. toolkit/simulator_lib/__pycache__/urandom.cpython-312.pyc +0 -0
  180. toolkit/simulator_lib/__pycache__/usocket.cpython-312.pyc +0 -0
  181. toolkit/simulator_lib/__pycache__/ussl.cpython-312.pyc +0 -0
  182. toolkit/simulator_lib/aioespnow.py +28 -0
  183. toolkit/simulator_lib/camera.py +84 -0
  184. toolkit/simulator_lib/dht.py +1 -1
  185. toolkit/simulator_lib/framebuf.py +49 -1
  186. toolkit/simulator_lib/machine.py +32 -2
  187. toolkit/simulator_lib/micropython.py +3 -3
  188. toolkit/simulator_lib/mip.py +165 -0
  189. toolkit/simulator_lib/neopixel.py +3 -2
  190. toolkit/simulator_lib/network.py +2 -1
  191. toolkit/simulator_lib/node_config.json +2 -3
  192. toolkit/simulator_lib/ntptime.py +1 -1
  193. toolkit/simulator_lib/{sim_console.py → sim_common.py} +2 -3
  194. toolkit/simulator_lib/simgc.py +6 -2
  195. toolkit/simulator_lib/simulator.py +138 -46
  196. toolkit/simulator_lib/uasyncio.py +34 -3
  197. toolkit/simulator_lib/uos.py +147 -0
  198. toolkit/simulator_lib/urandom.py +4 -0
  199. toolkit/simulator_lib/usocket.py +5 -1
  200. toolkit/simulator_lib/view01.jpg +0 -0
  201. toolkit/simulator_lib/view02.jpg +0 -0
  202. toolkit/socketClient.py +43 -23
  203. toolkit/user_data/webhooks/generic.py +1 -1
  204. toolkit/user_data/webhooks/macro.py +44 -0
  205. toolkit/user_data/webhooks/template.macro +20 -0
  206. toolkit/user_data/webhooks/template.py +1 -1
  207. toolkit/workspace/precompiled/Auth.mpy +0 -0
  208. toolkit/workspace/precompiled/Common.mpy +0 -0
  209. toolkit/workspace/precompiled/Config.mpy +0 -0
  210. toolkit/workspace/precompiled/Debug.mpy +0 -0
  211. toolkit/workspace/precompiled/Espnow.mpy +0 -0
  212. toolkit/workspace/precompiled/Files.mpy +0 -0
  213. toolkit/workspace/precompiled/Hooks.mpy +0 -0
  214. toolkit/workspace/precompiled/InterConnect.mpy +0 -0
  215. toolkit/workspace/precompiled/Interrupts.mpy +0 -0
  216. toolkit/workspace/precompiled/Logger.mpy +0 -0
  217. toolkit/workspace/precompiled/Network.mpy +0 -0
  218. toolkit/workspace/precompiled/Notify.mpy +0 -0
  219. toolkit/workspace/precompiled/Pacman.mpy +0 -0
  220. toolkit/workspace/precompiled/Scheduler.mpy +0 -0
  221. toolkit/workspace/precompiled/Server.mpy +0 -0
  222. toolkit/workspace/precompiled/Shell.mpy +0 -0
  223. toolkit/workspace/precompiled/Tasks.mpy +0 -0
  224. toolkit/workspace/precompiled/Time.mpy +0 -0
  225. toolkit/workspace/precompiled/Types.mpy +0 -0
  226. toolkit/workspace/precompiled/Web.mpy +0 -0
  227. toolkit/workspace/precompiled/_mpy.version +1 -1
  228. toolkit/workspace/precompiled/config/_git.keep +0 -0
  229. toolkit/workspace/precompiled/helpers.mpy +0 -0
  230. toolkit/workspace/precompiled/micrOS.mpy +0 -0
  231. toolkit/workspace/precompiled/micrOSloader.mpy +0 -0
  232. toolkit/workspace/precompiled/microIO.mpy +0 -0
  233. toolkit/workspace/precompiled/modules/IO_esp32.mpy +0 -0
  234. toolkit/workspace/precompiled/modules/IO_esp32c3.mpy +0 -0
  235. toolkit/workspace/precompiled/modules/IO_esp32c6.mpy +0 -0
  236. toolkit/workspace/precompiled/modules/IO_esp32s2.mpy +0 -0
  237. toolkit/workspace/precompiled/modules/IO_esp32s3.mpy +0 -0
  238. toolkit/workspace/precompiled/modules/IO_m5stamp.mpy +0 -0
  239. toolkit/workspace/precompiled/modules/IO_qtpy.mpy +0 -0
  240. toolkit/workspace/precompiled/modules/IO_rp2.mpy +0 -0
  241. toolkit/workspace/precompiled/modules/IO_tinypico.mpy +0 -0
  242. toolkit/workspace/precompiled/modules/LM_L298N.mpy +0 -0
  243. {micrOS/source → toolkit/workspace/precompiled/modules}/LM_L9110_DCmotor.py +4 -4
  244. toolkit/workspace/precompiled/modules/LM_OV2640.mpy +0 -0
  245. toolkit/workspace/precompiled/{LM_VL53L0X.py → modules/LM_VL53L0X.py} +5 -5
  246. toolkit/workspace/precompiled/modules/LM_aht10.mpy +0 -0
  247. toolkit/workspace/precompiled/modules/LM_bme280.mpy +0 -0
  248. toolkit/workspace/precompiled/{LM_buzzer.mpy → modules/LM_buzzer.mpy} +0 -0
  249. toolkit/workspace/precompiled/modules/LM_cct.mpy +0 -0
  250. toolkit/workspace/precompiled/modules/LM_cluster.mpy +0 -0
  251. toolkit/workspace/precompiled/modules/LM_co2.mpy +0 -0
  252. toolkit/workspace/precompiled/modules/LM_dht11.mpy +0 -0
  253. toolkit/workspace/precompiled/modules/LM_dht22.mpy +0 -0
  254. toolkit/workspace/precompiled/modules/LM_dimmer.mpy +0 -0
  255. toolkit/workspace/precompiled/modules/LM_distance.mpy +0 -0
  256. toolkit/workspace/precompiled/modules/LM_ds18.mpy +0 -0
  257. toolkit/workspace/precompiled/{LM_esp32.py → modules/LM_esp32.py} +16 -4
  258. toolkit/workspace/precompiled/modules/LM_espnow.py +53 -0
  259. toolkit/workspace/precompiled/modules/LM_fileserver.mpy +0 -0
  260. toolkit/workspace/precompiled/{LM_gameOfLife.mpy → modules/LM_gameOfLife.mpy} +0 -0
  261. toolkit/workspace/precompiled/modules/LM_genIO.mpy +0 -0
  262. toolkit/workspace/precompiled/modules/LM_haptic.mpy +0 -0
  263. toolkit/workspace/precompiled/modules/LM_i2c.py +61 -0
  264. toolkit/workspace/precompiled/modules/LM_i2s_mic.mpy +0 -0
  265. toolkit/workspace/precompiled/{LM_ld2410.mpy → modules/LM_ld2410.mpy} +0 -0
  266. toolkit/workspace/precompiled/modules/LM_light_sensor.mpy +0 -0
  267. toolkit/workspace/precompiled/modules/LM_mh_z19c.py +198 -0
  268. toolkit/workspace/precompiled/modules/LM_neoeffects.mpy +0 -0
  269. toolkit/workspace/precompiled/modules/LM_neopixel.mpy +0 -0
  270. toolkit/workspace/precompiled/modules/LM_oled.mpy +0 -0
  271. toolkit/workspace/precompiled/modules/LM_oled_sh1106.mpy +0 -0
  272. toolkit/workspace/precompiled/modules/LM_oled_ui.mpy +0 -0
  273. toolkit/workspace/precompiled/modules/LM_pacman.mpy +0 -0
  274. toolkit/workspace/precompiled/modules/LM_presence.mpy +0 -0
  275. toolkit/workspace/precompiled/modules/LM_qmi8658.py +204 -0
  276. toolkit/workspace/precompiled/{LM_rencoder.py → modules/LM_rencoder.py} +40 -11
  277. toolkit/workspace/precompiled/modules/LM_rest.mpy +0 -0
  278. toolkit/workspace/precompiled/modules/LM_rgb.mpy +0 -0
  279. toolkit/workspace/precompiled/{LM_rgbcct.mpy → modules/LM_rgbcct.mpy} +0 -0
  280. toolkit/workspace/precompiled/modules/LM_roboarm.mpy +0 -0
  281. toolkit/workspace/precompiled/modules/LM_robustness.py +137 -0
  282. toolkit/workspace/precompiled/{LM_rp2w.py → modules/LM_rp2w.py} +3 -0
  283. toolkit/workspace/precompiled/{LM_sdcard.py → modules/LM_sdcard.py} +3 -0
  284. toolkit/workspace/precompiled/{LM_servo.mpy → modules/LM_servo.mpy} +0 -0
  285. toolkit/workspace/precompiled/modules/LM_sound_event.mpy +0 -0
  286. toolkit/workspace/precompiled/{LM_stepper.mpy → modules/LM_stepper.mpy} +0 -0
  287. toolkit/workspace/precompiled/modules/LM_switch.mpy +0 -0
  288. toolkit/workspace/precompiled/modules/LM_system.mpy +0 -0
  289. toolkit/workspace/precompiled/modules/LM_tcs3472.py +187 -0
  290. toolkit/workspace/precompiled/modules/LM_telegram.mpy +0 -0
  291. toolkit/workspace/precompiled/{LM_tinyrgb.mpy → modules/LM_tinyrgb.mpy} +0 -0
  292. toolkit/workspace/precompiled/modules/LM_trackball.mpy +0 -0
  293. toolkit/workspace/precompiled/modules/LM_veml7700.mpy +0 -0
  294. toolkit/workspace/precompiled/modules/LM_web.mpy +0 -0
  295. toolkit/workspace/precompiled/urequests.mpy +0 -0
  296. {micrOS/source → toolkit/workspace/precompiled/web}/dashboard.html +9 -4
  297. toolkit/workspace/precompiled/web/editor.js +440 -0
  298. toolkit/workspace/precompiled/web/filesui.html +178 -0
  299. toolkit/workspace/precompiled/web/filesui.js +338 -0
  300. toolkit/workspace/precompiled/{index.html → web/index.html} +44 -2
  301. toolkit/workspace/precompiled/web/uapi.js +103 -0
  302. toolkit/workspace/precompiled/web/udashboard.js +129 -0
  303. toolkit/workspace/precompiled/web/ustyle.css +55 -0
  304. toolkit/workspace/precompiled/web/uwidgets.js +172 -0
  305. toolkit/workspace/precompiled/web/uwidgets_pro.js +99 -0
  306. env/driver_cp210x/CH34XSER_MAC/CH34X_DRV_INSTALL_INSTRUCTIONS.pdf +0 -0
  307. env/driver_cp210x/CH34XSER_MAC/CH34xVCPDriver.pkg +0 -0
  308. micrOS/micropython/esp32-20231005-v1.21.0.bin +0 -0
  309. micrOS/micropython/esp32c3-GENERIC-20240105-v1.22.1.bin +0 -0
  310. micrOS/micropython/esp32c3-GENERIC-20240222-v1.22.2.bin +0 -0
  311. micrOS/micropython/esp32s2-GENERIC-20240105-v1.22.1.bin +0 -0
  312. micrOS/micropython/esp32s2-LOLIN_MINI-20220618-v1.19.1.bin +0 -0
  313. micrOS/micropython/esp32s3-GENERIC-20240105-v1.22.1.bin +0 -0
  314. micrOS/micropython/esp32s3_spiram_oct-20231005-v1.21.0.bin +0 -0
  315. micrOS/micropython/rpi-pico-w-20231005-v1.21.0.uf2 +0 -0
  316. micrOS/micropython/tinypico-20231005-v1.21.0.bin +0 -0
  317. micrOS/micropython/tinypico-usbc-UM-20240105-v1.22.1.bin +0 -0
  318. micrOS/source/LM_L298N_DCmotor.py +0 -86
  319. micrOS/source/LM_catgame.py +0 -74
  320. micrOS/source/LM_dashboard_be.py +0 -37
  321. micrOS/source/LM_demo.py +0 -85
  322. micrOS/source/LM_distance.py +0 -88
  323. micrOS/source/LM_i2c.py +0 -44
  324. micrOS/source/LM_intercon.py +0 -57
  325. micrOS/source/LM_keychain.py +0 -318
  326. micrOS/source/LM_lmpacman.py +0 -126
  327. micrOS/source/LM_neoeffects.py +0 -327
  328. micrOS/source/LM_pet_feeder.py +0 -76
  329. micrOS/source/LM_ph_sensor.py +0 -51
  330. micrOS/source/LM_rest.py +0 -40
  331. micrOS/source/LM_robustness.py +0 -73
  332. micrOS/source/LM_telegram.py +0 -96
  333. micrOS/source/reset.py +0 -11
  334. micrOS/source/uapi.js +0 -76
  335. micrOS/source/udashboard.js +0 -137
  336. micrOS/source/ustyle.css +0 -28
  337. micrOS/source/uwidgets.js +0 -179
  338. micrOSDevToolKit-2.1.5.dist-info/RECORD +0 -337
  339. toolkit/dashboard_apps/AirQualityBME280.py +0 -36
  340. toolkit/dashboard_apps/AirQualityDHT22_CO2.py +0 -36
  341. toolkit/lib/file_extensions.py +0 -16
  342. toolkit/simulator_lib/__pycache__/LP_darwin.cpython-312.pyc +0 -0
  343. toolkit/simulator_lib/__pycache__/LP_darwin.cpython-38.pyc +0 -0
  344. toolkit/simulator_lib/__pycache__/LP_darwin.cpython-39.pyc +0 -0
  345. toolkit/simulator_lib/__pycache__/sim_console.cpython-312.pyc +0 -0
  346. toolkit/simulator_lib/__pycache__/sim_console.cpython-38.pyc +0 -0
  347. toolkit/simulator_lib/__pycache__/sim_console.cpython-39.pyc +0 -0
  348. toolkit/workspace/precompiled/IO_esp32.mpy +0 -0
  349. toolkit/workspace/precompiled/IO_esp32c3.mpy +0 -0
  350. toolkit/workspace/precompiled/IO_esp32s2.mpy +0 -0
  351. toolkit/workspace/precompiled/IO_esp32s3.mpy +0 -0
  352. toolkit/workspace/precompiled/IO_qtpy.mpy +0 -0
  353. toolkit/workspace/precompiled/IO_rp2.mpy +0 -0
  354. toolkit/workspace/precompiled/IO_tinypico.mpy +0 -0
  355. toolkit/workspace/precompiled/LM_L298N_DCmotor.mpy +0 -0
  356. toolkit/workspace/precompiled/LM_OV2640.mpy +0 -0
  357. toolkit/workspace/precompiled/LM_aht10.mpy +0 -0
  358. toolkit/workspace/precompiled/LM_bme280.mpy +0 -0
  359. toolkit/workspace/precompiled/LM_catgame.py +0 -74
  360. toolkit/workspace/precompiled/LM_cct.mpy +0 -0
  361. toolkit/workspace/precompiled/LM_co2.mpy +0 -0
  362. toolkit/workspace/precompiled/LM_dashboard_be.py +0 -37
  363. toolkit/workspace/precompiled/LM_demo.py +0 -85
  364. toolkit/workspace/precompiled/LM_dht11.mpy +0 -0
  365. toolkit/workspace/precompiled/LM_dht22.mpy +0 -0
  366. toolkit/workspace/precompiled/LM_dimmer.mpy +0 -0
  367. toolkit/workspace/precompiled/LM_distance.py +0 -88
  368. toolkit/workspace/precompiled/LM_ds18.mpy +0 -0
  369. toolkit/workspace/precompiled/LM_genIO.mpy +0 -0
  370. toolkit/workspace/precompiled/LM_i2c.py +0 -44
  371. toolkit/workspace/precompiled/LM_i2s_mic.mpy +0 -0
  372. toolkit/workspace/precompiled/LM_intercon.mpy +0 -0
  373. toolkit/workspace/precompiled/LM_keychain.mpy +0 -0
  374. toolkit/workspace/precompiled/LM_light_sensor.mpy +0 -0
  375. toolkit/workspace/precompiled/LM_lmpacman.mpy +0 -0
  376. toolkit/workspace/precompiled/LM_neoeffects.mpy +0 -0
  377. toolkit/workspace/precompiled/LM_neopixel.mpy +0 -0
  378. toolkit/workspace/precompiled/LM_oled.mpy +0 -0
  379. toolkit/workspace/precompiled/LM_oled_sh1106.mpy +0 -0
  380. toolkit/workspace/precompiled/LM_oled_ui.mpy +0 -0
  381. toolkit/workspace/precompiled/LM_pet_feeder.py +0 -76
  382. toolkit/workspace/precompiled/LM_ph_sensor.py +0 -51
  383. toolkit/workspace/precompiled/LM_presence.mpy +0 -0
  384. toolkit/workspace/precompiled/LM_rest.mpy +0 -0
  385. toolkit/workspace/precompiled/LM_rgb.mpy +0 -0
  386. toolkit/workspace/precompiled/LM_roboarm.mpy +0 -0
  387. toolkit/workspace/precompiled/LM_robustness.py +0 -73
  388. toolkit/workspace/precompiled/LM_switch.mpy +0 -0
  389. toolkit/workspace/precompiled/LM_system.mpy +0 -0
  390. toolkit/workspace/precompiled/LM_telegram.mpy +0 -0
  391. toolkit/workspace/precompiled/reset.mpy +0 -0
  392. toolkit/workspace/precompiled/uapi.js +0 -76
  393. toolkit/workspace/precompiled/udashboard.js +0 -137
  394. toolkit/workspace/precompiled/ustyle.css +0 -28
  395. toolkit/workspace/precompiled/uwidgets.js +0 -179
  396. /toolkit/user_data/node_config_archive/.include → /micrOS/source/config/_git.keep +0 -0
  397. /micrOS/source/{IO_rp2.py → modules/IO_rp2.py} +0 -0
  398. /micrOS/source/{LM_tinyrgb.py → modules/LM_tinyrgb.py} +0 -0
  399. {micrOSDevToolKit-2.1.5.dist-info → microsdevtoolkit-2.26.1.dist-info/licenses}/LICENSE +0 -0
  400. {micrOSDevToolKit-2.1.5.dist-info → microsdevtoolkit-2.26.1.dist-info}/top_level.txt +0 -0
@@ -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}")