micrOSDevToolKit 2.13.0__py3-none-any.whl → 2.17.0__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 (221) 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 +2 -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 +250 -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.0.dist-info → microsdevtoolkit-2.17.0.dist-info}/METADATA +30 -37
  41. {microsdevtoolkit-2.13.0.dist-info → microsdevtoolkit-2.17.0.dist-info}/RECORD +201 -191
  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/lib/pip_package_installer.py +5 -2
  51. toolkit/micrOSdashboard.py +2 -2
  52. toolkit/micrOSlint.py +17 -7
  53. toolkit/simulator_lib/__pycache__/simulator.cpython-312.pyc +0 -0
  54. toolkit/simulator_lib/__pycache__/uos.cpython-312.pyc +0 -0
  55. toolkit/simulator_lib/mqtt_as/Note.md +15 -0
  56. toolkit/simulator_lib/mqtt_as/__init__.py +950 -0
  57. toolkit/simulator_lib/mqtt_as/__pycache__/__init__.cpython-312.pyc +0 -0
  58. toolkit/simulator_lib/mqtt_as/clean.py +69 -0
  59. toolkit/simulator_lib/mqtt_as/mqtt_v5_properties.py +239 -0
  60. toolkit/simulator_lib/mqtt_as/range.py +90 -0
  61. toolkit/simulator_lib/mqtt_as/range_ex.py +119 -0
  62. toolkit/simulator_lib/simulator.py +14 -1
  63. toolkit/simulator_lib/uos.py +2 -0
  64. toolkit/workspace/precompiled/Common.mpy +0 -0
  65. toolkit/workspace/precompiled/Config.mpy +0 -0
  66. toolkit/workspace/precompiled/Espnow.mpy +0 -0
  67. toolkit/workspace/precompiled/Files.mpy +0 -0
  68. toolkit/workspace/precompiled/Hooks.mpy +0 -0
  69. toolkit/workspace/precompiled/Logger.mpy +0 -0
  70. toolkit/workspace/precompiled/Network.mpy +0 -0
  71. toolkit/workspace/precompiled/Server.mpy +0 -0
  72. toolkit/workspace/precompiled/Shell.mpy +0 -0
  73. toolkit/workspace/precompiled/Tasks.mpy +0 -0
  74. toolkit/workspace/precompiled/Web.mpy +0 -0
  75. toolkit/workspace/precompiled/config/_git.keep +0 -0
  76. toolkit/workspace/precompiled/micrOS.mpy +0 -0
  77. toolkit/workspace/precompiled/micrOSloader.mpy +0 -0
  78. toolkit/workspace/precompiled/microIO.mpy +0 -0
  79. toolkit/workspace/precompiled/{IO_esp32.mpy → modules/IO_esp32.mpy} +0 -0
  80. toolkit/workspace/precompiled/{IO_esp32c3.mpy → modules/IO_esp32c3.mpy} +0 -0
  81. toolkit/workspace/precompiled/modules/IO_esp32c6.mpy +0 -0
  82. toolkit/workspace/precompiled/{IO_esp32s2.mpy → modules/IO_esp32s2.mpy} +0 -0
  83. toolkit/workspace/precompiled/{IO_esp32s3.mpy → modules/IO_esp32s3.mpy} +0 -0
  84. toolkit/workspace/precompiled/{IO_m5stamp.mpy → modules/IO_m5stamp.mpy} +0 -0
  85. toolkit/workspace/precompiled/{IO_qtpy.mpy → modules/IO_qtpy.mpy} +0 -0
  86. toolkit/workspace/precompiled/modules/IO_rp2.mpy +0 -0
  87. toolkit/workspace/precompiled/modules/IO_s3matrix.mpy +0 -0
  88. toolkit/workspace/precompiled/{IO_tinypico.mpy → modules/IO_tinypico.mpy} +0 -0
  89. toolkit/workspace/precompiled/modules/LM_L298N.mpy +0 -0
  90. toolkit/workspace/precompiled/{LM_OV2640.mpy → modules/LM_OV2640.mpy} +0 -0
  91. toolkit/workspace/precompiled/{LM_aht10.mpy → modules/LM_aht10.mpy} +0 -0
  92. toolkit/workspace/precompiled/{LM_bme280.mpy → modules/LM_bme280.mpy} +0 -0
  93. toolkit/workspace/precompiled/{LM_buzzer.mpy → modules/LM_buzzer.mpy} +0 -0
  94. toolkit/workspace/precompiled/{LM_cct.mpy → modules/LM_cct.mpy} +0 -0
  95. toolkit/workspace/precompiled/modules/LM_cluster.mpy +0 -0
  96. toolkit/workspace/precompiled/{LM_co2.mpy → modules/LM_co2.mpy} +0 -0
  97. toolkit/workspace/precompiled/{LM_dht11.mpy → modules/LM_dht11.mpy} +0 -0
  98. toolkit/workspace/precompiled/{LM_dht22.mpy → modules/LM_dht22.mpy} +0 -0
  99. toolkit/workspace/precompiled/{LM_dimmer.mpy → modules/LM_dimmer.mpy} +0 -0
  100. toolkit/workspace/precompiled/{LM_distance.mpy → modules/LM_distance.mpy} +0 -0
  101. toolkit/workspace/precompiled/{LM_ds18.mpy → modules/LM_ds18.mpy} +0 -0
  102. {micrOS/source → toolkit/workspace/precompiled/modules}/LM_esp32.py +5 -0
  103. toolkit/workspace/precompiled/modules/LM_espnow.py +36 -0
  104. toolkit/workspace/precompiled/{LM_gameOfLife.mpy → modules/LM_gameOfLife.mpy} +0 -0
  105. toolkit/workspace/precompiled/{LM_genIO.mpy → modules/LM_genIO.mpy} +0 -0
  106. toolkit/workspace/precompiled/{LM_haptic.mpy → modules/LM_haptic.mpy} +0 -0
  107. toolkit/workspace/precompiled/{LM_i2c.py → modules/LM_i2c.py} +1 -1
  108. toolkit/workspace/precompiled/{LM_i2s_mic.mpy → modules/LM_i2s_mic.mpy} +0 -0
  109. toolkit/workspace/precompiled/{LM_keychain.mpy → modules/LM_keychain.mpy} +0 -0
  110. toolkit/workspace/precompiled/{LM_ld2410.mpy → modules/LM_ld2410.mpy} +0 -0
  111. toolkit/workspace/precompiled/modules/LM_light_sensor.mpy +0 -0
  112. toolkit/workspace/precompiled/modules/LM_mqtt_client.mpy +0 -0
  113. toolkit/workspace/precompiled/{LM_neoeffects.mpy → modules/LM_neoeffects.mpy} +0 -0
  114. toolkit/workspace/precompiled/modules/LM_neomatrix.mpy +0 -0
  115. toolkit/workspace/precompiled/{LM_neopixel.mpy → modules/LM_neopixel.mpy} +0 -0
  116. toolkit/workspace/precompiled/{LM_oled.mpy → modules/LM_oled.mpy} +0 -0
  117. toolkit/workspace/precompiled/{LM_oled_sh1106.mpy → modules/LM_oled_sh1106.mpy} +0 -0
  118. toolkit/workspace/precompiled/modules/LM_oled_ui.mpy +0 -0
  119. toolkit/workspace/precompiled/modules/LM_oledui.mpy +0 -0
  120. toolkit/workspace/precompiled/modules/LM_pacman.mpy +0 -0
  121. toolkit/workspace/precompiled/modules/LM_presence.mpy +0 -0
  122. toolkit/workspace/precompiled/{LM_rest.mpy → modules/LM_rest.mpy} +0 -0
  123. toolkit/workspace/precompiled/{LM_rgb.mpy → modules/LM_rgb.mpy} +0 -0
  124. toolkit/workspace/precompiled/{LM_rgbcct.mpy → modules/LM_rgbcct.mpy} +0 -0
  125. toolkit/workspace/precompiled/{LM_roboarm.mpy → modules/LM_roboarm.mpy} +0 -0
  126. toolkit/workspace/precompiled/{LM_robustness.py → modules/LM_robustness.py} +49 -2
  127. toolkit/workspace/precompiled/{LM_servo.mpy → modules/LM_servo.mpy} +0 -0
  128. toolkit/workspace/precompiled/{LM_sound_event.mpy → modules/LM_sound_event.mpy} +0 -0
  129. toolkit/workspace/precompiled/{LM_stepper.mpy → modules/LM_stepper.mpy} +0 -0
  130. toolkit/workspace/precompiled/{LM_switch.mpy → modules/LM_switch.mpy} +0 -0
  131. toolkit/workspace/precompiled/{LM_system.mpy → modules/LM_system.mpy} +0 -0
  132. toolkit/workspace/precompiled/{LM_tcs3472.py → modules/LM_tcs3472.py} +4 -6
  133. toolkit/workspace/precompiled/{LM_telegram.mpy → modules/LM_telegram.mpy} +0 -0
  134. toolkit/workspace/precompiled/{LM_tinyrgb.mpy → modules/LM_tinyrgb.mpy} +0 -0
  135. toolkit/workspace/precompiled/{LM_trackball.mpy → modules/LM_trackball.mpy} +0 -0
  136. toolkit/workspace/precompiled/{LM_veml7700.mpy → modules/LM_veml7700.mpy} +0 -0
  137. toolkit/workspace/precompiled/web/dashboard.html +2 -0
  138. toolkit/workspace/precompiled/web/matrix_draw.html +390 -0
  139. toolkit/workspace/precompiled/web/uapi.js +9 -6
  140. micrOS/source/IO_esp32c6.py +0 -16
  141. micrOS/source/LM_L298N_DCmotor.py +0 -86
  142. micrOS/source/LM_espnow.py +0 -57
  143. micrOS/source/LM_mqtt_pro.py +0 -211
  144. toolkit/lib/file_extensions.py +0 -22
  145. toolkit/workspace/precompiled/Common.cpython-312.pyc +0 -0
  146. toolkit/workspace/precompiled/IO_esp32c6.mpy +0 -0
  147. toolkit/workspace/precompiled/IO_rp2.mpy +0 -0
  148. toolkit/workspace/precompiled/IO_s3matrix.mpy +0 -0
  149. toolkit/workspace/precompiled/LM_L298N_DCmotor.mpy +0 -0
  150. toolkit/workspace/precompiled/LM_espnow.py +0 -57
  151. toolkit/workspace/precompiled/LM_light_sensor.mpy +0 -0
  152. toolkit/workspace/precompiled/LM_mqtt_pro.py +0 -211
  153. toolkit/workspace/precompiled/LM_neomatrix.mpy +0 -0
  154. toolkit/workspace/precompiled/LM_oled_ui.mpy +0 -0
  155. toolkit/workspace/precompiled/LM_oledui.mpy +0 -0
  156. toolkit/workspace/precompiled/LM_pacman.mpy +0 -0
  157. toolkit/workspace/precompiled/LM_presence.mpy +0 -0
  158. toolkit/workspace/precompiled/Logger.cpython-312.pyc +0 -0
  159. toolkit/workspace/precompiled/Server.cpython-312.pyc +0 -0
  160. /micrOS/source/{IO_esp32.py → modules/IO_esp32.py} +0 -0
  161. /micrOS/source/{IO_esp32c3.py → modules/IO_esp32c3.py} +0 -0
  162. /micrOS/source/{IO_esp32s2.py → modules/IO_esp32s2.py} +0 -0
  163. /micrOS/source/{IO_esp32s3.py → modules/IO_esp32s3.py} +0 -0
  164. /micrOS/source/{IO_m5stamp.py → modules/IO_m5stamp.py} +0 -0
  165. /micrOS/source/{IO_qtpy.py → modules/IO_qtpy.py} +0 -0
  166. /micrOS/source/{IO_rp2.py → modules/IO_rp2.py} +0 -0
  167. /micrOS/source/{IO_s3matrix.py → modules/IO_s3matrix.py} +0 -0
  168. /micrOS/source/{IO_tinypico.py → modules/IO_tinypico.py} +0 -0
  169. /micrOS/source/{LM_L9110_DCmotor.py → modules/LM_L9110_DCmotor.py} +0 -0
  170. /micrOS/source/{LM_OV2640.py → modules/LM_OV2640.py} +0 -0
  171. /micrOS/source/{LM_VL53L0X.py → modules/LM_VL53L0X.py} +0 -0
  172. /micrOS/source/{LM_aht10.py → modules/LM_aht10.py} +0 -0
  173. /micrOS/source/{LM_bme280.py → modules/LM_bme280.py} +0 -0
  174. /micrOS/source/{LM_buzzer.py → modules/LM_buzzer.py} +0 -0
  175. /micrOS/source/{LM_cct.py → modules/LM_cct.py} +0 -0
  176. /micrOS/source/{LM_co2.py → modules/LM_co2.py} +0 -0
  177. /micrOS/source/{LM_dashboard_be.py → modules/LM_dashboard_be.py} +0 -0
  178. /micrOS/source/{LM_dht11.py → modules/LM_dht11.py} +0 -0
  179. /micrOS/source/{LM_dht22.py → modules/LM_dht22.py} +0 -0
  180. /micrOS/source/{LM_dimmer.py → modules/LM_dimmer.py} +0 -0
  181. /micrOS/source/{LM_distance.py → modules/LM_distance.py} +0 -0
  182. /micrOS/source/{LM_ds18.py → modules/LM_ds18.py} +0 -0
  183. /micrOS/source/{LM_gameOfLife.py → modules/LM_gameOfLife.py} +0 -0
  184. /micrOS/source/{LM_genIO.py → modules/LM_genIO.py} +0 -0
  185. /micrOS/source/{LM_haptic.py → modules/LM_haptic.py} +0 -0
  186. /micrOS/source/{LM_i2s_mic.py → modules/LM_i2s_mic.py} +0 -0
  187. /micrOS/source/{LM_keychain.py → modules/LM_keychain.py} +0 -0
  188. /micrOS/source/{LM_ld2410.py → modules/LM_ld2410.py} +0 -0
  189. /micrOS/source/{LM_neopixel.py → modules/LM_neopixel.py} +0 -0
  190. /micrOS/source/{LM_oled.py → modules/LM_oled.py} +0 -0
  191. /micrOS/source/{LM_oled_sh1106.py → modules/LM_oled_sh1106.py} +0 -0
  192. /micrOS/source/{LM_pet_feeder.py → modules/LM_pet_feeder.py} +0 -0
  193. /micrOS/source/{LM_qmi8658.py → modules/LM_qmi8658.py} +0 -0
  194. /micrOS/source/{LM_rencoder.py → modules/LM_rencoder.py} +0 -0
  195. /micrOS/source/{LM_rest.py → modules/LM_rest.py} +0 -0
  196. /micrOS/source/{LM_rgb.py → modules/LM_rgb.py} +0 -0
  197. /micrOS/source/{LM_rgbcct.py → modules/LM_rgbcct.py} +0 -0
  198. /micrOS/source/{LM_roboarm.py → modules/LM_roboarm.py} +0 -0
  199. /micrOS/source/{LM_rp2w.py → modules/LM_rp2w.py} +0 -0
  200. /micrOS/source/{LM_sdcard.py → modules/LM_sdcard.py} +0 -0
  201. /micrOS/source/{LM_servo.py → modules/LM_servo.py} +0 -0
  202. /micrOS/source/{LM_sound_event.py → modules/LM_sound_event.py} +0 -0
  203. /micrOS/source/{LM_stepper.py → modules/LM_stepper.py} +0 -0
  204. /micrOS/source/{LM_switch.py → modules/LM_switch.py} +0 -0
  205. /micrOS/source/{LM_system.py → modules/LM_system.py} +0 -0
  206. /micrOS/source/{LM_telegram.py → modules/LM_telegram.py} +0 -0
  207. /micrOS/source/{LM_tinyrgb.py → modules/LM_tinyrgb.py} +0 -0
  208. /micrOS/source/{LM_trackball.py → modules/LM_trackball.py} +0 -0
  209. /micrOS/source/{LM_veml7700.py → modules/LM_veml7700.py} +0 -0
  210. {microsdevtoolkit-2.13.0.data → microsdevtoolkit-2.17.0.data}/scripts/devToolKit.py +0 -0
  211. {microsdevtoolkit-2.13.0.dist-info → microsdevtoolkit-2.17.0.dist-info}/WHEEL +0 -0
  212. {microsdevtoolkit-2.13.0.dist-info → microsdevtoolkit-2.17.0.dist-info}/licenses/LICENSE +0 -0
  213. {microsdevtoolkit-2.13.0.dist-info → microsdevtoolkit-2.17.0.dist-info}/top_level.txt +0 -0
  214. /toolkit/workspace/precompiled/{LM_L9110_DCmotor.py → modules/LM_L9110_DCmotor.py} +0 -0
  215. /toolkit/workspace/precompiled/{LM_VL53L0X.py → modules/LM_VL53L0X.py} +0 -0
  216. /toolkit/workspace/precompiled/{LM_dashboard_be.py → modules/LM_dashboard_be.py} +0 -0
  217. /toolkit/workspace/precompiled/{LM_pet_feeder.py → modules/LM_pet_feeder.py} +0 -0
  218. /toolkit/workspace/precompiled/{LM_qmi8658.py → modules/LM_qmi8658.py} +0 -0
  219. /toolkit/workspace/precompiled/{LM_rencoder.py → modules/LM_rencoder.py} +0 -0
  220. /toolkit/workspace/precompiled/{LM_rp2w.py → modules/LM_rp2w.py} +0 -0
  221. /toolkit/workspace/precompiled/{LM_sdcard.py → modules/LM_sdcard.py} +0 -0
micrOS/source/Espnow.py CHANGED
@@ -1,25 +1,24 @@
1
1
  from aioespnow import AIOESPNow
2
2
  from binascii import hexlify
3
- from Tasks import NativeTask, TaskBase, lm_exec, lm_is_loaded
3
+ from json import load, dump
4
4
  import uasyncio as asyncio
5
- from Network import get_mac
5
+ from Tasks import NativeTask, lm_exec, lm_is_loaded
6
6
  from Config import cfgget
7
7
  from Debug import syslog
8
+ from Files import OSPath, path_join, is_file
8
9
 
9
10
 
10
11
  # ----------- PARSE AND RENDER MSG PROTOCOL --------------
11
12
 
12
- def render_response(tid, oper, data, prompt) -> str:
13
+ def render_response(tid:str, oper:str, data:str, prompt:str) -> str:
13
14
  """
14
15
  Render ESPNow custom message (protocol)
15
16
  """
16
17
  if oper not in ("REQ", "RSP"):
17
18
  syslog(f"[ERR] espnow render_response, unknown oper: {oper}")
18
19
  tmp = "{tid}|{oper}|{data}|{prompt}$"
19
- tmp = tmp.replace("{tid}", str(tid))
20
- tmp = tmp.replace("{oper}", str(oper))
21
- tmp = tmp.replace("{data}", str(data))
22
- tmp = tmp.replace("{prompt}", str(prompt))
20
+ tmp = (tmp.replace("{tid}", tid).replace("{oper}", oper)
21
+ .replace("{data}", str(data)).replace("{prompt}", prompt))
23
22
  return tmp
24
23
 
25
24
  def parse_request(msg:bytes) -> (bool, dict|str):
@@ -85,6 +84,7 @@ class ESPNowSS:
85
84
  _instance = None
86
85
 
87
86
  def __new__(cls, *args, **kwargs):
87
+ # SINGLETON PATTERN
88
88
  if cls._instance is None:
89
89
  # first time: actually create it
90
90
  cls._instance = super().__new__(cls)
@@ -93,12 +93,27 @@ class ESPNowSS:
93
93
  def __init__(self):
94
94
  # __init__ still runs each time, so guard if needed
95
95
  if not hasattr(self, '_initialized'):
96
+ self._initialized = True
96
97
  self.espnow = AIOESPNow() # Instance with async support
97
98
  self.espnow.active(True)
98
99
  self.devfid = cfgget('devfid')
99
100
  self.devices: dict[bytes, str] = {} # mapping for { "mac address": "devfid" } pairs
100
- self._initialized = True
101
101
  self.server_ready = False
102
+ self.peer_cache = path_join(OSPath.DATA, "espnow_peers.app_json")
103
+ self._load_peers()
104
+
105
+ def _load_peers(self):
106
+ if not is_file(self.peer_cache):
107
+ return
108
+ try:
109
+ with open(self.peer_cache, 'r') as f:
110
+ devices_map = load(f)
111
+ self.devices = {bytes(k): v for k, v in devices_map}
112
+ for mac in self.devices:
113
+ # PEER REGISTRATION
114
+ self.espnow.add_peer(mac)
115
+ except Exception as e:
116
+ syslog(f"[ERR][ESPNOW] Loading peers: {e}")
102
117
 
103
118
  # ----------- SERVER METHODS --------------
104
119
  def _request_handler(self, msg:bytes, my_task:NativeTask, mac:bytes):
@@ -126,6 +141,11 @@ class ESPNowSS:
126
141
  if operation == "REQ":
127
142
  command = request["data"].split()
128
143
  module = command[0]
144
+ # Handle default hello - handshake message
145
+ if len(command) == 1 and module == "hello":
146
+ rendered_out = render_response(tid="?", oper="RSP", data=f"hello {prompt}", prompt=self.devfid)
147
+ return True, rendered_out
148
+ # COMMAND EXECUTION
129
149
  if lm_is_loaded(module):
130
150
  try:
131
151
  state, out = lm_exec(command)
@@ -134,39 +154,43 @@ class ESPNowSS:
134
154
  return state, rendered_out
135
155
  except Exception as e:
136
156
  # Optionally log the exception here.
137
- state, out = False, f"[ERR][_ESPNOW] {command}: {e}"
157
+ syslog(f"[ERR][_ESPNOW] {command}: {e}")
158
+ state, out = False, ""
138
159
  else:
139
- state, out = False, f"[WARN][_ESPNOW] NotAllowed {module}"
160
+ warning_msg = f"[WARN][_ESPNOW] NotAllowed {module}"
161
+ syslog(warning_msg)
162
+ rendered_out = render_response(tid="?", oper="RSP", data=warning_msg,
163
+ prompt=self.devfid)
164
+ state, out = True, rendered_out
140
165
  return state, out
141
166
  if operation == "RSP":
142
167
  resp_data = request["data"]
143
168
  ResponseRouter.update_response(mac, resp_data) # USE <tid> for proper session response mapping
144
- return False, f"[_ESPNOW] No action, {request}"
169
+ #syslog(f"[_ESPNOW] No action, {request}")
170
+ return False, ""
145
171
 
146
172
  async def _server(self, tag:str):
147
173
  """
148
174
  ESPnow async listener task
149
175
  :param tag: micro_task tag for task access
150
176
  """
151
- with TaskBase.TASKS.get(tag, None) as my_task:
177
+
178
+ with NativeTask.TASKS.get(tag, None) as my_task:
152
179
  self.server_ready = True
153
180
  my_task.out = "ESPNow receiver running"
154
181
  async for mac, msg in self.espnow:
155
182
  try:
156
- state, response = self._request_handler(msg, my_task, mac)
157
- if state:
183
+ reply, response = self._request_handler(msg, my_task, mac)
184
+ if reply:
158
185
  await self.__asend_raw(mac, response)
159
- else:
160
- syslog(response)
161
186
  except OSError as err:
162
187
  # If the peer is not yet added, add it and retry.
163
188
  if len(err.args) > 1 and err.args[1] == 'ESP_ERR_ESPNOW_NOT_FOUND':
164
- self.add_peer(mac)
165
- state, response = self._request_handler(msg, my_task, mac)
166
- if state:
189
+ # AUTOMATIC PEER REGISTRATION
190
+ self.espnow.add_peer(mac)
191
+ reply, response = self._request_handler(msg, my_task, mac)
192
+ if reply:
167
193
  await self.__asend_raw(mac, response)
168
- else:
169
- syslog(response)
170
194
  else:
171
195
  # Optionally handle or log other OSErrors here.
172
196
  syslog(f"[ERR][NOW SERVER] {err}")
@@ -178,7 +202,7 @@ class ESPNowSS:
178
202
  # Create an asynchronous task with tag 'espnow.server'
179
203
  tag = 'espnow.server'
180
204
  state = NativeTask().create(callback=self._server(tag), tag=tag)
181
- return "Starting" if state else "Already running"
205
+ return {tag: "Starting"} if state else {tag: "Already running"}
182
206
 
183
207
  #----------- SEND METHODS --------------
184
208
  async def __asend_raw(self, mac:bytes, msg:str):
@@ -191,7 +215,7 @@ class ESPNowSS:
191
215
  """
192
216
  ESPNow client task: send a command to a peer and update task status.
193
217
  """
194
- with TaskBase.TASKS.get(tag, None) as my_task:
218
+ with NativeTask.TASKS.get(tag, None) as my_task:
195
219
  router = ResponseRouter(peer)
196
220
  # rendered_output: "{tid}|{oper}|{data}|{prompt}$"
197
221
  rendered_out = render_response(tid="?", oper="REQ", data=msg, prompt=self.devfid)
@@ -207,7 +231,7 @@ class ESPNowSS:
207
231
  peer = matches[0] if matches else None
208
232
  return peer
209
233
 
210
- def send(self, peer:bytes|str, msg:str) -> str:
234
+ def send(self, peer:bytes|str, msg:str) -> dict:
211
235
  """
212
236
  Send a command over ESPNow.
213
237
  :param peer: Binary MAC address of another device.
@@ -218,7 +242,7 @@ class ESPNowSS:
218
242
  # Peer as device name (prompt)
219
243
  _peer = self.mac_by_peer_name(peer)
220
244
  if _peer is None:
221
- return f"Unknown device: {peer}"
245
+ return {peer: "Unknown device"}
222
246
  peer_name = peer
223
247
  peer = _peer
224
248
 
@@ -226,24 +250,62 @@ class ESPNowSS:
226
250
  task_id = f"con.espnow.{peer_name}"
227
251
  # Create an asynchronous sending task.
228
252
  state = NativeTask().create(callback=self._asend_task(peer, task_id, msg), tag=task_id)
229
- return "Starting" if state else "Already running"
253
+ return {task_id: "Starting"} if state else {task_id: "Already running"}
254
+
255
+ def cluster_send(self, msg):
256
+ """
257
+ Send message for all peers
258
+ """
259
+ _tasks = []
260
+ for peer_name in self.devices.values():
261
+ _tasks.append(self.send(peer_name, msg))
262
+ return _tasks
230
263
 
231
264
  # ----------- OTHER METHODS --------------
232
265
 
233
- def add_peer(self, peer:bytes, devfid:str=None):
266
+ async def _handshake(self, peer:bytes, tag:str):
234
267
  """
235
- Add a peer given its MAC address.
236
- :param peer: Binary MAC address of a peer (e.g. b'\xbb\xbb\xbb\xbb\xbb\xbb').
237
- :param devfid: optional parameter to register dev uid for mac address
268
+ Handshake with peer
269
+ - with device caching
238
270
  """
239
- try:
240
- self.espnow.add_peer(peer)
241
- if devfid is not None:
242
- # Update known devices
243
- self.devices[peer] = devfid
244
- except Exception as e:
245
- return f"Peer error: {e}"
246
- return "Peer register done"
271
+ with NativeTask.TASKS.get(tag, None) as my_task:
272
+ if self.devices.get(peer) is None:
273
+ my_task.out = "ESPNow Add Peer"
274
+ try:
275
+ # PEER REGISTRATION
276
+ self.espnow.add_peer(peer)
277
+ except Exception as e:
278
+ my_task.out = f"ESPNow Peer Error: {e}"
279
+ return
280
+ my_task.out = "Handshake In Progress..."
281
+ sender = self.send(peer, "hello")
282
+ task_key = list(sender.keys())[0]
283
+ sender_task = NativeTask.TASKS.get(task_key, None)
284
+ result = await sender_task.await_result(timeout=10)
285
+ expected_response = f"hello {self.devfid}"
286
+ is_ok = False
287
+ if result == expected_response:
288
+ try:
289
+ with open(self.peer_cache, "w") as f:
290
+ dump([[list(k), v] for k, v in self.devices.items()], f)
291
+ is_ok = True
292
+ except Exception as e:
293
+ syslog(f"[ERR][ESPNOW] Saving peers: {e}")
294
+ my_task.out = f"Handshake: {result} from {self.devices.get(peer)} [{'OK' if is_ok else 'NOK'}]"
295
+ sender_task.cancel() # Delete sender task (cleanup)
296
+
297
+
298
+ def handshake(self, peer:bytes|str):
299
+ task_id = f"con.espnow.handshake"
300
+ # Create an asynchronous sending task.
301
+ if isinstance(peer, str) and ":" in peer:
302
+ # Convert 50:02:91:86:34:28 format to b'P\x02\x91\x864(' bytes
303
+ peer = bytes(int(x, 16) for x in peer.split(":"))
304
+ if isinstance(peer, bytes):
305
+ state = NativeTask().create(callback=self._handshake(peer, task_id), tag=task_id)
306
+ return {task_id: "Starting"} if state else {task_id: "Already running"}
307
+ else:
308
+ return {None: "Invalid MAC address format. Use 50:02:91:86:34:28 or b'P\\x02\\x91\\x864('"}
247
309
 
248
310
  def stats(self):
249
311
  """
@@ -260,23 +322,3 @@ class ESPNowSS:
260
322
  except Exception as e:
261
323
  _peers = str(e)
262
324
  return {"stats": _stats, "peers": _peers, "map": self.devices, "ready": self.server_ready}
263
-
264
-
265
- ###################################################
266
- # Control functions #
267
- ###################################################
268
- INSTANCE = ESPNowSS()
269
-
270
- def initialize():
271
- # TODO: remove, use ESPNowSS() class instead
272
- global INSTANCE
273
- if INSTANCE is None:
274
- INSTANCE = ESPNowSS()
275
- return INSTANCE
276
-
277
-
278
- def mac_address():
279
- """
280
- Get the binary MAC address.
281
- """
282
- return get_mac()
micrOS/source/Files.py CHANGED
@@ -1,26 +1,43 @@
1
- from uos import ilistdir, remove, stat, getcwd
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
7
+ from sys import path as upath
2
8
 
3
9
  ################################ Helper functions #####################################
4
10
 
5
- def _is_module(path:str='/', pyprefix:str='*') -> bool:
11
+ def _filter(path:str='/', ext:tuple=None, prefix:tuple=None, hide_core:bool=True) -> bool:
6
12
  """
7
- Filter application modules, LM_.* (pyprefix) or app data or web resource
13
+ Filter files
8
14
  :param path: file to check
9
- :param pyprefix: python resource filter prefix, default: * (all: LM and IO)
15
+ :param ext: tuple of extensions to filter by, default: None (all)
16
+ :param hide_core: hide core files (mpy, py), default: True
10
17
  """
11
- # micrOS file types
12
- allowed_exts = ('html', 'js', 'css', 'log', 'cache', 'dat')
13
- mod_prefixes = ('LM', "IO")
14
18
  fname = path.split("/")[-1]
15
- if fname.split("_")[0] in mod_prefixes or fname.split('.')[-1] in allowed_exts:
16
- if pyprefix == '*':
17
- # MODE: ALL app resources
18
- return True
19
- if fname.startswith(f"{pyprefix.upper()}_"):
20
- # MODE: SELECTED app resources
21
- return True
19
+ _ext = fname.split(".")[-1]
20
+ if hide_core and _ext in ("mpy", "py") and not (fname.startswith("LM_") or fname.startswith("IO_")):
21
+ return False
22
+ if ext is None and prefix is None:
23
+ return True
24
+ if isinstance(prefix, tuple) and fname.split("_")[0] in prefix:
25
+ return True
26
+ if isinstance(ext, tuple) and fname.split(".")[-1] in ext:
27
+ return True
22
28
  return False
23
29
 
30
+ def is_protected(path:str='/') -> bool:
31
+ """
32
+ Check is file protected
33
+ - deny deletion
34
+ """
35
+ protected_entities = ("", "node_config.json", "modules", "config", "logs", "web", "data",
36
+ "LM_pacman.mpy", "LM_system.mpy")
37
+ entity = path.split("/")[-1].replace("/", "")
38
+ if entity in protected_entities:
39
+ return True
40
+ return False
24
41
 
25
42
  def _type_mask_to_str(item_type:int=None) -> str:
26
43
  # Map the raw bit-mask to a single character
@@ -33,6 +50,19 @@ def _type_mask_to_str(item_type:int=None) -> str:
33
50
  return item_type
34
51
 
35
52
  ########################### Public functions #############################
53
+ def is_dir(path):
54
+ try:
55
+ return stat(path)[0] & 0x4000
56
+ except OSError:
57
+ return False
58
+
59
+
60
+ def is_file(path):
61
+ try:
62
+ return stat(path)[0] & 0x8000
63
+ except OSError:
64
+ return False
65
+
36
66
 
37
67
  def ilist_fs(path:str="/", type_filter:str='*', select:str='*', core:bool=False):
38
68
  """
@@ -52,7 +82,8 @@ def ilist_fs(path:str="/", type_filter:str='*', select:str='*', core:bool=False)
52
82
  item_type = _type_mask_to_str(item_type)
53
83
  if type_filter in ("*", item_type):
54
84
  # Mods only
55
- if not core and item_type == 'f' and not _is_module(path+item, pyprefix=select):
85
+ _select = None if select == "*" else (select,)
86
+ if item_type == 'f' and not _filter(item, prefix=_select, hide_core=not core):
56
87
  continue
57
88
  if select != '*' and item_type == 'd':
58
89
  continue
@@ -78,33 +109,19 @@ def remove_fs(path, allow_dir=False):
78
109
  :param allow_dir: enable directory deletion, default: False
79
110
  """
80
111
  # protect some resources
81
- if 'pacman.mpy' in path or 'system.mpy' in path or "/" == path.strip():
82
- return f'Load module {path} is protected, skip deletion'
83
- _is_dir = is_dir(path)
84
- if _is_module(path) or (_is_dir and allow_dir):
112
+ if is_protected(path):
113
+ return f'{path} is protected, skip deletion'
114
+ _is_file = is_file(path)
115
+ if _is_file or allow_dir:
85
116
  remove(path)
86
- return f"Removed: {path} {'dir' if _is_dir else 'file'}"
87
- return f"Protected path: {path}"
88
-
89
-
90
- def is_dir(path):
91
- try:
92
- return stat(path)[0] & 0x4000
93
- except OSError:
94
- return False
95
-
96
-
97
- def is_file(path):
98
- try:
99
- return stat(path)[0] & 0x8000
100
- except OSError:
101
- return False
117
+ return f"{path} deleted"
118
+ return f"Cannot delete {'file' if _is_file else 'dir'}: {path}"
102
119
 
103
120
 
104
121
  def path_join(*parts):
105
122
  path = "/".join(part.strip("/") for part in parts if part)
106
123
  if parts and parts[0].startswith("/"):
107
- path = "/" + path
124
+ path = path if path.startswith("/") else "/" + path
108
125
  return path
109
126
 
110
127
 
@@ -114,9 +131,28 @@ class OSPath:
114
131
  LOGS = path_join(_ROOT, '/logs') # Logs (.log)
115
132
  DATA = path_join(_ROOT,'/data') # Application data (.dat, .cache, etc.)
116
133
  WEB = path_join(_ROOT,'/web') # Web resources (.html, .css, .js, .json, etc.)
117
- MODULES = path_join(_ROOT, '/modules') # Application modules (.mpy, .py) (todo)
118
- CONFIG = path_join(_ROOT, '/config') # System configuration files (node_config.json, etc.)(todo)
134
+ MODULES = path_join(_ROOT, '/modules') # Application modules (.mpy, .py)
135
+ CONFIG = path_join(_ROOT, '/config') # System configuration files (node_config.json, etc.)
136
+
119
137
 
120
- @property
121
- def ROOT(self):
122
- return self._ROOT
138
+ def init_micros_dirs():
139
+ """
140
+ Init micrOS root file system directories
141
+ """
142
+ # ENABLE MODULES ACCESS
143
+ if OSPath.MODULES not in upath:
144
+ upath.insert(0, OSPath.MODULES)
145
+
146
+ root_dirs = [
147
+ getattr(OSPath, key)
148
+ for key in dir(OSPath)
149
+ if not key.startswith("_") and isinstance(getattr(OSPath, key), str)
150
+ ]
151
+ print(f"[BOOT] rootFS validation: {root_dirs}")
152
+ for dir_path in root_dirs:
153
+ if not is_dir(dir_path):
154
+ try:
155
+ mkdir(dir_path)
156
+ print(f"[BOOT] init dir: {dir_path}")
157
+ except Exception as e:
158
+ print(f"[ERR][BOOT] cannot init dir {dir_path}: {e}")
micrOS/source/Hooks.py CHANGED
@@ -21,8 +21,6 @@ from Config import cfgget, cfgput
21
21
  from microIO import detect_platform
22
22
  from Debug import console_write, syslog
23
23
  from Tasks import exec_lm_pipe
24
- from Files import OSPath, is_dir
25
- from uos import mkdir
26
24
  from micropython import mem_info
27
25
  from machine import freq
28
26
  try:
@@ -45,7 +43,6 @@ def bootup():
45
43
  """
46
44
  # Execute LMs from boothook config parameter
47
45
  console_write("[BOOT] EXECUTION...")
48
- _init_micros_dirs()
49
46
  bootasks = cfgget('boothook')
50
47
  if bootasks is not None and bootasks.lower() != 'n/a':
51
48
  console_write(f"|-[BOOT] TASKS: {bootasks}")
@@ -62,25 +59,6 @@ def bootup():
62
59
  _tune_performance()
63
60
 
64
61
 
65
- def _init_micros_dirs():
66
- """
67
- Init micrOS root file system directories
68
- """
69
- root_dirs = [
70
- getattr(OSPath, key)
71
- for key in dir(OSPath)
72
- if not key.startswith("_") and isinstance(getattr(OSPath, key), str)
73
- ]
74
- console_write(f"|-[BOOT] rootFS validation: {root_dirs}")
75
- for dir_path in root_dirs:
76
- if not is_dir(dir_path):
77
- try:
78
- mkdir(dir_path)
79
- syslog(f"[BOOT] init dir: {dir_path}")
80
- except Exception as e:
81
- syslog(f"[ERR][BOOT] cannot init dir {dir_path}: {e}")
82
-
83
-
84
62
  def _tune_queue_size():
85
63
  """
86
64
  Tune queue size based on available ram
@@ -96,20 +74,26 @@ def _tune_queue_size():
96
74
 
97
75
 
98
76
  def _tune_performance():
99
- # Set boosted (boost mode)
77
+ # {(platforms, ...): (min_clock, max_clock), ...}
78
+ cpu_clocks = {
79
+ ('esp32c3', 'esp32c6'): (80_000_000, 160_000_000),
80
+ ('esp32',): (160_000_000, 240_000_000), # default
81
+ }
100
82
  platform = detect_platform()
101
- if cfgget('boostmd') is True:
102
- console_write(f"[BOOT HOOKS] Set up CPU high Hz - boostmd: {cfgget('boostmd')}")
103
- if platform == 'esp32c3':
104
- freq(160_000_000) # 160 Mhz (max)
105
- elif 'esp32' in platform:
106
- freq(240_000_000) # 240 Mhz (max)
83
+ cpu_min_max = cpu_clocks[('esp32',)]
84
+ for platforms, clocks in cpu_clocks.items():
85
+ if platform in platforms:
86
+ cpu_min_max = clocks
87
+ break
88
+ # Set boosted (boost mode)
89
+ if cfgget('boostmd'):
90
+ max_hz = cpu_min_max[1]
91
+ console_write(f"[BOOT HOOKS] CPU boost mode ON: {max_hz} Hz")
92
+ freq(max_hz)
107
93
  else:
108
- console_write(f"[BOOT HOOKS] Set up CPU low Hz - boostmd: {cfgget('boostmd')}")
109
- if platform == 'esp32c3':
110
- freq(80_000_000) # 80 Mhz / Half the max CPU clock
111
- elif 'esp32' in platform:
112
- freq(160_000_000) # 160 Mhz / Half the max CPU clock
94
+ min_hz = cpu_min_max[0]
95
+ console_write(f"[BOOT HOOKS] CPU boost mode OFF: {min_hz} Hz")
96
+ freq(min_hz)
113
97
 
114
98
 
115
99
  def profiling_info(label=""):
micrOS/source/Logger.py CHANGED
@@ -6,7 +6,7 @@ Designed by Marcell Ban aka BxNxM
6
6
  """
7
7
  from time import localtime
8
8
  from re import match
9
- from uos import remove, mkdir
9
+ from uos import remove
10
10
  from Files import OSPath, path_join, ilist_fs, is_dir
11
11
 
12
12
  #############################################
@@ -16,12 +16,7 @@ from Files import OSPath, path_join, ilist_fs, is_dir
16
16
  def _init_logger():
17
17
  """ Init /logs folder """
18
18
  if not is_dir(OSPath.LOGS):
19
- try:
20
- mkdir(OSPath.LOGS)
21
- syslog(f"[BOOT] log dir {OSPath.LOGS} init")
22
- except Exception as e:
23
- OSPath.LOGS = OSPath.ROOT
24
- syslog(f"[BOOT] log dir {OSPath.LOGS} fallback: {e}")
19
+ OSPath.LOGS = OSPath._ROOT
25
20
  return OSPath.LOGS
26
21
 
27
22