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
@@ -0,0 +1,161 @@
1
+ from machine import Pin, PWM
2
+ from microIO import bind_pin, pinmap_search
3
+
4
+
5
+ # Cache of last set motor speeds (motor1, motor2) to ensure state() reports the latest values
6
+ # even if hardware PWM duty readout is delayed or unsupported on some boards
7
+ __MOTOR_SPEEDS = [0, 0] # motor1, motor2
8
+ __L298N_OBJS = []
9
+
10
+ PWM_FREQ = 50
11
+ # Deprecated LM_L298N_DCmotor: l298speed, l298dir_1, l298dir_2
12
+ PIN_BINDINGS = [
13
+ ('l298n_ENA', 10), ('l298n_INA', 12), ('l298n_INB', 11), # motor 1
14
+ ('l298n_ENB', 3), ('l298n_INC', 9), ('l298n_IND', 40), # motor 2
15
+ ]
16
+ STATE_MAP = {
17
+ (1, 0): 'forward',
18
+ (0, 1): 'backward',
19
+ (0, 0): 'coast',
20
+ (1, 1): 'brake'
21
+ }
22
+
23
+
24
+ def __l298n_init():
25
+ global __L298N_OBJS
26
+ if not __L298N_OBJS:
27
+ for index, (name, pin) in enumerate(PIN_BINDINGS):
28
+ if index % 3 == 0: # PWM pin
29
+ pwm = PWM(bind_pin(name, pin), freq=PWM_FREQ)
30
+ pwm.duty(0)
31
+ __L298N_OBJS.append(pwm)
32
+ else:
33
+ p = Pin(bind_pin(name, pin), Pin.OUT)
34
+ p.value(0)
35
+ __L298N_OBJS.append(p)
36
+ return __L298N_OBJS
37
+
38
+
39
+ def __get_motor_state(motor_index):
40
+ objlist = __l298n_init()
41
+ pwm_index = motor_index * 3
42
+ in1, in2 = objlist[pwm_index + 1].value(), objlist[pwm_index + 2].value()
43
+ state = STATE_MAP.get((in1, in2), 'unknown')
44
+ return {'speed': __MOTOR_SPEEDS[motor_index], 'state': state}
45
+
46
+
47
+ #########################
48
+ # Application functions #
49
+ #########################
50
+
51
+ def load(pwm_freq:int=None):
52
+ """
53
+ [i] micrOS LM naming convention
54
+ Load the L298N motor driver module
55
+ """
56
+ global PWM_FREQ
57
+ if pwm_freq is not None:
58
+ PWM_FREQ = pwm_freq
59
+ __l298n_init()
60
+ return "Motor driver loaded successfully."
61
+
62
+
63
+ def state(motor=0):
64
+ """
65
+ [i] micrOS LM naming convention
66
+ Get the current state of a motor or all motors
67
+ :param motor: Motor number (1 or 2) or None for all motors
68
+ """
69
+ if motor == 1:
70
+ return {'motor1': __get_motor_state(0)}
71
+ elif motor == 2:
72
+ return {'motor2': __get_motor_state(1)}
73
+ else:
74
+ return {
75
+ 'motor1': __get_motor_state(0),
76
+ 'motor2': __get_motor_state(1)
77
+ }
78
+
79
+
80
+ def _control_motor(motor, in1, in2):
81
+ objlist = __l298n_init()
82
+ motor_index = 0 if motor == 1 else 3
83
+ objlist[motor_index + 1].value(in1)
84
+ objlist[motor_index + 2].value(in2)
85
+
86
+
87
+ def speed(motor, speed:int=1023):
88
+ """
89
+ Set the speed of a motor
90
+ :param motor: Motor number (1 or 2)
91
+ :param speed: Speed value (0-1023)
92
+ :return: Current motor state
93
+ """
94
+ if not (0 <= speed <= 1023):
95
+ return {'speed': 'value range error'}
96
+ pwm_index = 0 if motor == 1 else 3
97
+ motor_index = 0 if motor == 1 else 1
98
+ __l298n_init()[pwm_index].duty(speed)
99
+ __MOTOR_SPEEDS[motor_index] = speed
100
+ return state(motor)
101
+
102
+
103
+ def direction(motor, forward: bool=True):
104
+ """
105
+ Set the direction of a motor
106
+ :param motor: Motor number (1 or 2)
107
+ :param forward: True if motor should move forward, False if backward
108
+ :return: Current motor state
109
+ """
110
+ _control_motor(motor, 1 if forward else 0, 0 if forward else 1)
111
+ return state(motor)
112
+
113
+
114
+ def coast(motor):
115
+ """
116
+ Coast the motor
117
+ :param motor: Motor number (1 or 2)
118
+ """
119
+ _control_motor(motor, 0, 0)
120
+ return state(motor)
121
+
122
+
123
+ def brake(motor):
124
+ """
125
+ Brake the motor
126
+ :param motor: Motor number (1 or 2)
127
+ :return: Current motor state
128
+ """
129
+ _control_motor(motor, 1, 1)
130
+ return state(motor)
131
+
132
+
133
+ #######################
134
+ # LM helper functions #
135
+ #######################
136
+
137
+ def pinmap():
138
+ """
139
+ [i] micrOS LM naming convention
140
+ Shows logical pins - pin number(s) used by this Load module
141
+ - info which pins to use for this application
142
+ :return dict: pin name (str) - pin value (int) pairs
143
+ """
144
+ return pinmap_search([name for name, _ in PIN_BINDINGS])
145
+
146
+
147
+ def help(widgets=False):
148
+ """
149
+ [i] micrOS LM naming convention - built-in help message
150
+ :return tuple:
151
+ (widgets=False) list of functions implemented by this application
152
+ (widgets=True) list of widget json for UI generation
153
+ """
154
+ return (
155
+ 'speed motor=<1/2> speed=<0-1023>',
156
+ 'direction motor=<1/2> forward=<True/False>',
157
+ 'coast motor=<1/2>',
158
+ 'brake motor=<1/2>',
159
+ 'state motor=<0/1/2>',
160
+ 'pinmap'
161
+ )
@@ -0,0 +1,250 @@
1
+ """
2
+ micrOS ESPNow cluster module.
3
+ -- STA/AP Channel sync [OK]
4
+ -- wifi settings sync [OK]
5
+ -- health (cluster availability) [OK]
6
+ -- cluster run <cmd> [OK]
7
+ ShellCli (socket) extension (todo)
8
+ -- health (cluster availability)
9
+ """
10
+
11
+ from binascii import hexlify
12
+ from machine import soft_reset
13
+ from Config import cfgget, cfgput
14
+ from Network import _select_available_wifi_nw, ifconfig, WLAN, STA_IF, AP_IF
15
+ from Common import micro_task, exec_cmd, syslog
16
+ if cfgget("espnow"):
17
+ from Espnow import ESPNowSS
18
+ ESPNOW = ESPNowSS()
19
+ else:
20
+ ESPNOW = None
21
+
22
+ class Cluster:
23
+ CHANNEL = 6 # Leader channel to align with
24
+ ANCHOR_SSID_POSTFIX = "-mCSA" # micrOS (Wifi) Channel Sync Anchor
25
+ ANCHOR_ENABLED = False # Anchor indicator
26
+ REFRESH_MS = 20 * 60_000 # 20min - refresh channel sync period (AP mode)
27
+
28
+ #########################################################
29
+ # CLUSTER - LOCAL EXECUTION "ON TARGET" #
30
+ #########################################################
31
+
32
+ @micro_task("cluster", _wrap=True)
33
+ async def _reboot(tag:str):
34
+ """
35
+ Restart in the background
36
+ (Enable function return before restart...)
37
+ """
38
+ with micro_task(tag) as my_task:
39
+ await my_task.feed(3000)
40
+ soft_reset()
41
+
42
+
43
+ def _update_wifi(ssid:str, pwd:str):
44
+ """
45
+ Local Config Setter - Add WiFi settings
46
+ :param ssid: str - SSID
47
+ :param pwd: str - Password
48
+ """
49
+ def _update_sta_config():
50
+ nonlocal _ssid, _pwd
51
+ essids = cfgget("staessid")
52
+ passwords = cfgget("stapwd")
53
+ if _ssid in essids:
54
+ # [INFO] No password refresh is supported
55
+ return f"Wifi already known: {ssid}"
56
+ # Deserialize wifi settings from config file
57
+ essids_list = essids.split(";")
58
+ passwords_list = passwords.split(";")
59
+ # Extend with new wifi settings
60
+ essids_list.append(_ssid)
61
+ passwords_list.append(_pwd)
62
+ # Serialize back wifi settings to config file
63
+ cfgput("staessid",";".join(essids_list))
64
+ cfgput("stapwd", ";".join(passwords_list))
65
+ # REBOOT
66
+ _reboot()
67
+ return "Soft reboot, apply wifi settings"
68
+
69
+ if ';' in ssid or ';' in pwd:
70
+ return "Unsupported character in ssid or pwd ;"
71
+ # Check Wifi settings
72
+ if cfgget("nwmd").upper() == "STA":
73
+ # Check current wifi state
74
+ if ifconfig()[0] == "STA":
75
+ return "Device already connected to WiFi"
76
+ # Validate wifi essid is available
77
+ _ssid, _pwd = _select_available_wifi_nw(ssid, pwd)
78
+ if _ssid is None:
79
+ return f"Wifi SSID not available: {ssid}"
80
+ return _update_sta_config()
81
+ return "Wifi is in Access Point mode, skip sync"
82
+
83
+
84
+ def _run(cmd:str):
85
+ state, out = exec_cmd(cmd.split(), secure=True)
86
+ return f"[{'OK' if state else 'NOK'}] {out}"
87
+
88
+
89
+ @micro_task("cluster", _wrap=True)
90
+ async def _align_channel(tag:str, sta:STA_IF, ap:AP_IF):
91
+ _initialized = False
92
+
93
+ with micro_task(tag) as my_task:
94
+ while True:
95
+ my_task.out = "Sync in progress..."
96
+ _channel = Cluster.CHANNEL
97
+ _refresh_ms = Cluster.REFRESH_MS
98
+ for s, b, c, *_ in sta.scan():
99
+ sta_name = (s.decode() if isinstance(s, bytes) else s)
100
+ if sta_name.endswith(Cluster.ANCHOR_SSID_POSTFIX):
101
+ my_task.out = f"Anchor {sta_name} channel: {c} (refresh: {int(_refresh_ms/60_000)}min)"
102
+ _channel = c
103
+
104
+ if not _initialized or _channel != Cluster.CHANNEL:
105
+ Cluster.CHANNEL = _channel
106
+ if not sta.isconnected():
107
+ # ENABLE ESPNOW STA MAC ACCESS - AFTER THIS ALL DEVICES CAN REACH EACH OTHER ON STA MAC ADDRESS
108
+ try: sta.config(channel=_channel)
109
+ except Exception as e:
110
+ my_task.out = f"STA Failed to set {_channel} channel: {e}"
111
+ # ensure AP advertises/operates on the same channel (important when STA not connected yet)
112
+ try: ap.config(channel=_channel)
113
+ except Exception as e:
114
+ my_task.out = f"AP Failed to set {_channel} channel: {e}"
115
+ _initialized = True
116
+ await my_task.feed(_refresh_ms)
117
+
118
+
119
+ def __configure_cluster_network(anchor=False):
120
+ """
121
+ Support hybrid AP/STA ESPNow communication
122
+ For ESPNow communication all device should use the same Wifi channel!
123
+ - LEADER: STA*/AP mode - *channel is set by the router and can change
124
+ - AP deices should use the same channel as ANCHOR (discovery by leader/anchor(STA) over AP anchor)
125
+ - ESPNOW: STA mac addresses are stored on ANCHOR
126
+ :param anchor: enable anchor mode (AP) for ESPNow channel synchronization
127
+ """
128
+ def _conf_ap_anchor():
129
+ _ap = WLAN(AP_IF)
130
+ if not _ap.active(): _ap.active(True)
131
+ anchor_ssid = f"{cfgget('devfid')}{Cluster.ANCHOR_SSID_POSTFIX}"
132
+ try:
133
+ _ap.config(essid=anchor_ssid, password=cfgget("appwd"), authmode=3, max_clients=5)
134
+ return True
135
+ except Exception as e:
136
+ syslog(f"[ERR] Anchor configuration failed: {e}")
137
+ return False
138
+ def _enable_sta():
139
+ sta = WLAN(STA_IF)
140
+ if not sta.active(): sta.active(True)
141
+ return sta
142
+
143
+ # CONFIGURE WIFI CHANNEL SYNC ROLES
144
+ micros_nw = ifconfig()[0]
145
+ # MICROS in STA MODE
146
+ if micros_nw == "STA":
147
+ if anchor:
148
+ # CREATE CHANNEL ANCHOR - AP channel is inherited from STA
149
+ Cluster.ANCHOR_ENABLED = _conf_ap_anchor()
150
+ else:
151
+ # MICROS in AP MODE: CLUSTER FOLLOWER - WIFI CHANNEL SYNC WITH ANCHOR
152
+ ap, sta = WLAN(AP_IF), _enable_sta()
153
+ _align_channel(sta, ap)
154
+ if anchor:
155
+ # MICROS in AP MODE - RECONFIGURE AP NAME to match Anchor
156
+ # EXPERIMENTAL FEATURE: Scans and Advertise the channel as well.
157
+ Cluster.ANCHOR_ENABLED = _conf_ap_anchor()
158
+ return status()
159
+
160
+
161
+ #########################################################
162
+ # CLUSTER WIDE FEATURES #
163
+ #########################################################
164
+
165
+ def sync_wifi():
166
+ """
167
+ ESPNow Cluster Wifi Settings Sync for Station (STA) Connection Mode
168
+ - Join devices to the same wifi network...
169
+ """
170
+ if ESPNOW is None:
171
+ return "ESPNow is disabled"
172
+ # Check current wifi state
173
+ if ifconfig()[0] != "STA":
174
+ return "No WiFi STA Connection: no verified settings to share"
175
+ # [1] Get current wifi ssid and password
176
+ _ssid, _pwd = _select_available_wifi_nw(cfgget("staessid"), cfgget("stapwd"))
177
+ # [2] Send command: 'cluster _update_wifi "<ssid>" "<pwd>"'
178
+ command = f"cluster _update_wifi '{_ssid}' '{_pwd}'"
179
+ sync_tasks = ESPNOW.cluster_send(command)
180
+ return sync_tasks
181
+
182
+
183
+ def run(cmd:str):
184
+ """
185
+ Run a command on the cluster
186
+ :param cmd: str - command to run on the cluster
187
+ Example: cmd='system heartbeat'
188
+ """
189
+ command = f"cluster _run '{cmd}'"
190
+ sync_tasks = ESPNOW.cluster_send(command)
191
+ return sync_tasks
192
+
193
+
194
+ def health():
195
+ """
196
+ Simple connection check
197
+ - with default espnow-micros hello message
198
+ """
199
+ if ESPNOW is None:
200
+ return "ESPNow is disabled"
201
+ return ESPNOW.cluster_send("hello")
202
+
203
+
204
+ def members():
205
+ """
206
+ List all members in the cluster
207
+ """
208
+ if ESPNOW is None:
209
+ return "ESPNow is disabled"
210
+ return {hexlify(mac, ':').decode(): name for mac, name in ESPNOW.devices.items()}
211
+
212
+
213
+ def status():
214
+ """
215
+ Cluster setup status
216
+ """
217
+ nw_if = ifconfig()[0]
218
+ cluster_settings = {"channel": Cluster.CHANNEL, "primary_nw": nw_if,
219
+ "anchor": Cluster.ANCHOR_ENABLED}
220
+ if nw_if == "AP":
221
+ cluster_settings["refresh"] = Cluster.REFRESH_MS
222
+ return cluster_settings
223
+
224
+
225
+ def load(anchor:bool=False, refresh:int=None):
226
+ """
227
+ Enable ESPNow protocol 'cluster' module access
228
+ :param anchor: create AP as Channel Anchor (Channel Leader Role)
229
+ :param refresh: refresh channel sync period in minute (AP)
230
+ """
231
+ if ESPNOW is None:
232
+ return "ESPNow is disabled"
233
+ if refresh is not None:
234
+ Cluster.REFRESH_MS = 60_000 if refresh < 1 else refresh * 60_000
235
+ out = __configure_cluster_network(anchor)
236
+ return f"Enable cluster module access\n{out}"
237
+
238
+
239
+ def help(widgets=True):
240
+ """
241
+ [BETA]
242
+ Show help for cluster commands
243
+ """
244
+ return ("load anchor=False refresh=20",
245
+ "run 'command'",
246
+ "sync_wifi",
247
+ "health",
248
+ "members",
249
+ "status",
250
+ "[Info] Get command results:\n\ttask show con.espnow.*")
@@ -18,6 +18,11 @@ def temp():
18
18
  """
19
19
  Measure CPU temperature
20
20
  """
21
+ try:
22
+ cpu_temp = esp32.mcu_temperature()
23
+ return {'MCU temp [C]': int(cpu_temp)}
24
+ except:
25
+ pass
21
26
  try:
22
27
  raw_temp = esp32.raw_temperature()
23
28
  except Exception as e:
@@ -0,0 +1,36 @@
1
+ from Espnow import ESPNowSS
2
+ ESPNOW = ESPNowSS()
3
+
4
+
5
+ def send(peer:bytes|str, cmd:str='modules'):
6
+ """
7
+ Send message to peer (by mac address)
8
+ :param peer: mac address of espnow device
9
+ :param cmd: message string/load module call
10
+ """
11
+ return ESPNOW.send(peer, cmd)
12
+
13
+ def stats():
14
+ """
15
+ Get ESPNOW stats
16
+ """
17
+ return ESPNOW.stats()
18
+
19
+
20
+ def handshake(peer:bytes|str):
21
+ """
22
+ Handshake with ESPNow Peer
23
+ :param peer: mac address of espnow device
24
+ - device name detection
25
+ - address:name caching
26
+ """
27
+ return ESPNOW.handshake(peer)
28
+
29
+
30
+ def help():
31
+ """
32
+ ESPNOW sender/receiver with LM execution
33
+ """
34
+ return ('handshake peer=<mac-address>',
35
+ 'send peer=<peer-name> cmd="hello"',
36
+ 'stats')
@@ -26,7 +26,7 @@ def discover():
26
26
  """
27
27
  known_addresses = {hex(0x0A): "TRACKBALL", hex(0x3c): "OLED",
28
28
  hex(0x76): "BME280", hex(0x10): 'VEML7700',
29
- hex(0x6b): "QMI8658"}
29
+ hex(0x6b): "QMI8658", hex(0x29): 'TCS3472'}
30
30
  devices = scan()
31
31
  output = {"unknown": []}
32
32
  for k in devices:
@@ -73,13 +73,13 @@ async def _task(on, off, threshold, tolerance=2, check_ms=5000):
73
73
  if percent <= threshold:
74
74
  if on != last_ev:
75
75
  host = on[0]
76
- state, _ = exec_cmd(on[1:] + [f">>{host}"], jsonify=True, skip_check=True)
76
+ state, _ = exec_cmd(on[1:] + [f">>{host}"], jsonify=True)
77
77
  my_task.out = f"{percent}% <= threshold: {threshold}% - ON [{'OK' if state else 'NOK'}]"
78
78
  last_ev = on
79
79
  elif percent > threshold+tolerance: # +tolerance to avoid "on/off/on/off" on threshold limit
80
80
  if off != last_ev:
81
81
  host = off[0]
82
- state, _ = exec_cmd(off[1:] + [f">>{host}"], jsonify=True, skip_check=True)
82
+ state, _ = exec_cmd(off[1:] + [f">>{host}"], jsonify=True)
83
83
  my_task.out = f"{percent}% > threshold: {threshold+tolerance}% - OFF [{'OK' if state else 'NOK'}]"
84
84
  last_ev = off
85
85
  await my_task.feed(sleep_ms=check_ms) # Sample every <check_ms> sec