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
micrOS/source/Web.py ADDED
@@ -0,0 +1,485 @@
1
+ """
2
+ Module is responsible for webserver environment
3
+ dedicated to micrOS framework.
4
+ Built-in-function:
5
+ - response
6
+ - landing page: index.html
7
+ - rest/ - call load modules, e.x.: system/top
8
+ - file response (.html, .css, .js, .jpeg) - generic file server feature
9
+ - "virtual" endpoints - to reply from script on a defined endpoint
10
+ - stream - stream data (jpeg) function
11
+
12
+ Designed by Marcell Ban aka BxNxM and szeka9 (GitHub)
13
+ """
14
+
15
+ from re import compile
16
+ from json import dumps, loads
17
+ from uos import stat
18
+ import uasyncio as asyncio
19
+ from Tasks import lm_exec, NativeTask, lm_is_loaded
20
+ from Debug import syslog, console_write
21
+ from Config import cfgget
22
+ from Files import OSPath, path_join, abs_path
23
+ try:
24
+ from gc import mem_free, collect
25
+ except:
26
+ from simgc import mem_free, collect # simulator mode
27
+
28
+
29
+ class ServerBusyException(Exception):
30
+ pass
31
+
32
+ class ConnectionError(Exception):
33
+ pass
34
+
35
+ class HeaderDecodingError(Exception):
36
+ pass
37
+
38
+ def url_path_resolve(path:str) -> tuple[bool, str]:
39
+ """
40
+ :param path: input path
41
+ Return: isError, absolutePath
42
+ """
43
+ # $Extended mount check: WEB_MOUNTS (/modules and /web)
44
+ path = path.lstrip("/")
45
+ if path.startswith("$"):
46
+ mount_alias = path.split("/")[0]
47
+ mount_path = WebEngine.WEB_MOUNTS.get(mount_alias, None)
48
+ if mount_path is None:
49
+ return True, f"Invalid mount point: {mount_alias}"
50
+ mount_path = path.replace(mount_alias, mount_path)
51
+ return False, mount_path
52
+ # Default web path: /web
53
+ return False, path_join(OSPath.WEB, path)
54
+
55
+
56
+ class WebEngine:
57
+ __slots__ = ["client"]
58
+ ENDPOINTS = {}
59
+ AUTH = cfgget('auth')
60
+ VERSION = "n/a"
61
+ REQ200 = "HTTP/1.1 200 OK\r\nContent-Type: {dtype}\r\nContent-Length:{len}\r\n\r\n{data}"
62
+ REQ200_CHUNKED = "HTTP/1.1 200 OK\r\nContent-Type: {dtype}\r\nTransfer-Encoding: chunked\r\n\r\n"
63
+ REQ400 = "HTTP/1.1 400 Bad Request\r\nContent-Type: text/plain\r\nContent-Length: {len}\r\n\r\n{data}"
64
+ REQ404 = "HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\nContent-Length: {len}\r\n\r\n{data}"
65
+ REQ500 = "HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/plain\r\nContent-Length: {len}\r\n\r\n{data}"
66
+ REQ503 = "HTTP/1.1 503 Service Unavailable\r\nContent-Type: text/plain\r\nContent-Length: {len}\r\n\r\n{data}"
67
+ CONTENT_TYPES = {"html": "text/html",
68
+ "css": "text/css",
69
+ "js": "application/javascript",
70
+ "json": "application/json",
71
+ "ico": "image/x-icon", # favicon
72
+ "jpeg": "image/jpeg",
73
+ "png": "image/png",
74
+ "gif": "image/gif"}
75
+ METHODS = ("GET", "POST", "DELETE")
76
+ WEB_MOUNTS = {}
77
+ # MEMORY DIMENSIONING FOR THE BEST PERFORMANCE
78
+ # (is_limited, free_mem, min_mem_req_kb, chunk_threshold_kb, chunk_size_bytes)
79
+ MEM_DIM = (None, -1, 20, 2, 1024)
80
+ READ_TIMEOUT_SEC = 10
81
+
82
+ def __init__(self, client, version):
83
+ self.client = client
84
+ WebEngine.VERSION = version
85
+
86
+ async def a_send(self, response:str, encode:str='utf8'):
87
+ raise NotImplementedError("Child class must implement a_send coroutine.")
88
+
89
+ @staticmethod
90
+ def file_type(path:str):
91
+ """File dynamic Content-Type handling"""
92
+ default_type = "text/plain"
93
+ # Extract the file extension
94
+ ext = path.rsplit('.', 1)[-1]
95
+ # Return the content type based on the file extension
96
+ return WebEngine.CONTENT_TYPES.get(ext, default_type)
97
+
98
+ @staticmethod
99
+ def parse_headers(raw_headers:bytes):
100
+ """Basic parser to extract HTTP/MIME headers without guarantees on RFC compliance"""
101
+ header_lines = raw_headers.decode('ascii').split('\r\n')
102
+ headers = {}
103
+ for line in header_lines:
104
+ # TODO: support for UTF-8 in field values (e.g filenames), can be board dependent
105
+ if any(ord(c) > 127 for c in line):
106
+ raise HeaderDecodingError('Non-ASCII character found in the request')
107
+ if ':' not in line:
108
+ continue
109
+ name, value = line.split(':', 1)
110
+ headers[name.strip().lower()] = value.strip()
111
+ return headers
112
+
113
+ @staticmethod
114
+ def dimensioning():
115
+ # (is_limited, free_mem, min_mem_req_kb, chunk_threshold_kb, chunk_size_bytes)
116
+ if WebEngine.MEM_DIM[0] is None:
117
+ collect()
118
+ mfree = mem_free() // 1024 # <- bytes->kb
119
+ if mfree < WebEngine.MEM_DIM[2]:
120
+ # Too low memory - No Web UI - under 20kb
121
+ WebEngine.MEM_DIM = (True, mfree) + WebEngine.MEM_DIM[2:]
122
+ return WebEngine.MEM_DIM
123
+ if mfree < WebEngine.MEM_DIM[2] * 5:
124
+ # Normal: default memory setup - Web UI - under 100kb
125
+ WebEngine.MEM_DIM = (False, mfree) + WebEngine.MEM_DIM[2:]
126
+ return WebEngine.MEM_DIM
127
+ # Large memory - Web UI - over 100kb
128
+ upscale = max(1, min(25, int((mfree // WebEngine.MEM_DIM[2]) // 2))) # ~50% free mem budget
129
+ WebEngine.MEM_DIM = (False, mfree, WebEngine.MEM_DIM[2],
130
+ WebEngine.MEM_DIM[3]*upscale, WebEngine.MEM_DIM[4]*upscale)
131
+ syslog(f"[INFO] WebEngine ChunkUpscale ({upscale}x): {WebEngine.MEM_DIM}")
132
+ return WebEngine.MEM_DIM
133
+
134
+ @staticmethod
135
+ def register(endpoint:str, callback:object|str, method:str='GET') -> None:
136
+ """
137
+ PUBLIC METHOD FOR LMs: Webengine endpoint registration handler
138
+ :param endpoint: name of the endpoint
139
+ :param callback: callback function (WebEngine compatible: return: html_type, content)
140
+ :param method: HTTP method name
141
+ """
142
+ if not endpoint in WebEngine.ENDPOINTS:
143
+ WebEngine.ENDPOINTS[endpoint] = {}
144
+ if method not in WebEngine.METHODS:
145
+ raise ValueError(f"method must be one of {WebEngine.METHODS}")
146
+ WebEngine.ENDPOINTS[endpoint][method] = callback
147
+ return
148
+
149
+ @staticmethod
150
+ def web_mounts(modules:bool=None, data:bool=None, logs:bool=None) -> dict:
151
+ """
152
+ PUBLIC METHOD FOR LMs: WebEngine access path handler
153
+ - default path: /web
154
+ - extended path access: with $modules and $data dirs
155
+ """
156
+ def _update(state, alias, path):
157
+ if state:
158
+ WebEngine.WEB_MOUNTS[alias] = path
159
+ elif WebEngine.WEB_MOUNTS.get(alias, False):
160
+ del WebEngine.WEB_MOUNTS[alias]
161
+ if modules is not None:
162
+ # Set modules dir access
163
+ _update(modules, "$modules", OSPath.MODULES)
164
+ if data is not None:
165
+ # Set data dir access
166
+ _update(data, "$data", OSPath.DATA)
167
+ if logs is not None:
168
+ # Set logs dir access
169
+ _update(logs, "$logs", OSPath.LOGS)
170
+ return WebEngine.WEB_MOUNTS
171
+
172
+ async def response(self, request:bytes) -> bool:
173
+ """HTTP GET/POST REQUEST - WEB INTERFACE"""
174
+ # [0] PROTOCOL VALIDATION AND PARSING
175
+ if not request:
176
+ _err = "Empty request"
177
+ await self.a_send(self.REQ400.format(len=len(_err), data=_err))
178
+ return True
179
+ status_line = request.split(b'\r\n', 1)[0]
180
+ status_parts = status_line.decode('ascii').split()
181
+ if len(status_parts) != 3:
182
+ if status_parts[0] not in self.METHODS:
183
+ # INVALID REQUEST - REQUEST OVERFLOW - NO RESPONSE
184
+ syslog(f"[WARN] WebCli REQ Overflow: {len(status_parts)}")
185
+ return False # Close connection...
186
+ _err = "Malformed request line"
187
+ await self.a_send(self.REQ400.format(len=len(_err), data=_err))
188
+ return True
189
+ _method, url, _version = status_parts
190
+ url = abs_path(url)
191
+ if _method not in self.METHODS or not _version.startswith('HTTP/'):
192
+ _err = f"Unsupported method: {_method} {_version}"
193
+ await self.a_send(self.REQ400.format(len=len(_err), data=_err))
194
+ return True
195
+ payload = request[len(status_line):]
196
+ blank_idx = payload.find(b'\r\n\r\n')
197
+ try:
198
+ if blank_idx > -1:
199
+ headers = self.parse_headers(payload[0:blank_idx])
200
+ body = payload[blank_idx + 4:]
201
+ else:
202
+ headers = self.parse_headers(payload)
203
+ body = b''
204
+ except HeaderDecodingError:
205
+ await self.client.a_send(self.REQ400.format(len=18, data='400 Invalid Header'))
206
+ return True
207
+ # [1] REST API GET ENDPOINT [/rest]
208
+ if url.startswith('/rest') and _method == "GET":
209
+ self.client.console("[WebCli] --- /rest ACCEPT")
210
+ try:
211
+ await self.client.a_send(WebEngine.rest(url))
212
+ except Exception as e:
213
+ await self.client.a_send(self.REQ404.format(len=len(str(e)), data=e))
214
+ return True
215
+ # [2] DYNAMIC/USER ENDPOINTS (from Load Modules)
216
+ if await self.endpoints(url, _method, headers, body):
217
+ return True
218
+ # MEMORY DIMENSIONING
219
+ mem_limited, free, *_ = self.dimensioning()
220
+ if mem_limited:
221
+ _err = f"Low memory ({free} kb): serving API only."
222
+ await self.a_send(self.REQ400.format(len=len(_err), data=_err))
223
+ return True
224
+ # [3] HOME/PAGE ENDPOINT(s) [default: / -> /index.html]
225
+ if url.startswith('/') and _method == "GET":
226
+ resource = 'index.html' if url == '/' else url.lstrip('/')
227
+ self.client.console(f"[WebCli] --- {url} ACCEPT -> {resource}")
228
+ if "/" not in resource and resource.split('.')[-1] not in self.CONTENT_TYPES:
229
+ # Validate /web root types only - otherwise default fallback type for unknowns: "text/plain"
230
+ await self.client.a_send(self.REQ404.format(len=27, data='404 Not Supported File Type'))
231
+ return True
232
+ try:
233
+ # SEND RESOURCE CONTENT: HTML, JS, CSS (WebEngine.CONTENT_TYPES)
234
+ await self.file_transfer(resource)
235
+ except OSError:
236
+ await self.client.a_send(self.REQ404.format(len=13, data='404 Not Found'))
237
+ except MemoryError as e:
238
+ syslog(f"[ERR] WebCli {resource}: {e}")
239
+ await self.client.a_send(self.REQ500.format(len=17, data='500 Out of Memory'))
240
+ except Exception as e:
241
+ syslog(f"[ERR] WebCli {resource}: {e}")
242
+ await self.client.a_send(self.REQ500.format(len=16, data='500 Server Error'))
243
+ return True
244
+ # INVALID/BAD REQUEST
245
+ await self.client.a_send(self.REQ400.format(len=15, data='400 Bad Request'))
246
+ return True
247
+
248
+ @staticmethod
249
+ def rest(url):
250
+ resp_schema = {'result': {}, 'state': False}
251
+ cmd = url.replace('/rest', '')
252
+ if len(cmd) > 1:
253
+ # REST sub-parameter handling (rest commands) TODO: create url_decode helper for: " ' >
254
+ cmd = (cmd.replace('/', ' ').replace('-', ' ').replace("%3E", ">")
255
+ .replace('%22', '"').replace('%E2%80%9C', '"').replace('%E2%80%9D', '"')
256
+ .strip().split())
257
+ # EXECUTE COMMAND - LoadModule
258
+ if WebEngine.AUTH:
259
+ state, out = lm_exec(cmd, jsonify=True) if lm_is_loaded(cmd[0]) else (True, 'Auth:Protected')
260
+ else:
261
+ state, out = lm_exec(cmd, jsonify=True)
262
+ try:
263
+ resp_schema['result'] = loads(out) # Load again ... hack for embedded json converter...
264
+ except:
265
+ resp_schema['result'] = out
266
+ resp_schema['state'] = state
267
+ else:
268
+ resp_schema['result'] = {"micrOS": WebEngine.VERSION, 'node': cfgget('devfid'), 'auth': WebEngine.AUTH}
269
+ if len(tuple(WebEngine.ENDPOINTS.keys())) > 0:
270
+ resp_schema['result']['usr_endpoints'] = tuple(WebEngine.ENDPOINTS)
271
+ resp_schema['state'] = True
272
+ response = dumps(resp_schema)
273
+ return WebEngine.REQ200.format(dtype='text/html', len=len(response), data=response)
274
+
275
+ async def endpoints(self, url:str, method:str, headers:dict, body:bytes):
276
+ url = url[1:] # Cut first / char
277
+ if url in WebEngine.ENDPOINTS and method in WebEngine.ENDPOINTS[url]: # TODO: support for query parameters
278
+ console_write(f"[WebCli] endpoint: {url}")
279
+ # Registered endpoint was found - exec callback
280
+ try:
281
+ # RESOLVE ENDPOINT CALLBACK
282
+ # dtype:
283
+ # one-shot (simple MIME types): image/jpeg | text/html | text/plain - data: raw
284
+ # task (streaming MIME types): multipart/x-mixed-replace | multipart/form-data - data: dict{callback,content-type}
285
+ # content-type: image/jpeg | audio/l16;*
286
+ if body and (response := await self.handle_with_body(url, method, headers, body)):
287
+ dtype, data = response
288
+ else:
289
+ # TODO: contract needed for passing headers
290
+ callback = WebEngine.ENDPOINTS[url][method]
291
+ if callable(callback):
292
+ dtype, data = WebEngine.ENDPOINTS[url][method]()
293
+ else:
294
+ # Endpoint callback is a file reference
295
+ await self.file_transfer(callback)
296
+ return True
297
+
298
+ if dtype == 'image/jpeg':
299
+ resp = f"HTTP/1.1 200 OK\r\nContent-Type: {dtype}\r\nContent-Length:{len(data)}\r\n\r\n".encode('ascii') + data
300
+ await self.client.a_send(resp, encode=None)
301
+ elif dtype in ('multipart/x-mixed-replace', 'multipart/form-data'):
302
+ resp_headers = f"HTTP/1.1 200 OK\r\nContent-Type: {dtype}; boundary=\"micrOS_boundary\"\r\n\r\n"
303
+ await self.client.a_send(resp_headers)
304
+ # Start Native stream async task
305
+ task = NativeTask()
306
+ tag=f"web.stream_{self.client.client_id.replace('W', '')}"
307
+ task.create(callback=self.stream(data['callback'], task, data['content-type']), tag=tag)
308
+ else: # dtype: text/html or text/plain
309
+ await self.client.a_send(WebEngine.REQ200.format(dtype=dtype, len=len(data), data=data))
310
+ except ServerBusyException as e:
311
+ await self.client.a_send(self.REQ503.format(len=len(str(e)), data=e))
312
+ except HeaderDecodingError as e:
313
+ await self.client.a_send(self.REQ400.format(len=len(str(e)), data=e))
314
+ except Exception as e:
315
+ if self.client.connected:
316
+ await self.client.a_send(self.REQ400.format(len=len(str(e)), data=e))
317
+ err = f"WebCli endpoints {url}: {e}" if "ReadOnly" in str(e) else f"[ERR] WebCli endpoints {url}: {e}"
318
+ syslog(err)
319
+ return True # Registered endpoint was found and executed
320
+ return False # Not registered endpoint
321
+
322
+ async def file_transfer(self, web_resource:str):
323
+ """
324
+ Send a file to the client using either normal or chunked HTTP transfer.
325
+ :param web_resource: Path to the file to be served.
326
+ """
327
+ # Resolve
328
+ err, web_resource = url_path_resolve(web_resource)
329
+ if err:
330
+ await self.client.a_send(self.REQ404.format(len=19, data='404 Mount Not Found'))
331
+ return False
332
+ with open(web_resource, "rb") as file:
333
+ chunking_threshold_kb = WebEngine.MEM_DIM[3]
334
+ chunk_size_bytes = WebEngine.MEM_DIM[4]
335
+ if stat(web_resource)[6] > chunking_threshold_kb * 1024:
336
+ # Chunked HTTP transfer
337
+ response = self.REQ200_CHUNKED.format(dtype=WebEngine.file_type(web_resource))
338
+ await self.client.a_send(response)
339
+ data = file.read(chunk_size_bytes)
340
+ while data:
341
+ await self.client.a_send(f"{len(data):X}\r\n".encode(), None)
342
+ await self.client.a_send(data, None)
343
+ await self.client.a_send(b'\r\n', None)
344
+ data = file.read(chunk_size_bytes)
345
+ await self.client.a_send(b'0\r\n\r\n', None)
346
+ return
347
+ # Normal HTTP transfer
348
+ data = file.read()
349
+ response = self.REQ200.format(dtype=WebEngine.file_type(web_resource), len=len(data), data="")
350
+ await self.client.a_send(response)
351
+ await self.client.a_send(data, None)
352
+ return True
353
+
354
+ async def stream(self, callback, task, content_type):
355
+ """
356
+ Async stream method.
357
+ :param callback: sync or async function callback (auto-detect) WARNING: works for functions only (not methods!)
358
+ :param task: NativeTask instance (that the stream runs in)
359
+ :param content_type: image/jpeg | audio/l16;*
360
+ """
361
+ is_coroutine = 'generator' in str(type(callback)) # async function callback auto-detect
362
+ with task:
363
+ task.out = 'Stream started'
364
+ data_to_send = b''
365
+
366
+ while self.client.connected and data_to_send is not None:
367
+ data_to_send = await callback() if is_coroutine else callback()
368
+ part = (f"\r\n--micrOS_boundary\r\nContent-Type: {content_type}\r\n\r\n").encode('ascii') + data_to_send
369
+ task.out = 'Data sent'
370
+ await self.client.a_send(part, encode=None)
371
+ await asyncio.sleep_ms(10)
372
+
373
+ # Gracefully terminate the stream
374
+ if self.client.connected:
375
+ closing_boundary = '\r\n--micrOS_boundary--\r\n'
376
+ await self.client.a_send(closing_boundary)
377
+ await self.client.close()
378
+ task.out = 'Finished stream'
379
+
380
+ async def handle_with_body(self, url:str, method:str, headers:dict, body:bytes):
381
+ """
382
+ Handle requests with a body.
383
+ :param url: synchronous function callback, expecting parts passed as bytes
384
+ :param boundary: boundary parameter
385
+ :param body: request body, handled depending on headers (e.g. content type)
386
+ """
387
+ content_length = int(headers.get("content-length", -1))
388
+ is_multipart = False
389
+ # [1] Header parsing
390
+ if headers:
391
+ if 'content-type' in headers:
392
+ multipart_regex = compile("multipart/form-data;\s*boundary=\"?([^\";\r\n]+)\"?")
393
+ if (multipart_match := multipart_regex.match(headers['content-type'])):
394
+ is_multipart = True
395
+ multipart_boundary = multipart_match.group(1).encode('ascii')
396
+ # [2] Header dependent body handling
397
+ if is_multipart:
398
+ return await self.recv_multipart(WebEngine.ENDPOINTS[url][method], multipart_boundary, bytearray(body), content_length)
399
+ # [3] Default handling
400
+ # TODO: contract needed for passing headers
401
+ return WebEngine.ENDPOINTS[url][method](body)
402
+
403
+ async def recv_multipart(self, callback, boundary:bytes, part_buffer:bytearray, content_length:int):
404
+ """
405
+ Receives multipart body and runs a callback on each complete part.
406
+ :param callback: synchronous callback function clb(part_headers, part_body), parsed headers and raw body
407
+ :param boundary: boundary parameter
408
+ :param part_buffer: contains initial request body, remaining parts are read if the body contains no closing delimiter
409
+ :param content_length: content length number
410
+ """
411
+ dtype = 'text/plain'
412
+ data = 'failed to parse'
413
+ delimiter = b'--' + boundary
414
+ delimiter_line = delimiter + b'\r\n'
415
+ close_delimiter = delimiter + b'--'
416
+ at_start = True
417
+ first_part = True
418
+ actual_length = len(part_buffer)
419
+
420
+ if content_length <= 0:
421
+ raise ValueError("Invalid content-length")
422
+
423
+ async def read_more():
424
+ error, more = await self.client.read(decoding=None, timeout_seconds=self.READ_TIMEOUT_SEC)
425
+ if error:
426
+ await self.client.close()
427
+ raise ConnectionError()
428
+ if not more:
429
+ raise ValueError('Unexpected EOF in multipart body')
430
+ return more
431
+
432
+ def parse_part(part:bytes):
433
+ blank_idx = part.find(b'\r\n\r\n')
434
+ if blank_idx == -1:
435
+ raise ValueError('Headers could not be parsed')
436
+ headers = self.parse_headers(part[:blank_idx])
437
+ body = part[blank_idx + 4:]
438
+ return headers, body
439
+
440
+ while True:
441
+ # [1] Read until a complete part is received
442
+ while b'\r\n' not in part_buffer:
443
+ more = await read_more()
444
+ if actual_length + len(more) > content_length:
445
+ raise ValueError('Invalid content-length')
446
+ part_buffer += more
447
+ actual_length += len(more)
448
+ if at_start:
449
+ if not part_buffer.startswith(delimiter_line):
450
+ raise ValueError('Missing initial multipart boundary')
451
+ part_buffer = part_buffer[len(delimiter_line):]
452
+ at_start = False
453
+ continue
454
+ idx = part_buffer.find(b'\r\n' + delimiter)
455
+ if idx == -1:
456
+ more = await read_more()
457
+ if actual_length + len(more) > content_length:
458
+ raise ValueError('Invalid content-length')
459
+ part_buffer += more
460
+ actual_length += len(more)
461
+ continue
462
+ # [2] Complete part received
463
+ part = part_buffer[:idx]
464
+ part_buffer = part_buffer[idx + 2:] # Consume leading CRLF
465
+ if part_buffer.startswith(close_delimiter):
466
+ # Last part found, stop processing delimiters
467
+ if part:
468
+ part_headers, part_body = parse_part(part)
469
+ dtype, data = callback(part_headers, part_body, first=first_part, last=True)
470
+ break
471
+ if not part_buffer.startswith(delimiter_line):
472
+ raise ValueError('Invalid multipart boundary formatting')
473
+ part_buffer = part_buffer[len(delimiter_line):]
474
+ if part:
475
+ # Process complete part
476
+ part_headers, part_body = parse_part(part)
477
+ dtype, data = callback(part_headers, part_body, first=first_part)
478
+ first_part = False
479
+ # [3] Verify content length
480
+ if actual_length < content_length:
481
+ more = await read_more()
482
+ if actual_length + len(more) != content_length:
483
+ raise ValueError('Invalid content-length')
484
+ # Ignore remaining content
485
+ return dtype, data
@@ -0,0 +1,132 @@
1
+ """
2
+ MICROPYTHON REPL micrOS Cli over UART
3
+ - reset() - reboot board
4
+ - shell() - start async micrOS shell (same as ShellCli)
5
+ """
6
+
7
+ from time import sleep
8
+ from uos import listdir
9
+ from machine import soft_reset, reset as hard_reset
10
+
11
+ try:
12
+ from Shell import Shell
13
+ from Tasks import Manager
14
+ from Common import micro_task
15
+ # --- nonblocking stdin line reader (USB serial REPL) ---
16
+ import sys, uselect
17
+ _poll = uselect.poll()
18
+ _poll.register(sys.stdin, uselect.POLLIN)
19
+ _buf = []
20
+ except ImportError as e:
21
+ print(f"!!!Cannot import shell: {e}")
22
+ Shell = None
23
+
24
+ #############################################
25
+ # REBOOT DEVICE #
26
+ #############################################
27
+
28
+ def reset():
29
+ """
30
+ [HELPER] Reboot board
31
+ """
32
+ print('Device reboot now, boot micrOSloader...')
33
+ sleep(1)
34
+
35
+ if "main.py" in listdir():
36
+ soft_reset()
37
+ else:
38
+ hard_reset()
39
+
40
+ #############################################
41
+ # SHELL in REPL #
42
+ #############################################
43
+
44
+ def _read_line_nb(echo=True):
45
+ """
46
+ Non-blocking:
47
+ - returns a full line (str, without trailing newline) when '\n' received
48
+ - returns None if no complete line yet
49
+ Echoes typed characters and supports backspace locally.
50
+ """
51
+ while _poll.poll(0): # check without waiting
52
+ ch = sys.stdin.read(1)
53
+ if not ch:
54
+ break
55
+
56
+ if ch == '\n': # end-of-line (LF)
57
+ line = ''.join(_buf)
58
+ _buf.clear()
59
+ return line # NOTE: we do NOT print a newline
60
+ if ch == '\r': # ignore CR
61
+ continue
62
+
63
+ # backspace / delete
64
+ if ch in ('\x08', '\x7f'):
65
+ if _buf and echo:
66
+ _buf.pop()
67
+ sys.stdout.write('\x08 \x08') # erase last char visually
68
+ elif _buf:
69
+ _buf.pop()
70
+ continue
71
+
72
+ # regular char
73
+ _buf.append(ch)
74
+ if echo:
75
+ sys.stdout.write(ch)
76
+ return None
77
+
78
+
79
+ async def _shell_task(task_id):
80
+
81
+ class ReplShell(Shell):
82
+ async def a_send(self, msg):
83
+ print(f"\n{msg}", end='')
84
+
85
+ shell_inst = ReplShell()
86
+ with micro_task(task_id) as my_task:
87
+ # Init shell welcome msg in repl mode
88
+ await shell_inst.shell("help")
89
+ # Run shell interpreter
90
+ while True:
91
+ my_task.out = "ShellCli in REPL"
92
+ _msg = _read_line_nb() # NoN Blocking ...
93
+ #_msg = input() # Blocking input handling
94
+ if _msg is not None:
95
+ state = await shell_inst.shell(_msg)
96
+ if not state or _msg.strip() == "exit":
97
+ print(f"\nEXIT SHELL: {_msg} ({state}) [Ctrl-C to return micropython repl]")
98
+ break
99
+ await my_task.feed(sleep_ms=10)
100
+ return f"Shell in REPL stopped...({state})"
101
+
102
+
103
+ def shell():
104
+ """
105
+ [HELPER] Run Shell in REPL
106
+ """
107
+ if Shell is None:
108
+ return "Cannot run Shell in REPL, import error"
109
+ # Prepare micrOS Task manager
110
+ aio = Manager()
111
+ # Initiate Shell as repl task
112
+ task_id = "repl.shell"
113
+ aio.create_task(callback=_shell_task(task_id), tag=task_id)
114
+ # Run async main event loop
115
+ aio.run_forever()
116
+ return "Async Main Event Loop Stopped"
117
+
118
+
119
+ if __name__ == "helpers":
120
+ # COMMAND LINE INTERFACE
121
+ print("\nmicrOS REPL Tools\n-----------------")
122
+ print("\t[0] helpers.reset() - reboot board")
123
+ if Shell: print("\t[1] helpers.shell() - Start Shell in REPL")
124
+ CHOICES = (reset, shell)
125
+ CHOICE = None
126
+ try:
127
+ CHOICE = input("Select option: ").strip()
128
+ CHOICE = int(CHOICE)
129
+ # EXEC COMMAND
130
+ CHOICES[CHOICE]()
131
+ except Exception as e:
132
+ print(f"Invalid input {CHOICE}: {e}")