micrOSDevToolKit 2.13.1__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.
- micrOS/release_info/micrOS_ReleaseInfo/system_analysis_sum.json +125 -121
- micrOS/source/Common.py +48 -26
- micrOS/source/Config.py +13 -5
- micrOS/source/Espnow.py +100 -58
- micrOS/source/Files.py +77 -41
- micrOS/source/Hooks.py +18 -34
- micrOS/source/Logger.py +2 -7
- micrOS/source/Network.py +36 -16
- micrOS/source/Server.py +22 -8
- micrOS/source/Shell.py +9 -6
- micrOS/source/Tasks.py +34 -13
- micrOS/source/Web.py +69 -31
- micrOS/source/__pycache__/Common.cpython-312.pyc +0 -0
- micrOS/source/__pycache__/Files.cpython-312.pyc +0 -0
- micrOS/source/__pycache__/Logger.cpython-312.pyc +0 -0
- micrOS/source/__pycache__/Server.cpython-312.pyc +0 -0
- micrOS/source/config/_git.keep +0 -0
- micrOS/source/micrOS.py +7 -0
- micrOS/source/micrOSloader.py +2 -10
- micrOS/source/microIO.py +2 -2
- micrOS/source/modules/IO_esp32c6.py +38 -0
- micrOS/source/modules/LM_L298N.py +161 -0
- micrOS/source/modules/LM_cluster.py +250 -0
- {toolkit/workspace/precompiled → micrOS/source/modules}/LM_esp32.py +5 -0
- micrOS/source/modules/LM_espnow.py +36 -0
- micrOS/source/{LM_i2c.py → modules/LM_i2c.py} +1 -1
- micrOS/source/{LM_light_sensor.py → modules/LM_light_sensor.py} +2 -2
- micrOS/source/modules/LM_mqtt_client.py +246 -0
- micrOS/source/{LM_neoeffects.py → modules/LM_neoeffects.py} +14 -4
- micrOS/source/{LM_neomatrix.py → modules/LM_neomatrix.py} +140 -38
- micrOS/source/{LM_oled_ui.py → modules/LM_oled_ui.py} +2 -2
- micrOS/source/{LM_oledui.py → modules/LM_oledui.py} +2 -2
- micrOS/source/{LM_pacman.py → modules/LM_pacman.py} +74 -29
- micrOS/source/{LM_presence.py → modules/LM_presence.py} +2 -2
- micrOS/source/{LM_robustness.py → modules/LM_robustness.py} +49 -2
- micrOS/source/{LM_tcs3472.py → modules/LM_tcs3472.py} +4 -6
- micrOS/source/web/dashboard.html +2 -0
- micrOS/source/web/matrix_draw.html +390 -0
- micrOS/source/web/uapi.js +9 -6
- {microsdevtoolkit-2.13.1.dist-info → microsdevtoolkit-2.17.0.dist-info}/METADATA +30 -37
- {microsdevtoolkit-2.13.1.dist-info → microsdevtoolkit-2.17.0.dist-info}/RECORD +200 -190
- toolkit/DevEnvCompile.py +21 -12
- toolkit/DevEnvOTA.py +27 -16
- toolkit/DevEnvUSB.py +35 -21
- toolkit/LM_to_compile.dat +3 -1
- toolkit/MicrOSDevEnv.py +37 -21
- toolkit/dashboard_apps/QMI8685_GYRO.py +1 -1
- toolkit/dashboard_apps/SystemTest.py +8 -5
- toolkit/{MicrosFiles.py → lib/MicrosFiles.py} +24 -4
- toolkit/micrOSdashboard.py +2 -2
- toolkit/micrOSlint.py +17 -7
- toolkit/simulator_lib/__pycache__/simulator.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/uos.cpython-312.pyc +0 -0
- toolkit/simulator_lib/mqtt_as/Note.md +15 -0
- toolkit/simulator_lib/mqtt_as/__init__.py +950 -0
- toolkit/simulator_lib/mqtt_as/__pycache__/__init__.cpython-312.pyc +0 -0
- toolkit/simulator_lib/mqtt_as/clean.py +69 -0
- toolkit/simulator_lib/mqtt_as/mqtt_v5_properties.py +239 -0
- toolkit/simulator_lib/mqtt_as/range.py +90 -0
- toolkit/simulator_lib/mqtt_as/range_ex.py +119 -0
- toolkit/simulator_lib/simulator.py +14 -1
- toolkit/simulator_lib/uos.py +2 -0
- toolkit/workspace/precompiled/Common.mpy +0 -0
- toolkit/workspace/precompiled/Config.mpy +0 -0
- toolkit/workspace/precompiled/Espnow.mpy +0 -0
- toolkit/workspace/precompiled/Files.mpy +0 -0
- toolkit/workspace/precompiled/Hooks.mpy +0 -0
- toolkit/workspace/precompiled/Logger.mpy +0 -0
- toolkit/workspace/precompiled/Network.mpy +0 -0
- toolkit/workspace/precompiled/Server.mpy +0 -0
- toolkit/workspace/precompiled/Shell.mpy +0 -0
- toolkit/workspace/precompiled/Tasks.mpy +0 -0
- toolkit/workspace/precompiled/Web.mpy +0 -0
- toolkit/workspace/precompiled/config/_git.keep +0 -0
- toolkit/workspace/precompiled/micrOS.mpy +0 -0
- toolkit/workspace/precompiled/micrOSloader.mpy +0 -0
- toolkit/workspace/precompiled/microIO.mpy +0 -0
- toolkit/workspace/precompiled/{IO_esp32.mpy → modules/IO_esp32.mpy} +0 -0
- toolkit/workspace/precompiled/{IO_esp32c3.mpy → modules/IO_esp32c3.mpy} +0 -0
- toolkit/workspace/precompiled/modules/IO_esp32c6.mpy +0 -0
- toolkit/workspace/precompiled/{IO_esp32s2.mpy → modules/IO_esp32s2.mpy} +0 -0
- toolkit/workspace/precompiled/{IO_esp32s3.mpy → modules/IO_esp32s3.mpy} +0 -0
- toolkit/workspace/precompiled/{IO_m5stamp.mpy → modules/IO_m5stamp.mpy} +0 -0
- toolkit/workspace/precompiled/{IO_qtpy.mpy → modules/IO_qtpy.mpy} +0 -0
- toolkit/workspace/precompiled/modules/IO_rp2.mpy +0 -0
- toolkit/workspace/precompiled/modules/IO_s3matrix.mpy +0 -0
- toolkit/workspace/precompiled/{IO_tinypico.mpy → modules/IO_tinypico.mpy} +0 -0
- toolkit/workspace/precompiled/modules/LM_L298N.mpy +0 -0
- toolkit/workspace/precompiled/{LM_OV2640.mpy → modules/LM_OV2640.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_aht10.mpy → modules/LM_aht10.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_bme280.mpy → modules/LM_bme280.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_buzzer.mpy → modules/LM_buzzer.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_cct.mpy → modules/LM_cct.mpy} +0 -0
- toolkit/workspace/precompiled/modules/LM_cluster.mpy +0 -0
- toolkit/workspace/precompiled/{LM_co2.mpy → modules/LM_co2.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_dht11.mpy → modules/LM_dht11.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_dht22.mpy → modules/LM_dht22.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_dimmer.mpy → modules/LM_dimmer.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_distance.mpy → modules/LM_distance.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_ds18.mpy → modules/LM_ds18.mpy} +0 -0
- {micrOS/source → toolkit/workspace/precompiled/modules}/LM_esp32.py +5 -0
- toolkit/workspace/precompiled/modules/LM_espnow.py +36 -0
- toolkit/workspace/precompiled/{LM_gameOfLife.mpy → modules/LM_gameOfLife.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_genIO.mpy → modules/LM_genIO.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_haptic.mpy → modules/LM_haptic.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_i2c.py → modules/LM_i2c.py} +1 -1
- toolkit/workspace/precompiled/{LM_i2s_mic.mpy → modules/LM_i2s_mic.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_keychain.mpy → modules/LM_keychain.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_ld2410.mpy → modules/LM_ld2410.mpy} +0 -0
- toolkit/workspace/precompiled/modules/LM_light_sensor.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_mqtt_client.mpy +0 -0
- toolkit/workspace/precompiled/{LM_neoeffects.mpy → modules/LM_neoeffects.mpy} +0 -0
- toolkit/workspace/precompiled/modules/LM_neomatrix.mpy +0 -0
- toolkit/workspace/precompiled/{LM_neopixel.mpy → modules/LM_neopixel.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_oled.mpy → modules/LM_oled.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_oled_sh1106.mpy → modules/LM_oled_sh1106.mpy} +0 -0
- toolkit/workspace/precompiled/modules/LM_oled_ui.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_oledui.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_pacman.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_presence.mpy +0 -0
- toolkit/workspace/precompiled/{LM_rest.mpy → modules/LM_rest.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_rgb.mpy → modules/LM_rgb.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_rgbcct.mpy → modules/LM_rgbcct.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_roboarm.mpy → modules/LM_roboarm.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_robustness.py → modules/LM_robustness.py} +49 -2
- toolkit/workspace/precompiled/{LM_servo.mpy → modules/LM_servo.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_sound_event.mpy → modules/LM_sound_event.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_stepper.mpy → modules/LM_stepper.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_switch.mpy → modules/LM_switch.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_system.mpy → modules/LM_system.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_tcs3472.py → modules/LM_tcs3472.py} +4 -6
- toolkit/workspace/precompiled/{LM_telegram.mpy → modules/LM_telegram.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_tinyrgb.mpy → modules/LM_tinyrgb.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_trackball.mpy → modules/LM_trackball.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_veml7700.mpy → modules/LM_veml7700.mpy} +0 -0
- toolkit/workspace/precompiled/web/dashboard.html +2 -0
- toolkit/workspace/precompiled/web/matrix_draw.html +390 -0
- toolkit/workspace/precompiled/web/uapi.js +9 -6
- micrOS/source/IO_esp32c6.py +0 -16
- micrOS/source/LM_L298N_DCmotor.py +0 -86
- micrOS/source/LM_espnow.py +0 -57
- micrOS/source/LM_mqtt_pro.py +0 -211
- toolkit/lib/file_extensions.py +0 -22
- toolkit/workspace/precompiled/Common.cpython-312.pyc +0 -0
- toolkit/workspace/precompiled/IO_esp32c6.mpy +0 -0
- toolkit/workspace/precompiled/IO_rp2.mpy +0 -0
- toolkit/workspace/precompiled/IO_s3matrix.mpy +0 -0
- toolkit/workspace/precompiled/LM_L298N_DCmotor.mpy +0 -0
- toolkit/workspace/precompiled/LM_espnow.py +0 -57
- toolkit/workspace/precompiled/LM_light_sensor.mpy +0 -0
- toolkit/workspace/precompiled/LM_mqtt_pro.py +0 -211
- toolkit/workspace/precompiled/LM_neomatrix.mpy +0 -0
- toolkit/workspace/precompiled/LM_oled_ui.mpy +0 -0
- toolkit/workspace/precompiled/LM_oledui.mpy +0 -0
- toolkit/workspace/precompiled/LM_pacman.mpy +0 -0
- toolkit/workspace/precompiled/LM_presence.mpy +0 -0
- toolkit/workspace/precompiled/Logger.cpython-312.pyc +0 -0
- toolkit/workspace/precompiled/Server.cpython-312.pyc +0 -0
- /micrOS/source/{IO_esp32.py → modules/IO_esp32.py} +0 -0
- /micrOS/source/{IO_esp32c3.py → modules/IO_esp32c3.py} +0 -0
- /micrOS/source/{IO_esp32s2.py → modules/IO_esp32s2.py} +0 -0
- /micrOS/source/{IO_esp32s3.py → modules/IO_esp32s3.py} +0 -0
- /micrOS/source/{IO_m5stamp.py → modules/IO_m5stamp.py} +0 -0
- /micrOS/source/{IO_qtpy.py → modules/IO_qtpy.py} +0 -0
- /micrOS/source/{IO_rp2.py → modules/IO_rp2.py} +0 -0
- /micrOS/source/{IO_s3matrix.py → modules/IO_s3matrix.py} +0 -0
- /micrOS/source/{IO_tinypico.py → modules/IO_tinypico.py} +0 -0
- /micrOS/source/{LM_L9110_DCmotor.py → modules/LM_L9110_DCmotor.py} +0 -0
- /micrOS/source/{LM_OV2640.py → modules/LM_OV2640.py} +0 -0
- /micrOS/source/{LM_VL53L0X.py → modules/LM_VL53L0X.py} +0 -0
- /micrOS/source/{LM_aht10.py → modules/LM_aht10.py} +0 -0
- /micrOS/source/{LM_bme280.py → modules/LM_bme280.py} +0 -0
- /micrOS/source/{LM_buzzer.py → modules/LM_buzzer.py} +0 -0
- /micrOS/source/{LM_cct.py → modules/LM_cct.py} +0 -0
- /micrOS/source/{LM_co2.py → modules/LM_co2.py} +0 -0
- /micrOS/source/{LM_dashboard_be.py → modules/LM_dashboard_be.py} +0 -0
- /micrOS/source/{LM_dht11.py → modules/LM_dht11.py} +0 -0
- /micrOS/source/{LM_dht22.py → modules/LM_dht22.py} +0 -0
- /micrOS/source/{LM_dimmer.py → modules/LM_dimmer.py} +0 -0
- /micrOS/source/{LM_distance.py → modules/LM_distance.py} +0 -0
- /micrOS/source/{LM_ds18.py → modules/LM_ds18.py} +0 -0
- /micrOS/source/{LM_gameOfLife.py → modules/LM_gameOfLife.py} +0 -0
- /micrOS/source/{LM_genIO.py → modules/LM_genIO.py} +0 -0
- /micrOS/source/{LM_haptic.py → modules/LM_haptic.py} +0 -0
- /micrOS/source/{LM_i2s_mic.py → modules/LM_i2s_mic.py} +0 -0
- /micrOS/source/{LM_keychain.py → modules/LM_keychain.py} +0 -0
- /micrOS/source/{LM_ld2410.py → modules/LM_ld2410.py} +0 -0
- /micrOS/source/{LM_neopixel.py → modules/LM_neopixel.py} +0 -0
- /micrOS/source/{LM_oled.py → modules/LM_oled.py} +0 -0
- /micrOS/source/{LM_oled_sh1106.py → modules/LM_oled_sh1106.py} +0 -0
- /micrOS/source/{LM_pet_feeder.py → modules/LM_pet_feeder.py} +0 -0
- /micrOS/source/{LM_qmi8658.py → modules/LM_qmi8658.py} +0 -0
- /micrOS/source/{LM_rencoder.py → modules/LM_rencoder.py} +0 -0
- /micrOS/source/{LM_rest.py → modules/LM_rest.py} +0 -0
- /micrOS/source/{LM_rgb.py → modules/LM_rgb.py} +0 -0
- /micrOS/source/{LM_rgbcct.py → modules/LM_rgbcct.py} +0 -0
- /micrOS/source/{LM_roboarm.py → modules/LM_roboarm.py} +0 -0
- /micrOS/source/{LM_rp2w.py → modules/LM_rp2w.py} +0 -0
- /micrOS/source/{LM_sdcard.py → modules/LM_sdcard.py} +0 -0
- /micrOS/source/{LM_servo.py → modules/LM_servo.py} +0 -0
- /micrOS/source/{LM_sound_event.py → modules/LM_sound_event.py} +0 -0
- /micrOS/source/{LM_stepper.py → modules/LM_stepper.py} +0 -0
- /micrOS/source/{LM_switch.py → modules/LM_switch.py} +0 -0
- /micrOS/source/{LM_system.py → modules/LM_system.py} +0 -0
- /micrOS/source/{LM_telegram.py → modules/LM_telegram.py} +0 -0
- /micrOS/source/{LM_tinyrgb.py → modules/LM_tinyrgb.py} +0 -0
- /micrOS/source/{LM_trackball.py → modules/LM_trackball.py} +0 -0
- /micrOS/source/{LM_veml7700.py → modules/LM_veml7700.py} +0 -0
- {microsdevtoolkit-2.13.1.data → microsdevtoolkit-2.17.0.data}/scripts/devToolKit.py +0 -0
- {microsdevtoolkit-2.13.1.dist-info → microsdevtoolkit-2.17.0.dist-info}/WHEEL +0 -0
- {microsdevtoolkit-2.13.1.dist-info → microsdevtoolkit-2.17.0.dist-info}/licenses/LICENSE +0 -0
- {microsdevtoolkit-2.13.1.dist-info → microsdevtoolkit-2.17.0.dist-info}/top_level.txt +0 -0
- /toolkit/workspace/precompiled/{LM_L9110_DCmotor.py → modules/LM_L9110_DCmotor.py} +0 -0
- /toolkit/workspace/precompiled/{LM_VL53L0X.py → modules/LM_VL53L0X.py} +0 -0
- /toolkit/workspace/precompiled/{LM_dashboard_be.py → modules/LM_dashboard_be.py} +0 -0
- /toolkit/workspace/precompiled/{LM_pet_feeder.py → modules/LM_pet_feeder.py} +0 -0
- /toolkit/workspace/precompiled/{LM_qmi8658.py → modules/LM_qmi8658.py} +0 -0
- /toolkit/workspace/precompiled/{LM_rencoder.py → modules/LM_rencoder.py} +0 -0
- /toolkit/workspace/precompiled/{LM_rp2w.py → modules/LM_rp2w.py} +0 -0
- /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.*")
|
|
@@ -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
|
|
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
|
|
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
|