micrOSDevToolKit 2.13.1__py3-none-any.whl → 2.17.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.

Potentially problematic release.


This version of micrOSDevToolKit might be problematic. Click here for more details.

Files changed (220) hide show
  1. micrOS/release_info/micrOS_ReleaseInfo/system_analysis_sum.json +125 -121
  2. micrOS/source/Common.py +48 -26
  3. micrOS/source/Config.py +13 -5
  4. micrOS/source/Espnow.py +100 -58
  5. micrOS/source/Files.py +77 -41
  6. micrOS/source/Hooks.py +18 -34
  7. micrOS/source/Logger.py +2 -7
  8. micrOS/source/Network.py +36 -16
  9. micrOS/source/Server.py +22 -8
  10. micrOS/source/Shell.py +9 -6
  11. micrOS/source/Tasks.py +34 -13
  12. micrOS/source/Web.py +69 -31
  13. micrOS/source/__pycache__/Common.cpython-312.pyc +0 -0
  14. micrOS/source/__pycache__/Files.cpython-312.pyc +0 -0
  15. micrOS/source/__pycache__/Logger.cpython-312.pyc +0 -0
  16. micrOS/source/__pycache__/Server.cpython-312.pyc +0 -0
  17. micrOS/source/config/_git.keep +0 -0
  18. micrOS/source/micrOS.py +7 -0
  19. micrOS/source/micrOSloader.py +4 -10
  20. micrOS/source/microIO.py +2 -2
  21. micrOS/source/modules/IO_esp32c6.py +38 -0
  22. micrOS/source/modules/LM_L298N.py +161 -0
  23. micrOS/source/modules/LM_cluster.py +255 -0
  24. {toolkit/workspace/precompiled → micrOS/source/modules}/LM_esp32.py +5 -0
  25. micrOS/source/modules/LM_espnow.py +36 -0
  26. micrOS/source/{LM_i2c.py → modules/LM_i2c.py} +1 -1
  27. micrOS/source/{LM_light_sensor.py → modules/LM_light_sensor.py} +2 -2
  28. micrOS/source/modules/LM_mqtt_client.py +246 -0
  29. micrOS/source/{LM_neoeffects.py → modules/LM_neoeffects.py} +14 -4
  30. micrOS/source/{LM_neomatrix.py → modules/LM_neomatrix.py} +140 -38
  31. micrOS/source/{LM_oled_ui.py → modules/LM_oled_ui.py} +2 -2
  32. micrOS/source/{LM_oledui.py → modules/LM_oledui.py} +2 -2
  33. micrOS/source/{LM_pacman.py → modules/LM_pacman.py} +74 -29
  34. micrOS/source/{LM_presence.py → modules/LM_presence.py} +2 -2
  35. micrOS/source/{LM_robustness.py → modules/LM_robustness.py} +49 -2
  36. micrOS/source/{LM_tcs3472.py → modules/LM_tcs3472.py} +4 -6
  37. micrOS/source/web/dashboard.html +2 -0
  38. micrOS/source/web/matrix_draw.html +390 -0
  39. micrOS/source/web/uapi.js +9 -6
  40. {microsdevtoolkit-2.13.1.dist-info → microsdevtoolkit-2.17.1.dist-info}/METADATA +30 -37
  41. {microsdevtoolkit-2.13.1.dist-info → microsdevtoolkit-2.17.1.dist-info}/RECORD +200 -190
  42. toolkit/DevEnvCompile.py +21 -12
  43. toolkit/DevEnvOTA.py +27 -16
  44. toolkit/DevEnvUSB.py +35 -21
  45. toolkit/LM_to_compile.dat +3 -1
  46. toolkit/MicrOSDevEnv.py +37 -21
  47. toolkit/dashboard_apps/QMI8685_GYRO.py +1 -1
  48. toolkit/dashboard_apps/SystemTest.py +8 -5
  49. toolkit/{MicrosFiles.py → lib/MicrosFiles.py} +24 -4
  50. toolkit/micrOSdashboard.py +2 -2
  51. toolkit/micrOSlint.py +17 -7
  52. toolkit/simulator_lib/__pycache__/simulator.cpython-312.pyc +0 -0
  53. toolkit/simulator_lib/__pycache__/uos.cpython-312.pyc +0 -0
  54. toolkit/simulator_lib/mqtt_as/Note.md +15 -0
  55. toolkit/simulator_lib/mqtt_as/__init__.py +950 -0
  56. toolkit/simulator_lib/mqtt_as/__pycache__/__init__.cpython-312.pyc +0 -0
  57. toolkit/simulator_lib/mqtt_as/clean.py +69 -0
  58. toolkit/simulator_lib/mqtt_as/mqtt_v5_properties.py +239 -0
  59. toolkit/simulator_lib/mqtt_as/range.py +90 -0
  60. toolkit/simulator_lib/mqtt_as/range_ex.py +119 -0
  61. toolkit/simulator_lib/simulator.py +14 -1
  62. toolkit/simulator_lib/uos.py +2 -0
  63. toolkit/workspace/precompiled/Common.mpy +0 -0
  64. toolkit/workspace/precompiled/Config.mpy +0 -0
  65. toolkit/workspace/precompiled/Espnow.mpy +0 -0
  66. toolkit/workspace/precompiled/Files.mpy +0 -0
  67. toolkit/workspace/precompiled/Hooks.mpy +0 -0
  68. toolkit/workspace/precompiled/Logger.mpy +0 -0
  69. toolkit/workspace/precompiled/Network.mpy +0 -0
  70. toolkit/workspace/precompiled/Server.mpy +0 -0
  71. toolkit/workspace/precompiled/Shell.mpy +0 -0
  72. toolkit/workspace/precompiled/Tasks.mpy +0 -0
  73. toolkit/workspace/precompiled/Web.mpy +0 -0
  74. toolkit/workspace/precompiled/config/_git.keep +0 -0
  75. toolkit/workspace/precompiled/micrOS.mpy +0 -0
  76. toolkit/workspace/precompiled/micrOSloader.mpy +0 -0
  77. toolkit/workspace/precompiled/microIO.mpy +0 -0
  78. toolkit/workspace/precompiled/{IO_esp32.mpy → modules/IO_esp32.mpy} +0 -0
  79. toolkit/workspace/precompiled/{IO_esp32c3.mpy → modules/IO_esp32c3.mpy} +0 -0
  80. toolkit/workspace/precompiled/modules/IO_esp32c6.mpy +0 -0
  81. toolkit/workspace/precompiled/{IO_esp32s2.mpy → modules/IO_esp32s2.mpy} +0 -0
  82. toolkit/workspace/precompiled/{IO_esp32s3.mpy → modules/IO_esp32s3.mpy} +0 -0
  83. toolkit/workspace/precompiled/{IO_m5stamp.mpy → modules/IO_m5stamp.mpy} +0 -0
  84. toolkit/workspace/precompiled/{IO_qtpy.mpy → modules/IO_qtpy.mpy} +0 -0
  85. toolkit/workspace/precompiled/modules/IO_rp2.mpy +0 -0
  86. toolkit/workspace/precompiled/modules/IO_s3matrix.mpy +0 -0
  87. toolkit/workspace/precompiled/{IO_tinypico.mpy → modules/IO_tinypico.mpy} +0 -0
  88. toolkit/workspace/precompiled/modules/LM_L298N.mpy +0 -0
  89. toolkit/workspace/precompiled/{LM_OV2640.mpy → modules/LM_OV2640.mpy} +0 -0
  90. toolkit/workspace/precompiled/{LM_aht10.mpy → modules/LM_aht10.mpy} +0 -0
  91. toolkit/workspace/precompiled/{LM_bme280.mpy → modules/LM_bme280.mpy} +0 -0
  92. toolkit/workspace/precompiled/{LM_buzzer.mpy → modules/LM_buzzer.mpy} +0 -0
  93. toolkit/workspace/precompiled/{LM_cct.mpy → modules/LM_cct.mpy} +0 -0
  94. toolkit/workspace/precompiled/modules/LM_cluster.mpy +0 -0
  95. toolkit/workspace/precompiled/{LM_co2.mpy → modules/LM_co2.mpy} +0 -0
  96. toolkit/workspace/precompiled/{LM_dht11.mpy → modules/LM_dht11.mpy} +0 -0
  97. toolkit/workspace/precompiled/{LM_dht22.mpy → modules/LM_dht22.mpy} +0 -0
  98. toolkit/workspace/precompiled/{LM_dimmer.mpy → modules/LM_dimmer.mpy} +0 -0
  99. toolkit/workspace/precompiled/{LM_distance.mpy → modules/LM_distance.mpy} +0 -0
  100. toolkit/workspace/precompiled/{LM_ds18.mpy → modules/LM_ds18.mpy} +0 -0
  101. {micrOS/source → toolkit/workspace/precompiled/modules}/LM_esp32.py +5 -0
  102. toolkit/workspace/precompiled/modules/LM_espnow.py +36 -0
  103. toolkit/workspace/precompiled/{LM_gameOfLife.mpy → modules/LM_gameOfLife.mpy} +0 -0
  104. toolkit/workspace/precompiled/{LM_genIO.mpy → modules/LM_genIO.mpy} +0 -0
  105. toolkit/workspace/precompiled/{LM_haptic.mpy → modules/LM_haptic.mpy} +0 -0
  106. toolkit/workspace/precompiled/{LM_i2c.py → modules/LM_i2c.py} +1 -1
  107. toolkit/workspace/precompiled/{LM_i2s_mic.mpy → modules/LM_i2s_mic.mpy} +0 -0
  108. toolkit/workspace/precompiled/{LM_keychain.mpy → modules/LM_keychain.mpy} +0 -0
  109. toolkit/workspace/precompiled/{LM_ld2410.mpy → modules/LM_ld2410.mpy} +0 -0
  110. toolkit/workspace/precompiled/modules/LM_light_sensor.mpy +0 -0
  111. toolkit/workspace/precompiled/modules/LM_mqtt_client.mpy +0 -0
  112. toolkit/workspace/precompiled/{LM_neoeffects.mpy → modules/LM_neoeffects.mpy} +0 -0
  113. toolkit/workspace/precompiled/modules/LM_neomatrix.mpy +0 -0
  114. toolkit/workspace/precompiled/{LM_neopixel.mpy → modules/LM_neopixel.mpy} +0 -0
  115. toolkit/workspace/precompiled/{LM_oled.mpy → modules/LM_oled.mpy} +0 -0
  116. toolkit/workspace/precompiled/{LM_oled_sh1106.mpy → modules/LM_oled_sh1106.mpy} +0 -0
  117. toolkit/workspace/precompiled/modules/LM_oled_ui.mpy +0 -0
  118. toolkit/workspace/precompiled/modules/LM_oledui.mpy +0 -0
  119. toolkit/workspace/precompiled/modules/LM_pacman.mpy +0 -0
  120. toolkit/workspace/precompiled/modules/LM_presence.mpy +0 -0
  121. toolkit/workspace/precompiled/{LM_rest.mpy → modules/LM_rest.mpy} +0 -0
  122. toolkit/workspace/precompiled/{LM_rgb.mpy → modules/LM_rgb.mpy} +0 -0
  123. toolkit/workspace/precompiled/{LM_rgbcct.mpy → modules/LM_rgbcct.mpy} +0 -0
  124. toolkit/workspace/precompiled/{LM_roboarm.mpy → modules/LM_roboarm.mpy} +0 -0
  125. toolkit/workspace/precompiled/{LM_robustness.py → modules/LM_robustness.py} +49 -2
  126. toolkit/workspace/precompiled/{LM_servo.mpy → modules/LM_servo.mpy} +0 -0
  127. toolkit/workspace/precompiled/{LM_sound_event.mpy → modules/LM_sound_event.mpy} +0 -0
  128. toolkit/workspace/precompiled/{LM_stepper.mpy → modules/LM_stepper.mpy} +0 -0
  129. toolkit/workspace/precompiled/{LM_switch.mpy → modules/LM_switch.mpy} +0 -0
  130. toolkit/workspace/precompiled/{LM_system.mpy → modules/LM_system.mpy} +0 -0
  131. toolkit/workspace/precompiled/{LM_tcs3472.py → modules/LM_tcs3472.py} +4 -6
  132. toolkit/workspace/precompiled/{LM_telegram.mpy → modules/LM_telegram.mpy} +0 -0
  133. toolkit/workspace/precompiled/{LM_tinyrgb.mpy → modules/LM_tinyrgb.mpy} +0 -0
  134. toolkit/workspace/precompiled/{LM_trackball.mpy → modules/LM_trackball.mpy} +0 -0
  135. toolkit/workspace/precompiled/{LM_veml7700.mpy → modules/LM_veml7700.mpy} +0 -0
  136. toolkit/workspace/precompiled/web/dashboard.html +2 -0
  137. toolkit/workspace/precompiled/web/matrix_draw.html +390 -0
  138. toolkit/workspace/precompiled/web/uapi.js +9 -6
  139. micrOS/source/IO_esp32c6.py +0 -16
  140. micrOS/source/LM_L298N_DCmotor.py +0 -86
  141. micrOS/source/LM_espnow.py +0 -57
  142. micrOS/source/LM_mqtt_pro.py +0 -211
  143. toolkit/lib/file_extensions.py +0 -22
  144. toolkit/workspace/precompiled/Common.cpython-312.pyc +0 -0
  145. toolkit/workspace/precompiled/IO_esp32c6.mpy +0 -0
  146. toolkit/workspace/precompiled/IO_rp2.mpy +0 -0
  147. toolkit/workspace/precompiled/IO_s3matrix.mpy +0 -0
  148. toolkit/workspace/precompiled/LM_L298N_DCmotor.mpy +0 -0
  149. toolkit/workspace/precompiled/LM_espnow.py +0 -57
  150. toolkit/workspace/precompiled/LM_light_sensor.mpy +0 -0
  151. toolkit/workspace/precompiled/LM_mqtt_pro.py +0 -211
  152. toolkit/workspace/precompiled/LM_neomatrix.mpy +0 -0
  153. toolkit/workspace/precompiled/LM_oled_ui.mpy +0 -0
  154. toolkit/workspace/precompiled/LM_oledui.mpy +0 -0
  155. toolkit/workspace/precompiled/LM_pacman.mpy +0 -0
  156. toolkit/workspace/precompiled/LM_presence.mpy +0 -0
  157. toolkit/workspace/precompiled/Logger.cpython-312.pyc +0 -0
  158. toolkit/workspace/precompiled/Server.cpython-312.pyc +0 -0
  159. /micrOS/source/{IO_esp32.py → modules/IO_esp32.py} +0 -0
  160. /micrOS/source/{IO_esp32c3.py → modules/IO_esp32c3.py} +0 -0
  161. /micrOS/source/{IO_esp32s2.py → modules/IO_esp32s2.py} +0 -0
  162. /micrOS/source/{IO_esp32s3.py → modules/IO_esp32s3.py} +0 -0
  163. /micrOS/source/{IO_m5stamp.py → modules/IO_m5stamp.py} +0 -0
  164. /micrOS/source/{IO_qtpy.py → modules/IO_qtpy.py} +0 -0
  165. /micrOS/source/{IO_rp2.py → modules/IO_rp2.py} +0 -0
  166. /micrOS/source/{IO_s3matrix.py → modules/IO_s3matrix.py} +0 -0
  167. /micrOS/source/{IO_tinypico.py → modules/IO_tinypico.py} +0 -0
  168. /micrOS/source/{LM_L9110_DCmotor.py → modules/LM_L9110_DCmotor.py} +0 -0
  169. /micrOS/source/{LM_OV2640.py → modules/LM_OV2640.py} +0 -0
  170. /micrOS/source/{LM_VL53L0X.py → modules/LM_VL53L0X.py} +0 -0
  171. /micrOS/source/{LM_aht10.py → modules/LM_aht10.py} +0 -0
  172. /micrOS/source/{LM_bme280.py → modules/LM_bme280.py} +0 -0
  173. /micrOS/source/{LM_buzzer.py → modules/LM_buzzer.py} +0 -0
  174. /micrOS/source/{LM_cct.py → modules/LM_cct.py} +0 -0
  175. /micrOS/source/{LM_co2.py → modules/LM_co2.py} +0 -0
  176. /micrOS/source/{LM_dashboard_be.py → modules/LM_dashboard_be.py} +0 -0
  177. /micrOS/source/{LM_dht11.py → modules/LM_dht11.py} +0 -0
  178. /micrOS/source/{LM_dht22.py → modules/LM_dht22.py} +0 -0
  179. /micrOS/source/{LM_dimmer.py → modules/LM_dimmer.py} +0 -0
  180. /micrOS/source/{LM_distance.py → modules/LM_distance.py} +0 -0
  181. /micrOS/source/{LM_ds18.py → modules/LM_ds18.py} +0 -0
  182. /micrOS/source/{LM_gameOfLife.py → modules/LM_gameOfLife.py} +0 -0
  183. /micrOS/source/{LM_genIO.py → modules/LM_genIO.py} +0 -0
  184. /micrOS/source/{LM_haptic.py → modules/LM_haptic.py} +0 -0
  185. /micrOS/source/{LM_i2s_mic.py → modules/LM_i2s_mic.py} +0 -0
  186. /micrOS/source/{LM_keychain.py → modules/LM_keychain.py} +0 -0
  187. /micrOS/source/{LM_ld2410.py → modules/LM_ld2410.py} +0 -0
  188. /micrOS/source/{LM_neopixel.py → modules/LM_neopixel.py} +0 -0
  189. /micrOS/source/{LM_oled.py → modules/LM_oled.py} +0 -0
  190. /micrOS/source/{LM_oled_sh1106.py → modules/LM_oled_sh1106.py} +0 -0
  191. /micrOS/source/{LM_pet_feeder.py → modules/LM_pet_feeder.py} +0 -0
  192. /micrOS/source/{LM_qmi8658.py → modules/LM_qmi8658.py} +0 -0
  193. /micrOS/source/{LM_rencoder.py → modules/LM_rencoder.py} +0 -0
  194. /micrOS/source/{LM_rest.py → modules/LM_rest.py} +0 -0
  195. /micrOS/source/{LM_rgb.py → modules/LM_rgb.py} +0 -0
  196. /micrOS/source/{LM_rgbcct.py → modules/LM_rgbcct.py} +0 -0
  197. /micrOS/source/{LM_roboarm.py → modules/LM_roboarm.py} +0 -0
  198. /micrOS/source/{LM_rp2w.py → modules/LM_rp2w.py} +0 -0
  199. /micrOS/source/{LM_sdcard.py → modules/LM_sdcard.py} +0 -0
  200. /micrOS/source/{LM_servo.py → modules/LM_servo.py} +0 -0
  201. /micrOS/source/{LM_sound_event.py → modules/LM_sound_event.py} +0 -0
  202. /micrOS/source/{LM_stepper.py → modules/LM_stepper.py} +0 -0
  203. /micrOS/source/{LM_switch.py → modules/LM_switch.py} +0 -0
  204. /micrOS/source/{LM_system.py → modules/LM_system.py} +0 -0
  205. /micrOS/source/{LM_telegram.py → modules/LM_telegram.py} +0 -0
  206. /micrOS/source/{LM_tinyrgb.py → modules/LM_tinyrgb.py} +0 -0
  207. /micrOS/source/{LM_trackball.py → modules/LM_trackball.py} +0 -0
  208. /micrOS/source/{LM_veml7700.py → modules/LM_veml7700.py} +0 -0
  209. {microsdevtoolkit-2.13.1.data → microsdevtoolkit-2.17.1.data}/scripts/devToolKit.py +0 -0
  210. {microsdevtoolkit-2.13.1.dist-info → microsdevtoolkit-2.17.1.dist-info}/WHEEL +0 -0
  211. {microsdevtoolkit-2.13.1.dist-info → microsdevtoolkit-2.17.1.dist-info}/licenses/LICENSE +0 -0
  212. {microsdevtoolkit-2.13.1.dist-info → microsdevtoolkit-2.17.1.dist-info}/top_level.txt +0 -0
  213. /toolkit/workspace/precompiled/{LM_L9110_DCmotor.py → modules/LM_L9110_DCmotor.py} +0 -0
  214. /toolkit/workspace/precompiled/{LM_VL53L0X.py → modules/LM_VL53L0X.py} +0 -0
  215. /toolkit/workspace/precompiled/{LM_dashboard_be.py → modules/LM_dashboard_be.py} +0 -0
  216. /toolkit/workspace/precompiled/{LM_pet_feeder.py → modules/LM_pet_feeder.py} +0 -0
  217. /toolkit/workspace/precompiled/{LM_qmi8658.py → modules/LM_qmi8658.py} +0 -0
  218. /toolkit/workspace/precompiled/{LM_rencoder.py → modules/LM_rencoder.py} +0 -0
  219. /toolkit/workspace/precompiled/{LM_rp2w.py → modules/LM_rp2w.py} +0 -0
  220. /toolkit/workspace/precompiled/{LM_sdcard.py → modules/LM_sdcard.py} +0 -0
@@ -0,0 +1,246 @@
1
+ import json
2
+ import time
3
+ from mqtt_as import MQTTClient, config
4
+ from Config import cfgget
5
+ from Common import micro_task, console, syslog, exec_cmd
6
+
7
+
8
+ class MQTT:
9
+ CLIENT:MQTTClient = None # MQTT Client (broker) instance
10
+ QOS: int = None
11
+ DEBUG: bool = True
12
+ DEFAULT_TOPIC: str = f"{cfgget('devfid')}/#"
13
+
14
+ # Micro Task TGS
15
+ SUB_TASK = 'mqtt.subscribe'
16
+ UNSUB_TASK = 'mqtt.unsubscribe'
17
+ CLIENT_TASK = 'mqtt.client'
18
+ UP_TASK = 'mqtt.up'
19
+
20
+
21
+ ######################
22
+ # CORE MQTT HANDLERS #
23
+ ######################
24
+
25
+
26
+ async def _receiver():
27
+ """
28
+ Asynchronous loop that listens for incoming MQTT messages from the subscribed topics.
29
+ - Decodes topic and message.
30
+ - Validates JSON payload, runs commands via exec_cmd, and publishes a JSON-formatted response.
31
+ """
32
+ async for topic, msg, retained in MQTT.CLIENT.queue:
33
+ incoming_topic, msg = topic.decode(), msg.decode()
34
+ console(f'Topic: "{incoming_topic}" Message: "{msg}" Retained: {retained}')
35
+
36
+ topic_parts = incoming_topic.split('/')
37
+ if len(topic_parts) == 3:
38
+ payload = {}
39
+ if msg.strip():
40
+ try:
41
+ payload = json.loads(msg)
42
+ except ValueError:
43
+ _publish_error(incoming_topic, f"Invalid payload JSON on topic {incoming_topic}: {msg}")
44
+ continue
45
+
46
+ args = [f'{k}="{v}"' if isinstance(v, str) else f'{k}={v}' for k, v in payload.items()]
47
+
48
+ cmd_parts = topic_parts[1:] + args
49
+
50
+ state, output_json = exec_cmd(cmd=cmd_parts, jsonify=True)
51
+ try:
52
+ output = json.loads(output_json)
53
+ except ValueError:
54
+ output = output_json
55
+
56
+ resp_payload = json.dumps({"state": state, "result": output})
57
+ publish(topic=f"{incoming_topic}/response", message=resp_payload)
58
+
59
+
60
+ def _publish_error(topic: str, error_msg: str):
61
+ """
62
+ Publish an error message to the <topic>/response MQTT topic and log it to the console.
63
+ :param topic: The original MQTT topic where the error occurred.
64
+ :param error_msg: The error message string.
65
+ """
66
+ publish(topic=f"{topic}/response", message=error_msg)
67
+ console(error_msg)
68
+
69
+
70
+ #############################
71
+ # SUBSCRIPTION \ PUBLISHING #
72
+ #############################
73
+
74
+
75
+ async def _unsubscribe(topic: str):
76
+ """
77
+ Unsubscribe from the specified MQTT topic.
78
+ :param topic: Topic string to unsubscribe from.
79
+ """
80
+ with micro_task(tag=MQTT.UNSUB_TASK) as my_task:
81
+ my_task.out = "Started"
82
+ try:
83
+ console(f"Unsubscribe topic: {topic}")
84
+ await MQTT.CLIENT.unsubscribe(topic)
85
+ my_task.out = "Done"
86
+ except Exception as e:
87
+ my_task.out = f"Error: {e}"
88
+
89
+
90
+ async def _subscribe(topic: str):
91
+ """
92
+ Subscribe to the specified MQTT topic with the global QoS.
93
+ :param topic: Topic string to subscribe to.
94
+ """
95
+ with micro_task(tag=MQTT.SUB_TASK) as my_task:
96
+ my_task.out = "Started"
97
+ try:
98
+ console(f"Subscribe topic: {topic}")
99
+ await MQTT.CLIENT.subscribe(topic, qos=MQTT.QOS)
100
+ my_task.out = "Done"
101
+ except Exception as e:
102
+ my_task.out = f"Error: {e}"
103
+
104
+
105
+ async def _publish(tag, message: str, topic: str, retain: bool):
106
+ """
107
+ Asynchronously publish a message to the given MQTT topic.
108
+ :param message: The message payload as a string.
109
+ :param topic: The MQTT topic string.
110
+ :param retain: Whether the message should be retained on the broker.
111
+ """
112
+ with micro_task(tag) as my_task:
113
+ await MQTT.CLIENT.publish(topic, message, qos=MQTT.QOS, retain=retain)
114
+
115
+
116
+ def publish(topic: str, message: str, retain: bool = False):
117
+ """
118
+ Wrapper to publish a message to the specified topic.
119
+ Creates a micro_task for asynchronous publishing.
120
+ :param topic: MQTT topic string.
121
+ :param message: Message string.
122
+ :param retain: Whether to retain the message on the broker (default False).
123
+ :return: Status message string.
124
+ """
125
+ unique_tag = f'mqtt.publish.{topic}.{time.ticks_ms()}'
126
+ state = micro_task(tag=unique_tag, task=_publish(unique_tag, message, topic, retain))
127
+ return f"Message was sent {state}"
128
+
129
+
130
+ ####################
131
+ # CLIENT LIFECYCLE #
132
+ ####################
133
+
134
+
135
+ async def _up():
136
+ """
137
+ UP Listener task that waits for an MQTT 'up' event (reconnection) and re-subscribes to the default topic.
138
+ """
139
+ with micro_task(tag=MQTT.UP_TASK) as my_task:
140
+ while True:
141
+ # Wait for UP Event - (re)subscribe
142
+ my_task.out = "Wait"
143
+ await MQTT.CLIENT.up.wait()
144
+ MQTT.CLIENT.up.clear()
145
+ state = micro_task(tag=MQTT.SUB_TASK, task=_subscribe(MQTT.DEFAULT_TOPIC))
146
+ my_task.out = f"Re-Subscription {state}"
147
+ my_task.feed()
148
+
149
+
150
+ async def _init_client():
151
+ """
152
+ Initialize and connect the MQTT client, subscribe to default topics,
153
+ start up-listener, and begin receiving messages.
154
+ """
155
+ with micro_task(tag=MQTT.CLIENT_TASK) as my_task:
156
+ try:
157
+ await MQTT.CLIENT.connect()
158
+ my_task.out = "Connection successful."
159
+ except OSError:
160
+ my_task.out = "Connection failed."
161
+ return
162
+
163
+ # Wait for mqtt client connected successfully
164
+ await MQTT.CLIENT.up.wait()
165
+ MQTT.CLIENT.up.clear()
166
+
167
+ # Initialize mqtt topics
168
+ if not micro_task(tag=MQTT.SUB_TASK, task=_subscribe(MQTT.DEFAULT_TOPIC)):
169
+ syslog(f"Failed start mqtt subscribe")
170
+ if not micro_task(tag=MQTT.UP_TASK, task=_up()):
171
+ syslog(f"Failed start mqtt up")
172
+ # Async listener loop
173
+ await _receiver()
174
+ my_task.out = "Receiver closed"
175
+
176
+ # Close when listener exits
177
+ MQTT.CLIENT.close()
178
+
179
+
180
+ #################
181
+ # CONFIGURATION #
182
+ #################
183
+
184
+
185
+ def get_config():
186
+ """
187
+ Get configuration for MQTT client.
188
+ :return: MQTT configuration dict.
189
+ """
190
+ return config
191
+
192
+
193
+ def _configure(username: str, password: str, server_ip: str, server_port: str):
194
+ """
195
+ Build the MQTT client configuration.
196
+ :param username: Broker username.
197
+ :param password: Broker password.
198
+ :param server_ip: Broker IP or hostname.
199
+ :param server_port: Broker port.
200
+ :return: Updated configuration dictionary.
201
+ """
202
+ config.update({
203
+ 'client_id': cfgget("devfid"),
204
+ 'server': server_ip,
205
+ 'port': server_port,
206
+ 'user': username,
207
+ 'password': password,
208
+ 'ssid': cfgget("staessid"), # Only supports a single Wi-Fi connection. Multiple SSIDs not handled.
209
+ 'wifi_pw': cfgget("stapwd"),
210
+ 'keepalive': 120,
211
+ 'queue_len': 1, # Use event interface with default queue
212
+ 'will': (f'{cfgget("devfid")}/status', '{"status": "offline"}', True, 1)
213
+ })
214
+ return config
215
+
216
+
217
+ def load(username: str, password: str, server_ip: str, server_port: str='1883', qos: str=1):
218
+ """
219
+ Configure, initialize, and start the MQTT client.
220
+ Requires that the micropython-mqtt package is installed. You can install it with:
221
+ mpremote mip install github:peterhinch/micropython-mqtt
222
+ :param username: Broker username.
223
+ :param password: Broker password.
224
+ :param server_ip: Broker IP or hostname.
225
+ :param server_port: MQTT port (default 1883).
226
+ :param qos: MQTT Quality of Service level (0, 1, or 2). Controls delivery guarantee.
227
+ :return: Status dict showing whether the client is starting or already running.
228
+ """
229
+ MQTTClient.DEBUG = MQTT.DEBUG
230
+ MQTT.CLIENT = MQTTClient(_configure(username, password, server_ip, server_port))
231
+ MQTT.QOS = qos
232
+
233
+ state = micro_task(tag=MQTT.CLIENT_TASK, task=_init_client())
234
+ return {MQTT.CLIENT_TASK: "Starting"} if state else {MQTT.CLIENT_TASK: "Already running"}
235
+
236
+
237
+ def help(widgets=False):
238
+ """
239
+ Show public functions. Return a tuple of public function usage strings for the MQTT module.
240
+ :param widgets: Unused, reserved for extra help formatting.
241
+ :return: Tuple of help strings.
242
+ """
243
+ return ('load username:str password:str server_ip:str server_port:str="1883"',
244
+ 'get_config',
245
+ 'publish topic:str message:str retain=False')
246
+
@@ -100,7 +100,13 @@ def cycle(speed_ms:int=60, shift:bool=True, batch:bool=True):
100
100
  return load().play(effect_cycle, speed_ms=speed_ms, bt_draw=batch, bt_size=4)
101
101
 
102
102
 
103
- def rainbow(speed_ms=20, br=15, batch=True):
103
+ def rainbow(speed_ms=50, br=15, batch=True):
104
+ """
105
+ Rainbow effect animation
106
+ :param speed_ms: animation speed in milliseconds
107
+ :param br: Brightness of the pixels (0-100)
108
+ :param batch: Batch drawing of pixels (True/False)
109
+ """
104
110
  def _wheel(pos):
105
111
  # Input a value 0 to 255 to get a color value.
106
112
  # The colours are a transition r - g - b - back to r.
@@ -121,8 +127,7 @@ def rainbow(speed_ms=20, br=15, batch=True):
121
127
  :param step: step size
122
128
  """
123
129
  nonlocal pix_cnt, br
124
- color_step = 3
125
- for color_wheel in range(0, 255, color_step):
130
+ for color_wheel in range(0, 255):
126
131
  for index in range(0, pix_cnt):
127
132
  rc_index = (index * 256 // pix_cnt) + color_wheel
128
133
  r, g, b = _wheel(rc_index & 255)
@@ -157,6 +162,11 @@ def fire(speed_ms:int=150, br:int=30, batch:bool=True):
157
162
 
158
163
 
159
164
  def shader(offset=0, size=6):
165
+ """
166
+ Shader effect on neopixel ring.
167
+ :param offset: start pixel index
168
+ :param size: shader size (number of pixels)
169
+ """
160
170
  def effect_shader():
161
171
  nonlocal size, offset, neoeffect
162
172
  pix_cnt = Data.NEOPIXEL_OBJ.n
@@ -261,7 +271,7 @@ def help(widgets=False):
261
271
  'BUTTON meteor speed_ms=60',
262
272
  'cycle speed_ms=60 shift=True batch=True',
263
273
  'BUTTON cycle speed_ms=60',
264
- 'rainbow speed_ms=20 br=15 batch=True',
274
+ 'rainbow speed_ms=50 br=15 batch=True',
265
275
  'BUTTON rainbow',
266
276
  'fire speed_ms=150 br=30 batch=True',
267
277
  'BUTTON fire speed_ms=150',
@@ -1,10 +1,11 @@
1
+ from random import randint
1
2
  from neopixel import NeoPixel
2
3
  from machine import Pin
3
4
  from utime import sleep_ms
4
5
 
5
6
  from microIO import bind_pin
6
7
  from Types import resolve
7
- from Common import manage_task, AnimationPlayer
8
+ from Common import manage_task, AnimationPlayer, web_dir, syslog, web_endpoint
8
9
 
9
10
 
10
11
  class NeoPixelMatrix(AnimationPlayer):
@@ -18,7 +19,7 @@ class NeoPixelMatrix(AnimationPlayer):
18
19
  self.num_pixels = width * height
19
20
  self.pixels = NeoPixel(Pin(pin, Pin.OUT), self.num_pixels)
20
21
  self._color_buffer = [(0, 0, 0)] * self.num_pixels # Store original RGB values
21
- self._brightness = 0.25 # Brightness level, default 25%
22
+ self._brightness = 0.20 # Brightness level, default 20%
22
23
  NeoPixelMatrix.INSTANCE = self
23
24
 
24
25
  def update(self, x:int, y:int, color:tuple[int, int, int]):
@@ -34,6 +35,7 @@ class NeoPixelMatrix(AnimationPlayer):
34
35
  for i in range(self.num_pixels):
35
36
  # Write pixel buffer before write to ws2812
36
37
  self.pixels[i] = (0, 0, 0)
38
+ self._color_buffer[i] = (0, 0, 0)
37
39
  # Send buffer to device
38
40
  self.draw()
39
41
 
@@ -41,9 +43,11 @@ class NeoPixelMatrix(AnimationPlayer):
41
43
  """
42
44
  Zigzag layout: even rows left-to-right, odd rows right-to-left
43
45
  """
44
- if (zigzag is None or zigzag) and y % 2 == 0:
45
- return y * self.width + x
46
- return y * self.width + (self.width - 1 - x)
46
+ if zigzag is None or zigzag:
47
+ if y % 2 == 0:
48
+ return y * self.width + x
49
+ return y * self.width + (self.width - 1 - x)
50
+ return y * self.width + x
47
51
 
48
52
  def _index_to_coord(self, index: int, zigzag:bool=True) -> tuple[int, int]:
49
53
  """
@@ -140,9 +144,21 @@ def load(width=8, height=8):
140
144
  """
141
145
  if NeoPixelMatrix.INSTANCE is None:
142
146
  NeoPixelMatrix(width=width, height=height, pin=bind_pin('neop'))
147
+ web_endpoint('matrixDraw', _web_endpoint_clb, auto_enable=False)
143
148
  return NeoPixelMatrix.INSTANCE
144
149
 
145
150
 
151
+ def _web_endpoint_clb():
152
+ try:
153
+ with open(web_dir('matrix_draw.html'), 'r') as html:
154
+ html_content = html.read()
155
+ return 'text/html', html_content
156
+ except Exception as e:
157
+ syslog(f"[ERR] neomatrix web: {e}")
158
+ html_content = None
159
+ return 'text/plain', f'html_content error: {html_content}'
160
+
161
+
146
162
  def pixel(x, y, color=None, show=True):
147
163
  """
148
164
  Set pixel at (x,y) to RGB color.
@@ -205,6 +221,10 @@ def stop():
205
221
 
206
222
 
207
223
  def draw_colormap(bitmap):
224
+ """
225
+ Draw colors as a color map
226
+ [(x, y, (r, g,b)), ...]
227
+ """
208
228
  try:
209
229
  load().draw_colormap(bitmap)
210
230
  except Exception as e:
@@ -215,12 +235,22 @@ def draw_colormap(bitmap):
215
235
  def get_colormap():
216
236
  return load().export_colormap()
217
237
 
238
+
239
+ def status():
240
+ """
241
+ Get the current status of the matrix
242
+ """
243
+ r, g, b = NeoPixelMatrix.DEFAULT_COLOR
244
+ br = NeoPixelMatrix.INSTANCE._brightness
245
+ return {'r': r, 'g': g, 'b': b, 'br': int(br*100)}
246
+
247
+
218
248
  # -----------------------------------------------------------------------------
219
249
  # -----------------------------------------------------------------------------
220
250
 
221
251
  def rainbow(speed_ms=0):
222
- def effect_rainbow():
223
- def hsv_to_rgb(h, s, v):
252
+ def _effect_rainbow():
253
+ def _hsv_to_rgb(h, s, v):
224
254
  max_color = 150 #255
225
255
  h = float(h)
226
256
  s = float(s)
@@ -254,14 +284,14 @@ def rainbow(speed_ms=0):
254
284
  for x in range(width):
255
285
  index = y * width + x
256
286
  hue = ((index + frame) % 64) / 64.0
257
- r, g, b = hsv_to_rgb(hue, 1.0, 0.7)
287
+ r, g, b = _hsv_to_rgb(hue, 1.0, 0.7)
258
288
  yield x, y, (r, g, b)
259
289
 
260
- return load().play(effect_rainbow, speed_ms=speed_ms, bt_draw=True, bt_size=8)
290
+ return load().play(_effect_rainbow, speed_ms=speed_ms, bt_draw=True, bt_size=8)
261
291
 
262
292
 
263
- def snake(speed_ms:int=30, length:int=5):
264
- def effect_snake():
293
+ def snake(speed_ms:int=30, length:int=6):
294
+ def _effect_snake():
265
295
  clear_color = (0, 0, 0)
266
296
  total_pixels = 8 * 8
267
297
  total_steps = total_pixels + length # run just past the end to clear tail
@@ -283,40 +313,110 @@ def snake(speed_ms:int=30, length:int=5):
283
313
  color = (int(r * br), int(g * br), int(b * br))
284
314
  yield x, y, color
285
315
 
286
- return load().play(effect_snake, speed_ms=speed_ms, bt_draw=False)
316
+ return load().play(_effect_snake, speed_ms=speed_ms, bt_draw=False)
287
317
 
288
318
 
289
- def cube(speed_ms=10):
290
- def effect_cube(max_radius:int = 3):
319
+ def spiral(speed_ms=40):
320
+ def _effect_spiral(trail=12, hold=6):
291
321
  """
292
- Generator yielding (x, y, color) for a centered 2×2 square ("cube")
293
- that expands outward to `max_radius` then collapses back.
322
+ Center-out spiral with row-prewarp so the visual is continuous
323
+ even when set_pixel() applies zigzag=True internally.
294
324
  """
295
- width, height = 8, 8
296
- # Center the 2×2 core in an 8×8 grid
297
- cx, cy = width // 2 - 1, height // 2 - 1
298
-
299
- # Expansion phase: radius 0 (2×2) up to max_radius
300
- for r in range(0, max_radius + 1):
301
- for dx in range(-r, r + 2):
302
- for dy in range(-r, r + 2):
303
- x, y = cx + dx, cy + dy
304
- if 0 <= x < width and 0 <= y < height:
305
- yield x, y, NeoPixelMatrix.DEFAULT_COLOR
306
- # Clear matrix
325
+ try:
326
+ W = NeoPixelMatrix.INSTANCE.width
327
+ H = NeoPixelMatrix.INSTANCE.height
328
+ except:
329
+ W = H = 8
330
+
331
+ # --- build center-out spiral path in true matrix coords (x,y) ---
332
+ # exact center on odd sizes; upper-left of center 2x2 on even sizes
333
+ cx = (W // 2 - 1) if (W % 2 == 0) else (W // 2)
334
+ cy = (H // 2 - 1) if (H % 2 == 0) else (H // 2)
335
+
336
+ x, y = cx, cy
337
+ path, seen = [], set()
338
+
339
+ def _add(ax, ay):
340
+ if 0 <= ax < W and 0 <= ay < H and (ax, ay) not in seen:
341
+ seen.add((ax, ay))
342
+ path.append((ax, ay))
343
+
344
+ _add(x, y)
345
+ dirs = ((1, 0), (0, 1), (-1, 0), (0, -1)) # R, D, L, U
346
+ step_len, d = 1, 0
347
+ while len(path) < W * H:
348
+ for _ in range(2):
349
+ dx, dy = dirs[d & 3]
350
+ for _ in range(step_len):
351
+ x += dx; y += dy
352
+ _add(x, y)
353
+ if len(path) >= W * H: break
354
+ d += 1
355
+ if len(path) >= W * H: break
356
+ step_len += 1
357
+
358
+ # --- PREWARP ---
359
+ # Cancel the internal zigzag mapping: flip x on odd rows so
360
+ # set_pixel(zigzag=True) flips it back -> visually linear.
361
+ def _warp(ax, ay):
362
+ return (W - 1 - ax, ay) if (ay & 1) else (ax, ay)
363
+
364
+ off = (0, 0, 0)
365
+
366
+ def _shade(k):
367
+ r0, g0, b0 = NeoPixelMatrix.DEFAULT_COLOR
368
+ k = max(0.0, min(1.0, k)) ** 0.9
369
+ return int(r0 * k), int(g0 * k), int(b0 * k)
370
+
307
371
  try:
308
372
  NeoPixelMatrix.INSTANCE.clear()
309
373
  except:
310
374
  pass
311
- # Collapse phase: back down, skipping the largest to avoid duplicate
312
- for r in range(max_radius - 1, -1, -1):
313
- for dx in range(-r, r + 2):
314
- for dy in range(-r, r + 2):
315
- x, y = cx + dx, cy + dy
316
- if 0 <= x < width and 0 <= y < height:
317
- yield x, y, NeoPixelMatrix.DEFAULT_COLOR
318
375
 
319
- return load().play(effect_cube, speed_ms=speed_ms, bt_draw=False)
376
+ # expand with tail
377
+ for n in range(len(path)):
378
+ clear_at = n - trail - 1
379
+ if clear_at >= 0:
380
+ cx_, cy_ = _warp(*path[clear_at])
381
+ yield cx_, cy_, off
382
+
383
+ start = 0 if n < trail else (n - trail + 1)
384
+ span = max(1, n - start + 1)
385
+ for i in range(start, n + 1):
386
+ k = (i - start + 1) / span
387
+ px, py = _warp(*path[i])
388
+ yield px, py, _shade(k)
389
+
390
+ # brief hold
391
+ hx, hy = _warp(*path[-1])
392
+ for _ in range(hold):
393
+ yield hx, hy, _shade(1.0)
394
+
395
+ # shrink with fading tail
396
+ for n in range(len(path) - 1, -1, -1):
397
+ px, py = _warp(*path[n])
398
+ yield px, py, off
399
+ start = max(0, n - trail + 1)
400
+ span = max(1, n - start)
401
+ for i in range(start, n):
402
+ k = (i - start + 1) / span
403
+ qx, qy = _warp(*path[i])
404
+ yield qx, qy, _shade(k)
405
+
406
+ return load().play(_effect_spiral, speed_ms=speed_ms, bt_draw=True, bt_size=8)
407
+
408
+
409
+ def noise(speed_ms:int=85):
410
+ def _effect_noise():
411
+ total_steps = 8 * 8
412
+ for step in range(total_steps):
413
+ x, y = step % 8, step // 8
414
+ r, g, b = NeoPixelMatrix.DEFAULT_COLOR
415
+ br = float(randint(0, 100) * 0.01) # Generate random brightness
416
+ color = (int(r * br), int(g * br), int(b * br))
417
+ yield x, y, color
418
+
419
+ return load().play(_effect_noise, speed_ms=speed_ms, bt_draw=True, bt_size=4)
320
420
 
321
421
 
322
422
  def help(widgets=False):
@@ -328,8 +428,10 @@ def help(widgets=False):
328
428
  'BUTTON stop',
329
429
  'BUTTON snake speed_ms=50 length=5',
330
430
  'BUTTON rainbow',
331
- 'BUTTON cube speed_ms=10',
431
+ 'BUTTON spiral speed_ms=40',
432
+ 'BUTTON noise speed_ms=85',
332
433
  'SLIDER control speed_ms=<1-200-2> bt_draw=None',
333
434
  'draw_colormap bitmap=[(0,0,(10,2,0)),(x,y,color),...]',
334
- 'get_colormap'
435
+ 'get_colormap',
436
+ 'status'
335
437
  ), widgets=widgets)
@@ -341,7 +341,7 @@ class PageUI:
341
341
  self.open_intercons.append(host)
342
342
  try:
343
343
  # Send CMD to other device & show result
344
- state, data_meta = exec_cmd(cmd + [f">>{host}"], jsonify=True, skip_check=True)
344
+ state, data_meta = exec_cmd(cmd + [f">>{host}"], jsonify=True)
345
345
  if state:
346
346
  self.cmd_task_tag = data_meta['tag']
347
347
  if "Task is Busy" in data_meta['verdict'] and not run:
@@ -386,7 +386,7 @@ class PageUI:
386
386
  try:
387
387
  cmd_list = cmd.strip().split()
388
388
  # Send CMD to other device & show result
389
- state, out = exec_cmd(cmd_list, skip_check=True)
389
+ state, out = exec_cmd(cmd_list)
390
390
  try:
391
391
  self.cmd_out = ''.join(out.strip().split()).replace(' ', '')
392
392
  except Exception:
@@ -745,7 +745,7 @@ class PageUI:
745
745
  try:
746
746
  cmd_list = cmd.strip().split()
747
747
  # Send CMD to other device & show result
748
- state, out = exec_cmd(cmd_list, skip_check=True)
748
+ state, out = exec_cmd(cmd_list)
749
749
  cmd_out = out.strip()
750
750
  except Exception as e:
751
751
  cmd_out = str(e)
@@ -780,7 +780,7 @@ class PageUI:
780
780
  # Check open host connection
781
781
  try:
782
782
  # Send CMD to other device & show result
783
- state, data_meta = exec_cmd(cmd + [f">>{host}"], jsonify=True, skip_check=True)
783
+ state, data_meta = exec_cmd(cmd + [f">>{host}"], jsonify=True)
784
784
  if state:
785
785
  self._cmd_task_tag = data_meta['tag']
786
786
  if "Task is Busy" in data_meta['verdict'] and not run: