micrOSDevToolKit 2.9.1__py3-none-any.whl → 2.26.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- env/driver_cp210x/macOS_VCP_Driver/SiLabsUSBDriverDisk.dmg +0 -0
- env/driver_cp210x/macOS_VCP_Driver/macOS_VCP_Driver_Release_Notes.txt +17 -1
- micrOS/micropython/esp32-20251209-v1.27.0.bin +0 -0
- micrOS/micropython/esp32c3-20251209-v1.27.0.bin +0 -0
- micrOS/micropython/esp32c6-20251209-v1.27.0.bin +0 -0
- micrOS/micropython/esp32s2-20251209-v1.27.0.bin +0 -0
- micrOS/micropython/esp32s2-LOLIN_MINI-20251209-v1.27.0.bin +0 -0
- micrOS/micropython/{esp32s3-20241129-v1.24.1.bin → esp32s3-4MBflash-20241129-v1.24.1.bin} +0 -0
- micrOS/micropython/esp32s3-8MBflash-20251209-v1.27.0.bin +0 -0
- micrOS/micropython/esp32s3_spiram_oct-20251209-v1.27.0.bin +0 -0
- micrOS/micropython/rpi-pico-w-20251209-v1.27.0.uf2 +0 -0
- micrOS/micropython/tinypico-20251209-v1.27.0.bin +0 -0
- micrOS/release_info/micrOS_ReleaseInfo/system_analysis_sum.json +167 -163
- micrOS/source/Auth.py +37 -0
- micrOS/source/Common.py +361 -116
- micrOS/source/Config.py +32 -22
- micrOS/source/Debug.py +50 -94
- micrOS/source/Espnow.py +377 -100
- micrOS/source/Files.py +207 -0
- micrOS/source/Hooks.py +48 -20
- micrOS/source/InterConnect.py +126 -42
- micrOS/source/Interrupts.py +6 -6
- micrOS/source/Logger.py +63 -26
- micrOS/source/Network.py +41 -21
- micrOS/source/Notify.py +48 -22
- micrOS/source/Pacman.py +326 -0
- micrOS/source/Scheduler.py +14 -54
- micrOS/source/Server.py +67 -69
- micrOS/source/Shell.py +99 -91
- micrOS/source/Tasks.py +141 -95
- micrOS/source/Time.py +19 -18
- micrOS/source/Types.py +53 -9
- micrOS/source/Web.py +381 -76
- micrOS/source/__pycache__/Common.cpython-312.pyc +0 -0
- micrOS/source/__pycache__/Debug.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__/Scheduler.cpython-312.pyc +0 -0
- micrOS/source/__pycache__/Server.cpython-312.pyc +0 -0
- micrOS/source/__pycache__/Shell.cpython-312.pyc +0 -0
- micrOS/source/__pycache__/replhelper.cpython-312.pyc +0 -0
- micrOS/source/config/_git.keep +0 -0
- micrOS/source/helpers.py +132 -0
- micrOS/source/micrOS.py +17 -7
- micrOS/source/micrOSloader.py +5 -12
- micrOS/source/microIO.py +44 -20
- micrOS/source/modules/IO_esp32c6.py +38 -0
- micrOS/source/{IO_esp32s3.py → modules/IO_esp32s3.py} +37 -1
- micrOS/source/{IO_m5stamp.py → modules/IO_m5stamp.py} +35 -1
- micrOS/source/{IO_qtpy.py → modules/IO_qtpy.py} +22 -17
- micrOS/source/{IO_tinypico.py → modules/IO_tinypico.py} +38 -0
- micrOS/source/modules/LM_L298N.py +161 -0
- {toolkit/workspace/precompiled → micrOS/source/modules}/LM_L9110_DCmotor.py +3 -3
- micrOS/source/{LM_OV2640.py → modules/LM_OV2640.py} +45 -27
- micrOS/source/{LM_VL53L0X.py → modules/LM_VL53L0X.py} +3 -3
- micrOS/source/{LM_aht10.py → modules/LM_aht10.py} +2 -2
- micrOS/source/{LM_bme280.py → modules/LM_bme280.py} +3 -3
- micrOS/source/{LM_buzzer.py → modules/LM_buzzer.py} +18 -25
- micrOS/source/{LM_cct.py → modules/LM_cct.py} +17 -21
- micrOS/source/modules/LM_cluster.py +255 -0
- micrOS/source/{LM_co2.py → modules/LM_co2.py} +3 -3
- micrOS/source/{LM_dht11.py → modules/LM_dht11.py} +2 -2
- micrOS/source/{LM_dht22.py → modules/LM_dht22.py} +2 -2
- micrOS/source/{LM_dimmer.py → modules/LM_dimmer.py} +9 -9
- micrOS/source/{LM_distance.py → modules/LM_distance.py} +4 -6
- micrOS/source/{LM_ds18.py → modules/LM_ds18.py} +2 -2
- micrOS/source/{LM_esp32.py → modules/LM_esp32.py} +5 -0
- micrOS/source/modules/LM_espnow.py +53 -0
- micrOS/source/modules/LM_fileserver.py +265 -0
- micrOS/source/{LM_genIO.py → modules/LM_genIO.py} +52 -37
- micrOS/source/{LM_haptic.py → modules/LM_haptic.py} +2 -2
- {toolkit/workspace/precompiled → micrOS/source/modules}/LM_i2c.py +5 -4
- micrOS/source/{LM_i2s_mic.py → modules/LM_i2s_mic.py} +6 -7
- micrOS/source/{LM_ld2410.py → modules/LM_ld2410.py} +2 -2
- micrOS/source/{LM_light_sensor.py → modules/LM_light_sensor.py} +10 -21
- micrOS/source/modules/LM_mh_z19c.py +198 -0
- micrOS/source/modules/LM_neoeffects.py +284 -0
- micrOS/source/{LM_neopixel.py → modules/LM_neopixel.py} +19 -23
- micrOS/source/{LM_oled.py → modules/LM_oled.py} +2 -2
- micrOS/source/{LM_oled_sh1106.py → modules/LM_oled_sh1106.py} +3 -3
- micrOS/source/{LM_oled_ui.py → modules/LM_oled_ui.py} +72 -64
- micrOS/source/modules/LM_pacman.py +320 -0
- micrOS/source/{LM_presence.py → modules/LM_presence.py} +11 -15
- micrOS/source/modules/LM_qmi8658.py +204 -0
- micrOS/source/{LM_rencoder.py → modules/LM_rencoder.py} +2 -2
- micrOS/source/{LM_rest.py → modules/LM_rest.py} +4 -6
- micrOS/source/{LM_rgb.py → modules/LM_rgb.py} +21 -29
- micrOS/source/{LM_roboarm.py → modules/LM_roboarm.py} +8 -8
- micrOS/source/modules/LM_robustness.py +137 -0
- micrOS/source/{LM_servo.py → modules/LM_servo.py} +3 -3
- micrOS/source/{LM_stepper.py → modules/LM_stepper.py} +5 -5
- micrOS/source/{LM_switch.py → modules/LM_switch.py} +11 -9
- micrOS/source/{LM_system.py → modules/LM_system.py} +38 -32
- micrOS/source/modules/LM_tcs3472.py +187 -0
- micrOS/source/{LM_telegram.py → modules/LM_telegram.py} +164 -116
- micrOS/source/{LM_trackball.py → modules/LM_trackball.py} +3 -3
- micrOS/source/{LM_veml7700.py → modules/LM_veml7700.py} +2 -2
- micrOS/source/modules/LM_web.py +38 -0
- micrOS/source/urequests.py +39 -15
- {toolkit/workspace/precompiled → micrOS/source/web}/dashboard.html +4 -0
- micrOS/source/web/editor.js +440 -0
- micrOS/source/web/filesui.html +178 -0
- micrOS/source/web/filesui.js +338 -0
- {toolkit/workspace/precompiled → micrOS/source/web}/index.html +44 -2
- micrOS/source/{uapi.js → web/uapi.js} +48 -7
- micrOS/source/{ustyle.css → web/ustyle.css} +6 -3
- micrOS/utests/__init__.py +0 -0
- micrOS/utests/test_scheduler.py +435 -0
- {micrOSDevToolKit-2.9.1.data → microsdevtoolkit-2.26.1.data}/scripts/devToolKit.py +33 -3
- {micrOSDevToolKit-2.9.1.dist-info → microsdevtoolkit-2.26.1.dist-info}/METADATA +327 -268
- microsdevtoolkit-2.26.1.dist-info/RECORD +396 -0
- {micrOSDevToolKit-2.9.1.dist-info → microsdevtoolkit-2.26.1.dist-info}/WHEEL +1 -1
- toolkit/DevEnvCompile.py +63 -33
- toolkit/DevEnvOTA.py +58 -22
- toolkit/DevEnvUSB.py +110 -55
- toolkit/Gateway.py +6 -6
- toolkit/LM_to_compile.dat +6 -4
- toolkit/MicrOSDevEnv.py +127 -57
- toolkit/WebRepl.py +73 -0
- toolkit/dashboard_apps/BackupRestore.py +20 -35
- toolkit/dashboard_apps/CCTDemo.py +12 -17
- toolkit/dashboard_apps/CCTTest.py +20 -24
- toolkit/dashboard_apps/CamStream.py +2 -6
- toolkit/dashboard_apps/CatGame.py +14 -16
- toolkit/dashboard_apps/Dimmer.py +11 -21
- toolkit/dashboard_apps/GetVersion.py +11 -19
- toolkit/dashboard_apps/MicrophoneTest.py +1 -6
- toolkit/dashboard_apps/NeoEffectsDemo.py +22 -35
- toolkit/dashboard_apps/NeopixelTest.py +20 -25
- toolkit/dashboard_apps/PresenceTest.py +2 -8
- toolkit/dashboard_apps/QMI8685_GYRO.py +68 -0
- toolkit/dashboard_apps/RGBTest.py +20 -24
- toolkit/dashboard_apps/RoboArm.py +24 -32
- toolkit/dashboard_apps/SED_test.py +10 -14
- toolkit/dashboard_apps/SensorsTest.py +61 -0
- toolkit/dashboard_apps/SystemTest.py +202 -105
- toolkit/dashboard_apps/Template_app.py +11 -23
- toolkit/dashboard_apps/_app_base.py +34 -0
- toolkit/dashboard_apps/_gyro_visualizer.py +78 -0
- toolkit/dashboard_apps/uLightDemo.py +15 -24
- toolkit/index.html +4 -4
- toolkit/lib/LocalMachine.py +6 -1
- toolkit/lib/MicrosFiles.py +46 -0
- toolkit/lib/Repository.py +64 -0
- toolkit/lib/TerminalColors.py +4 -0
- toolkit/lib/macroScript.py +6 -0
- toolkit/lib/micrOSClient.py +123 -50
- toolkit/lib/micrOSClientHistory.py +156 -0
- toolkit/lib/pip_package_installer.py +5 -2
- toolkit/micrOSdashboard.py +12 -17
- toolkit/micrOSlint.py +20 -8
- toolkit/simulator_lib/__pycache__/IO_darwin.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/aioespnow.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/framebuf.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/machine.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/micropython.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/mip.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/neopixel.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/network.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/sim_common.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/simgc.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/simulator.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/uasyncio.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/uos.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/urandom.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/usocket.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/ussl.cpython-312.pyc +0 -0
- toolkit/simulator_lib/aioespnow.py +28 -0
- toolkit/simulator_lib/dht.py +1 -1
- toolkit/simulator_lib/framebuf.py +49 -1
- toolkit/simulator_lib/machine.py +17 -2
- toolkit/simulator_lib/micropython.py +3 -3
- toolkit/simulator_lib/mip.py +165 -0
- toolkit/simulator_lib/neopixel.py +3 -2
- toolkit/simulator_lib/network.py +2 -1
- toolkit/simulator_lib/node_config.json +2 -3
- toolkit/simulator_lib/ntptime.py +1 -1
- toolkit/simulator_lib/{sim_console.py → sim_common.py} +2 -3
- toolkit/simulator_lib/simgc.py +6 -2
- toolkit/simulator_lib/simulator.py +137 -59
- toolkit/simulator_lib/uasyncio.py +33 -2
- toolkit/simulator_lib/uos.py +147 -0
- toolkit/simulator_lib/urandom.py +4 -0
- toolkit/socketClient.py +43 -23
- toolkit/user_data/webhooks/generic.py +1 -1
- toolkit/user_data/webhooks/macro.py +1 -1
- toolkit/user_data/webhooks/template.py +1 -1
- toolkit/workspace/precompiled/Auth.mpy +0 -0
- toolkit/workspace/precompiled/Common.mpy +0 -0
- toolkit/workspace/precompiled/Config.mpy +0 -0
- toolkit/workspace/precompiled/Debug.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/InterConnect.mpy +0 -0
- toolkit/workspace/precompiled/Interrupts.mpy +0 -0
- toolkit/workspace/precompiled/Logger.mpy +0 -0
- toolkit/workspace/precompiled/Network.mpy +0 -0
- toolkit/workspace/precompiled/Notify.mpy +0 -0
- toolkit/workspace/precompiled/Pacman.mpy +0 -0
- toolkit/workspace/precompiled/Scheduler.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/Time.mpy +0 -0
- toolkit/workspace/precompiled/Types.mpy +0 -0
- toolkit/workspace/precompiled/Web.mpy +0 -0
- toolkit/workspace/precompiled/_mpy.version +1 -1
- toolkit/workspace/precompiled/config/_git.keep +0 -0
- toolkit/workspace/precompiled/helpers.mpy +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/modules/IO_esp32s3.mpy +0 -0
- toolkit/workspace/precompiled/modules/IO_m5stamp.mpy +0 -0
- toolkit/workspace/precompiled/modules/IO_qtpy.mpy +0 -0
- toolkit/workspace/precompiled/modules/IO_rp2.mpy +0 -0
- toolkit/workspace/precompiled/modules/IO_tinypico.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_L298N.mpy +0 -0
- {micrOS/source → toolkit/workspace/precompiled/modules}/LM_L9110_DCmotor.py +3 -3
- toolkit/workspace/precompiled/modules/LM_OV2640.mpy +0 -0
- toolkit/workspace/precompiled/{LM_VL53L0X.py → modules/LM_VL53L0X.py} +3 -3
- 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/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/modules/LM_dimmer.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_distance.mpy +0 -0
- toolkit/workspace/precompiled/{LM_ds18.mpy → modules/LM_ds18.mpy} +0 -0
- toolkit/workspace/precompiled/{LM_esp32.py → modules/LM_esp32.py} +5 -0
- toolkit/workspace/precompiled/modules/LM_espnow.py +53 -0
- toolkit/workspace/precompiled/modules/LM_fileserver.mpy +0 -0
- toolkit/workspace/precompiled/{LM_gameOfLife.mpy → modules/LM_gameOfLife.mpy} +0 -0
- toolkit/workspace/precompiled/modules/LM_genIO.mpy +0 -0
- toolkit/workspace/precompiled/{LM_haptic.mpy → modules/LM_haptic.mpy} +0 -0
- {micrOS/source → toolkit/workspace/precompiled/modules}/LM_i2c.py +5 -4
- toolkit/workspace/precompiled/modules/LM_i2s_mic.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_mh_z19c.py +198 -0
- toolkit/workspace/precompiled/modules/LM_neoeffects.mpy +0 -0
- toolkit/workspace/precompiled/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_pacman.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_presence.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_qmi8658.py +204 -0
- toolkit/workspace/precompiled/{LM_rencoder.py → modules/LM_rencoder.py} +2 -2
- toolkit/workspace/precompiled/modules/LM_rest.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_rgb.mpy +0 -0
- toolkit/workspace/precompiled/{LM_rgbcct.mpy → modules/LM_rgbcct.mpy} +0 -0
- toolkit/workspace/precompiled/modules/LM_roboarm.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_robustness.py +137 -0
- 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/modules/LM_switch.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_system.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_tcs3472.py +187 -0
- toolkit/workspace/precompiled/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/modules/LM_web.mpy +0 -0
- toolkit/workspace/precompiled/urequests.mpy +0 -0
- {micrOS/source → toolkit/workspace/precompiled/web}/dashboard.html +4 -0
- toolkit/workspace/precompiled/web/editor.js +440 -0
- toolkit/workspace/precompiled/web/filesui.html +178 -0
- toolkit/workspace/precompiled/web/filesui.js +338 -0
- {micrOS/source → toolkit/workspace/precompiled/web}/index.html +44 -2
- toolkit/workspace/precompiled/{uapi.js → web/uapi.js} +48 -7
- toolkit/workspace/precompiled/{ustyle.css → web/ustyle.css} +6 -3
- micrOS/micropython/esp32-20241129-v1.24.1.bin +0 -0
- micrOS/micropython/esp32c3-20240222-v1.22.2.bin +0 -0
- micrOS/micropython/esp32s2-20240602-v1.23.0.bin +0 -0
- micrOS/micropython/esp32s2-LOLIN_MINI-20220618-v1.19.1.bin +0 -0
- micrOS/micropython/esp32s2-LOLIN_MINI-20240602-v1.23.0.bin +0 -0
- micrOS/micropython/esp32s3-20240105-v1.22.1.bin +0 -0
- micrOS/micropython/esp32s3_spiram_oct-20231005-v1.21.0.bin +0 -0
- micrOS/micropython/esp32s3_spiram_oct-20241129-v1.24.1.bin +0 -0
- micrOS/micropython/rpi-pico-w-20241129-v1.24.1.uf2 +0 -0
- micrOS/micropython/tinypico-20241129-v1.24.1.bin +0 -0
- micrOS/source/LM_L298N_DCmotor.py +0 -86
- micrOS/source/LM_catgame.py +0 -75
- micrOS/source/LM_dashboard_be.py +0 -37
- micrOS/source/LM_demo.py +0 -97
- micrOS/source/LM_espnow.py +0 -23
- micrOS/source/LM_intercon.py +0 -57
- micrOS/source/LM_keychain.py +0 -322
- micrOS/source/LM_lmpacman.py +0 -126
- micrOS/source/LM_neoeffects.py +0 -331
- micrOS/source/LM_oledui.py +0 -972
- micrOS/source/LM_pet_feeder.py +0 -78
- micrOS/source/LM_ph_sensor.py +0 -51
- micrOS/source/LM_robustness.py +0 -74
- micrOS/source/reset.py +0 -11
- micrOSDevToolKit-2.9.1.dist-info/RECORD +0 -365
- toolkit/dashboard_apps/AirQualityBME280.py +0 -36
- toolkit/dashboard_apps/AirQualityDHT22_CO2.py +0 -36
- toolkit/lib/file_extensions.py +0 -16
- toolkit/simulator_lib/__pycache__/sim_console.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/sim_console.cpython-38.pyc +0 -0
- toolkit/simulator_lib/__pycache__/sim_console.cpython-39.pyc +0 -0
- toolkit/workspace/precompiled/IO_esp32s3.mpy +0 -0
- toolkit/workspace/precompiled/IO_m5stamp.mpy +0 -0
- toolkit/workspace/precompiled/IO_qtpy.mpy +0 -0
- toolkit/workspace/precompiled/IO_rp2.mpy +0 -0
- toolkit/workspace/precompiled/IO_tinypico.mpy +0 -0
- toolkit/workspace/precompiled/LM_L298N_DCmotor.mpy +0 -0
- toolkit/workspace/precompiled/LM_OV2640.mpy +0 -0
- toolkit/workspace/precompiled/LM_catgame.py +0 -75
- toolkit/workspace/precompiled/LM_cct.mpy +0 -0
- toolkit/workspace/precompiled/LM_dashboard_be.py +0 -37
- toolkit/workspace/precompiled/LM_demo.py +0 -97
- toolkit/workspace/precompiled/LM_dimmer.mpy +0 -0
- toolkit/workspace/precompiled/LM_distance.mpy +0 -0
- toolkit/workspace/precompiled/LM_espnow.py +0 -23
- toolkit/workspace/precompiled/LM_genIO.mpy +0 -0
- toolkit/workspace/precompiled/LM_i2s_mic.mpy +0 -0
- toolkit/workspace/precompiled/LM_intercon.mpy +0 -0
- toolkit/workspace/precompiled/LM_keychain.mpy +0 -0
- toolkit/workspace/precompiled/LM_light_sensor.mpy +0 -0
- toolkit/workspace/precompiled/LM_lmpacman.mpy +0 -0
- toolkit/workspace/precompiled/LM_neoeffects.mpy +0 -0
- toolkit/workspace/precompiled/LM_neopixel.mpy +0 -0
- toolkit/workspace/precompiled/LM_oled_ui.mpy +0 -0
- toolkit/workspace/precompiled/LM_oledui.mpy +0 -0
- toolkit/workspace/precompiled/LM_pet_feeder.py +0 -78
- toolkit/workspace/precompiled/LM_ph_sensor.py +0 -51
- toolkit/workspace/precompiled/LM_presence.mpy +0 -0
- toolkit/workspace/precompiled/LM_rest.mpy +0 -0
- toolkit/workspace/precompiled/LM_rgb.mpy +0 -0
- toolkit/workspace/precompiled/LM_roboarm.mpy +0 -0
- toolkit/workspace/precompiled/LM_robustness.py +0 -74
- toolkit/workspace/precompiled/LM_switch.mpy +0 -0
- toolkit/workspace/precompiled/LM_system.mpy +0 -0
- toolkit/workspace/precompiled/LM_telegram.mpy +0 -0
- toolkit/workspace/precompiled/node_config.json +0 -1
- toolkit/workspace/precompiled/reset.mpy +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_rp2.py → modules/IO_rp2.py} +0 -0
- /micrOS/source/{LM_gameOfLife.py → modules/LM_gameOfLife.py} +0 -0
- /micrOS/source/{LM_rgbcct.py → modules/LM_rgbcct.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_sound_event.py → modules/LM_sound_event.py} +0 -0
- /micrOS/source/{LM_tinyrgb.py → modules/LM_tinyrgb.py} +0 -0
- /micrOS/source/{udashboard.js → web/udashboard.js} +0 -0
- /micrOS/source/{uwidgets.js → web/uwidgets.js} +0 -0
- /micrOS/source/{uwidgets_pro.js → web/uwidgets_pro.js} +0 -0
- {micrOSDevToolKit-2.9.1.dist-info → microsdevtoolkit-2.26.1.dist-info/licenses}/LICENSE +0 -0
- {micrOSDevToolKit-2.9.1.dist-info → microsdevtoolkit-2.26.1.dist-info}/top_level.txt +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
- /toolkit/workspace/precompiled/{udashboard.js → web/udashboard.js} +0 -0
- /toolkit/workspace/precompiled/{uwidgets.js → web/uwidgets.js} +0 -0
- /toolkit/workspace/precompiled/{uwidgets_pro.js → web/uwidgets_pro.js} +0 -0
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Scheduler.py unit test (single 1-year execution, end-of-run metrics)
|
|
3
|
+
|
|
4
|
+
Run:
|
|
5
|
+
python3 -m unittest -v utests.test_scheduler
|
|
6
|
+
|
|
7
|
+
This:
|
|
8
|
+
- loads ../source/Scheduler.py via importlib (no package needed)
|
|
9
|
+
- stubs external imports so Scheduler.py runs untouched
|
|
10
|
+
- simulates exactly RUN_DAYS days with virtual ticks
|
|
11
|
+
- calls Scheduler.scheduler(EXEC_PERIOD_SEC) in sync with the virtual tick step
|
|
12
|
+
- validates selected tasks via counts + ΔT metrics (requested vs executed time)
|
|
13
|
+
- prints one summary at the end (no per-tick spam; progress is throttled)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import math
|
|
19
|
+
import os
|
|
20
|
+
import sys
|
|
21
|
+
import types
|
|
22
|
+
import unittest
|
|
23
|
+
import importlib.util
|
|
24
|
+
from dataclasses import dataclass
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Dict, Iterator, List, Optional, Tuple
|
|
27
|
+
|
|
28
|
+
# =============================================================================
|
|
29
|
+
# EDIT HERE: generic test tasks
|
|
30
|
+
# =============================================================================
|
|
31
|
+
# Each entry becomes a cron task: "<time_spec>!<lm_name>"
|
|
32
|
+
# time_spec formats:
|
|
33
|
+
# WD:H:M:S (WD can be '*', 0..6, or ranges like 0-4)
|
|
34
|
+
# sunrise / sunset / sunrise+30 / sunset-15 (uses SUN_TIME mapping)
|
|
35
|
+
#
|
|
36
|
+
# NOTE: Builtins are always present in Scheduler.scheduler():
|
|
37
|
+
# "*:3:0:0" -> suntime
|
|
38
|
+
# "*:3:5:0" -> ntp_time
|
|
39
|
+
#
|
|
40
|
+
TEST_TASKS = [
|
|
41
|
+
# Fires daily at noon
|
|
42
|
+
{"lm": "LM_NOON", "time": "*:12:0:0", "expect_count": 365},
|
|
43
|
+
|
|
44
|
+
# Weekday only at 08:30:00 (in a 365-day year starting WD=0, weekdays occur 261 times)
|
|
45
|
+
{"lm": "LM_WEEKDAY_0830", "time": "0-4:8:30:0", "expect_count": 261},
|
|
46
|
+
|
|
47
|
+
# Tag + offset example: daily at sunset-15
|
|
48
|
+
{"lm": "LM_SUNSET_MINUS_15", "time": "sunset-15", "expect_count": 365},
|
|
49
|
+
|
|
50
|
+
# Wildcard seconds example: every second during 12:00 minute (60 per day)
|
|
51
|
+
# {"lm": "LM_STAR_SEC", "time": "*:12:0:*", "expect_count": 365 * 60},
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
# One-year run config
|
|
55
|
+
RUN_DAYS = 365
|
|
56
|
+
|
|
57
|
+
# Single knob used everywhere:
|
|
58
|
+
# - scheduler sampling/tolerance window (deltasec)
|
|
59
|
+
# - execution period (how frequently scheduler() is called)
|
|
60
|
+
# - virtual time tick step
|
|
61
|
+
EXEC_PERIOD_SEC = 5 # default 5 seconds
|
|
62
|
+
|
|
63
|
+
# Provide sunrise/sunset times for tag resolution (Sun.TIME)
|
|
64
|
+
SUN_TIME = {"sunrise": (6, 0, 0), "sunset": (18, 0, 0)}
|
|
65
|
+
|
|
66
|
+
# If set, prints extra details about each task summary
|
|
67
|
+
VERBOSE_SUMMARY = os.environ.get("SCHED_TEST_SUMMARY_VERBOSE", "0") == "1"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# =============================================================================
|
|
71
|
+
# Internal: stubs + module loader
|
|
72
|
+
# =============================================================================
|
|
73
|
+
|
|
74
|
+
def _install_import_stubs():
|
|
75
|
+
"""Install minimal stub modules so Scheduler.py imports succeed unchanged."""
|
|
76
|
+
if "Tasks" not in sys.modules:
|
|
77
|
+
m = types.ModuleType("Tasks")
|
|
78
|
+
m.exec_lm_pipe_schedule = lambda *_a, **_k: True
|
|
79
|
+
sys.modules["Tasks"] = m
|
|
80
|
+
|
|
81
|
+
if "Debug" not in sys.modules:
|
|
82
|
+
m = types.ModuleType("Debug")
|
|
83
|
+
m.console_write = lambda *_a, **_k: None
|
|
84
|
+
m.syslog = lambda *_a, **_k: None
|
|
85
|
+
sys.modules["Debug"] = m
|
|
86
|
+
|
|
87
|
+
if "Time" not in sys.modules:
|
|
88
|
+
m = types.ModuleType("Time")
|
|
89
|
+
|
|
90
|
+
class _Sun:
|
|
91
|
+
TIME: Dict[str, Tuple[int, int, int]] = {}
|
|
92
|
+
|
|
93
|
+
m.Sun = _Sun
|
|
94
|
+
m.suntime = lambda: "stub_suntime"
|
|
95
|
+
m.ntp_time = lambda: "stub_ntp_time"
|
|
96
|
+
sys.modules["Time"] = m
|
|
97
|
+
|
|
98
|
+
if "Config" not in sys.modules:
|
|
99
|
+
m = types.ModuleType("Config")
|
|
100
|
+
m.cfgget = lambda _k: ""
|
|
101
|
+
sys.modules["Config"] = m
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _load_scheduler_module():
|
|
105
|
+
here = Path(__file__).resolve()
|
|
106
|
+
scheduler_path = (here.parent.parent / "source" / "Scheduler.py").resolve()
|
|
107
|
+
if not scheduler_path.exists():
|
|
108
|
+
raise FileNotFoundError(f"Scheduler.py not found at: {scheduler_path}")
|
|
109
|
+
|
|
110
|
+
_install_import_stubs()
|
|
111
|
+
|
|
112
|
+
module_name = "micros_scheduler_under_test_single"
|
|
113
|
+
spec = importlib.util.spec_from_file_location(module_name, str(scheduler_path))
|
|
114
|
+
if spec is None or spec.loader is None:
|
|
115
|
+
raise ImportError(f"Cannot load spec for {scheduler_path}")
|
|
116
|
+
|
|
117
|
+
mod = importlib.util.module_from_spec(spec)
|
|
118
|
+
sys.modules[module_name] = mod
|
|
119
|
+
spec.loader.exec_module(mod)
|
|
120
|
+
return mod
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# =============================================================================
|
|
124
|
+
# Virtual time
|
|
125
|
+
# =============================================================================
|
|
126
|
+
|
|
127
|
+
def virtual_localtime_iter(
|
|
128
|
+
days: int,
|
|
129
|
+
step_sec: int,
|
|
130
|
+
start_year: int = 2024,
|
|
131
|
+
start_month: int = 1,
|
|
132
|
+
start_day: int = 1,
|
|
133
|
+
start_wd: int = 0, # 0=Monday ... 6=Sunday
|
|
134
|
+
) -> Iterator[Tuple[int, int, int, int, int, int, int, int]]:
|
|
135
|
+
"""
|
|
136
|
+
Generates valid localtime() tuples:
|
|
137
|
+
(Y, M, D, H, M, S, WD, YD)
|
|
138
|
+
|
|
139
|
+
step_sec controls tick rate (must match EXEC_PERIOD_SEC).
|
|
140
|
+
"""
|
|
141
|
+
if step_sec <= 0:
|
|
142
|
+
raise ValueError("step_sec must be >= 1")
|
|
143
|
+
|
|
144
|
+
def is_leap(y: int) -> bool:
|
|
145
|
+
return (y % 4 == 0 and y % 100 != 0) or (y % 400 == 0)
|
|
146
|
+
|
|
147
|
+
mdays_common = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
|
|
148
|
+
|
|
149
|
+
def month_len(y: int, mo: int) -> int:
|
|
150
|
+
if mo == 2 and is_leap(y):
|
|
151
|
+
return 29
|
|
152
|
+
return mdays_common[mo - 1]
|
|
153
|
+
|
|
154
|
+
y = start_year
|
|
155
|
+
mo = start_month
|
|
156
|
+
d = start_day
|
|
157
|
+
wd = start_wd
|
|
158
|
+
|
|
159
|
+
yday = 0
|
|
160
|
+
for m in range(1, mo):
|
|
161
|
+
yday += month_len(y, m)
|
|
162
|
+
yday += (d - 1)
|
|
163
|
+
|
|
164
|
+
for _day_idx in range(days):
|
|
165
|
+
sec = 0
|
|
166
|
+
while sec < 86400:
|
|
167
|
+
s = sec % 60
|
|
168
|
+
mi = (sec // 60) % 60
|
|
169
|
+
h = (sec // 3600)
|
|
170
|
+
yield (y, mo, d, h, mi, s, wd, yday)
|
|
171
|
+
sec += step_sec
|
|
172
|
+
|
|
173
|
+
# Advance date by 1 day
|
|
174
|
+
yday += 1
|
|
175
|
+
wd = (wd + 1) % 7
|
|
176
|
+
d += 1
|
|
177
|
+
if d > month_len(y, mo):
|
|
178
|
+
d = 1
|
|
179
|
+
mo += 1
|
|
180
|
+
if mo > 12:
|
|
181
|
+
mo = 1
|
|
182
|
+
y += 1
|
|
183
|
+
yday = 0
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _hhmmss_to_sec(h: int, m: int, s: int) -> int:
|
|
187
|
+
return int(h) * 3600 + int(m) * 60 + int(s)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _parse_tag_with_offset(tag: str) -> Tuple[str, int]:
|
|
191
|
+
tag = tag.strip()
|
|
192
|
+
if "+" in tag:
|
|
193
|
+
base, off = tag.split("+", 1)
|
|
194
|
+
return base.strip(), int(off.strip())
|
|
195
|
+
if "-" in tag:
|
|
196
|
+
base, off = tag.split("-", 1)
|
|
197
|
+
return base.strip(), -int(off.strip())
|
|
198
|
+
return tag, 0
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _requested_hms_for_spec(time_spec: str) -> Tuple[int, int, int]:
|
|
202
|
+
"""
|
|
203
|
+
Convert 'sunset-15' or 'sunrise+30' to absolute HMS based on SUN_TIME.
|
|
204
|
+
For WD:H:M:S specs, returns H,M,S if fixed, else returns 0,0,0 (handled elsewhere).
|
|
205
|
+
"""
|
|
206
|
+
time_spec = time_spec.strip()
|
|
207
|
+
if ":" in time_spec:
|
|
208
|
+
parts = [p.strip() for p in time_spec.split(":")]
|
|
209
|
+
h = parts[1] if len(parts) > 1 else "*"
|
|
210
|
+
m = parts[2] if len(parts) > 2 else "*"
|
|
211
|
+
s = parts[3] if len(parts) > 3 else "*"
|
|
212
|
+
if h.isdigit() and m.isdigit() and s.isdigit():
|
|
213
|
+
return int(h), int(m), int(s)
|
|
214
|
+
return 0, 0, 0
|
|
215
|
+
|
|
216
|
+
base, offset_min = _parse_tag_with_offset(time_spec)
|
|
217
|
+
h, m, s = SUN_TIME[base]
|
|
218
|
+
total_min = (h * 60 + m) + offset_min
|
|
219
|
+
total_min %= 1440
|
|
220
|
+
return int(total_min // 60), int(total_min % 60), int(s)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
# =============================================================================
|
|
224
|
+
# Metrics aggregator (no per-event storage)
|
|
225
|
+
# =============================================================================
|
|
226
|
+
|
|
227
|
+
@dataclass
|
|
228
|
+
class Agg:
|
|
229
|
+
count: int = 0
|
|
230
|
+
pass_count: int = 0
|
|
231
|
+
min_dt: int = 0
|
|
232
|
+
max_dt: int = 0
|
|
233
|
+
sum_dt: int = 0
|
|
234
|
+
sumsq_dt: int = 0
|
|
235
|
+
|
|
236
|
+
def add(self, dt: int, tol: int):
|
|
237
|
+
if self.count == 0:
|
|
238
|
+
self.min_dt = dt
|
|
239
|
+
self.max_dt = dt
|
|
240
|
+
else:
|
|
241
|
+
self.min_dt = min(self.min_dt, dt)
|
|
242
|
+
self.max_dt = max(self.max_dt, dt)
|
|
243
|
+
|
|
244
|
+
self.count += 1
|
|
245
|
+
self.sum_dt += dt
|
|
246
|
+
self.sumsq_dt += dt * dt
|
|
247
|
+
if -tol <= dt <= tol:
|
|
248
|
+
self.pass_count += 1
|
|
249
|
+
|
|
250
|
+
def stats(self) -> Tuple[float, float]:
|
|
251
|
+
if self.count == 0:
|
|
252
|
+
return 0.0, 0.0
|
|
253
|
+
mean = self.sum_dt / self.count
|
|
254
|
+
var = (self.sumsq_dt / self.count) - (mean * mean)
|
|
255
|
+
std = math.sqrt(var) if var > 0 else 0.0
|
|
256
|
+
return mean, std
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
# =============================================================================
|
|
260
|
+
# The test
|
|
261
|
+
# =============================================================================
|
|
262
|
+
|
|
263
|
+
class TestSchedulerSingleYearMetrics(unittest.TestCase):
|
|
264
|
+
|
|
265
|
+
@classmethod
|
|
266
|
+
def setUpClass(cls):
|
|
267
|
+
cls.S = _load_scheduler_module()
|
|
268
|
+
|
|
269
|
+
def setUp(self):
|
|
270
|
+
self.S.LAST_CRON_TASKS.clear()
|
|
271
|
+
|
|
272
|
+
def test_one_year_metrics_and_counts(self):
|
|
273
|
+
S = self.S
|
|
274
|
+
|
|
275
|
+
# Build cron_data string from TEST_TASKS
|
|
276
|
+
cron_data = ";".join(f"{t['time']}!{t['lm']}" for t in TEST_TASKS)
|
|
277
|
+
|
|
278
|
+
aggs: Dict[str, Agg] = {
|
|
279
|
+
"BUILTIN:suntime": Agg(),
|
|
280
|
+
"BUILTIN:ntp_time": Agg(),
|
|
281
|
+
}
|
|
282
|
+
expected_counts: Dict[str, int] = {
|
|
283
|
+
"BUILTIN:suntime": RUN_DAYS,
|
|
284
|
+
"BUILTIN:ntp_time": RUN_DAYS,
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
time_spec_by_lm: Dict[str, str] = {}
|
|
288
|
+
for t in TEST_TASKS:
|
|
289
|
+
key = f"LM:{t['lm']}"
|
|
290
|
+
aggs[key] = Agg()
|
|
291
|
+
expected_counts[key] = int(t["expect_count"])
|
|
292
|
+
time_spec_by_lm[t["lm"]] = str(t["time"])
|
|
293
|
+
|
|
294
|
+
# Patch Scheduler dependencies in-module
|
|
295
|
+
S.console_write = lambda *_a, **_k: None
|
|
296
|
+
S.syslog = lambda *_a, **_k: None
|
|
297
|
+
S.cfgget = lambda k: cron_data if k == "crontasks" else ""
|
|
298
|
+
|
|
299
|
+
class _SunObj:
|
|
300
|
+
TIME = dict(SUN_TIME)
|
|
301
|
+
|
|
302
|
+
S.Sun = _SunObj
|
|
303
|
+
|
|
304
|
+
current_localtime: Optional[Tuple[int, int, int, int, int, int, int, int]] = None
|
|
305
|
+
|
|
306
|
+
def fake_localtime():
|
|
307
|
+
return current_localtime # type: ignore[return-value]
|
|
308
|
+
|
|
309
|
+
S.localtime = fake_localtime
|
|
310
|
+
|
|
311
|
+
def _record(key: str, req_h: int, req_m: int, req_s: int):
|
|
312
|
+
nonlocal current_localtime
|
|
313
|
+
assert current_localtime is not None
|
|
314
|
+
_, _, _day, ah, am, asec, _wd, _yd = current_localtime
|
|
315
|
+
|
|
316
|
+
act_sec = _hhmmss_to_sec(ah, am, asec)
|
|
317
|
+
req_sec = _hhmmss_to_sec(req_h, req_m, req_s)
|
|
318
|
+
dt = act_sec - req_sec
|
|
319
|
+
aggs[key].add(dt, tol=EXEC_PERIOD_SEC)
|
|
320
|
+
|
|
321
|
+
def fake_suntime():
|
|
322
|
+
_record("BUILTIN:suntime", 3, 0, 0)
|
|
323
|
+
return "sun ok"
|
|
324
|
+
|
|
325
|
+
def fake_ntp_time():
|
|
326
|
+
_record("BUILTIN:ntp_time", 3, 5, 0)
|
|
327
|
+
return "ntp ok"
|
|
328
|
+
|
|
329
|
+
S.suntime = fake_suntime
|
|
330
|
+
S.ntp_time = fake_ntp_time
|
|
331
|
+
|
|
332
|
+
def fake_exec_lm_pipe_schedule(cmd: str):
|
|
333
|
+
cmd = str(cmd).strip()
|
|
334
|
+
time_spec = time_spec_by_lm.get(cmd, "")
|
|
335
|
+
|
|
336
|
+
if ":" in time_spec:
|
|
337
|
+
parts = [p.strip() for p in time_spec.split(":")]
|
|
338
|
+
h_spec = parts[1] if len(parts) > 1 else "*"
|
|
339
|
+
m_spec = parts[2] if len(parts) > 2 else "*"
|
|
340
|
+
s_spec = parts[3] if len(parts) > 3 else "*"
|
|
341
|
+
|
|
342
|
+
assert current_localtime is not None
|
|
343
|
+
_, _, _day, ah, am, asec, _wd, _yd = current_localtime
|
|
344
|
+
|
|
345
|
+
req_h = ah if h_spec == "*" else int(h_spec)
|
|
346
|
+
req_m = am if m_spec == "*" else int(m_spec)
|
|
347
|
+
req_s = asec if s_spec == "*" else int(s_spec)
|
|
348
|
+
_record(f"LM:{cmd}", req_h, req_m, req_s)
|
|
349
|
+
else:
|
|
350
|
+
req_h, req_m, req_s = _requested_hms_for_spec(time_spec)
|
|
351
|
+
_record(f"LM:{cmd}", req_h, req_m, req_s)
|
|
352
|
+
|
|
353
|
+
return True
|
|
354
|
+
|
|
355
|
+
S.exec_lm_pipe_schedule = fake_exec_lm_pipe_schedule
|
|
356
|
+
|
|
357
|
+
# Run exactly one year, tick + scheduler call step = EXEC_PERIOD_SEC
|
|
358
|
+
t_iter = virtual_localtime_iter(days=RUN_DAYS, step_sec=EXEC_PERIOD_SEC)
|
|
359
|
+
|
|
360
|
+
# Progressbar throttling (avoid printing millions of times)
|
|
361
|
+
last_percent_int = -1
|
|
362
|
+
|
|
363
|
+
print(f"\n== RUN CRON-SCHEDULER TEST {RUN_DAYS} days")
|
|
364
|
+
for lt in t_iter:
|
|
365
|
+
# Progressbar (based on yday + fraction of day)
|
|
366
|
+
progressbar_width: int = 50
|
|
367
|
+
yd = lt[7] # 0-based day index
|
|
368
|
+
sec_in_day = (lt[3] * 3600) + (lt[4] * 60) + lt[5]
|
|
369
|
+
percent: float = (yd + (sec_in_day / 86400.0)) / float(RUN_DAYS)
|
|
370
|
+
if percent < 0.0:
|
|
371
|
+
percent = 0.0
|
|
372
|
+
elif percent > 1.0:
|
|
373
|
+
percent = 1.0
|
|
374
|
+
|
|
375
|
+
percent_int = int(percent * 100)
|
|
376
|
+
if percent_int != last_percent_int:
|
|
377
|
+
last_percent_int = percent_int
|
|
378
|
+
filled = int(progressbar_width * percent)
|
|
379
|
+
progressbar: str = f"{'|' * filled}{' ' * (progressbar_width - filled)}"
|
|
380
|
+
print(f"({EXEC_PERIOD_SEC} sec timer): {lt}\t|{progressbar}| {percent_int} %", end="\r")
|
|
381
|
+
|
|
382
|
+
# RUN:
|
|
383
|
+
current_localtime = lt
|
|
384
|
+
S.scheduler(EXEC_PERIOD_SEC)
|
|
385
|
+
|
|
386
|
+
# ---- Final verdict summary
|
|
387
|
+
calls = RUN_DAYS * (86400 // EXEC_PERIOD_SEC)
|
|
388
|
+
|
|
389
|
+
lines: List[str] = []
|
|
390
|
+
lines.append("=== Scheduler 1-year metrics ===")
|
|
391
|
+
lines.append(f"days={RUN_DAYS}, exec_period={EXEC_PERIOD_SEC}s, calls~={calls}, tol=±{EXEC_PERIOD_SEC}s")
|
|
392
|
+
lines.append(f"cron_data='{cron_data}'")
|
|
393
|
+
lines.append("")
|
|
394
|
+
|
|
395
|
+
any_fail = False
|
|
396
|
+
|
|
397
|
+
for key in sorted(aggs.keys()):
|
|
398
|
+
agg = aggs[key]
|
|
399
|
+
exp = expected_counts.get(key, None)
|
|
400
|
+
mean, std = agg.stats()
|
|
401
|
+
pass_rate = (agg.pass_count / agg.count * 100.0) if agg.count else 0.0
|
|
402
|
+
|
|
403
|
+
lines.append(
|
|
404
|
+
f"{key:22s} "
|
|
405
|
+
f"count={agg.count:6d} exp={exp if exp is not None else '-':6} "
|
|
406
|
+
f"ΔT[min/avg/max]={agg.min_dt:+4d}/{mean:+6.2f}/{agg.max_dt:+4d} s "
|
|
407
|
+
f"std={std:5.2f} "
|
|
408
|
+
f"pass={agg.pass_count:6d} ({pass_rate:6.2f}%)"
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
if exp is not None and agg.count != exp:
|
|
412
|
+
any_fail = True
|
|
413
|
+
if agg.count and agg.pass_count != agg.count:
|
|
414
|
+
any_fail = True
|
|
415
|
+
|
|
416
|
+
summary = "\n".join(lines)
|
|
417
|
+
print("\n" + summary)
|
|
418
|
+
|
|
419
|
+
if any_fail:
|
|
420
|
+
mismatches = []
|
|
421
|
+
for key in sorted(aggs.keys()):
|
|
422
|
+
agg = aggs[key]
|
|
423
|
+
exp = expected_counts.get(key, None)
|
|
424
|
+
if exp is not None and agg.count != exp:
|
|
425
|
+
mismatches.append(f"{key}: expected count {exp}, got {agg.count}")
|
|
426
|
+
if agg.count and agg.pass_count != agg.count:
|
|
427
|
+
mismatches.append(f"{key}: {agg.count - agg.pass_count} executions outside ±{EXEC_PERIOD_SEC}s tolerance")
|
|
428
|
+
|
|
429
|
+
self.fail("Scheduler validation failed:\n" + "\n".join(mismatches) + "\n\n" + summary)
|
|
430
|
+
|
|
431
|
+
self.assertTrue(True)
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
if __name__ == "__main__":
|
|
435
|
+
unittest.main(verbosity=2)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
import sys
|
|
4
4
|
import os
|
|
5
|
+
import re
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
if len(sys.argv) > 1 and sys.argv[1] in ['light', '--light']:
|
|
@@ -10,8 +11,9 @@ if len(sys.argv) > 1 and sys.argv[1] in ['light', '--light']:
|
|
|
10
11
|
else:
|
|
11
12
|
# INSTALL OPTIONAL DEPENDENCIES - PIP HACK
|
|
12
13
|
from toolkit.lib import pip_package_installer as pip_install
|
|
13
|
-
pip_install.install_optional_dependencies(['PyQt5', 'opencv-python', 'PyAudio', 'mpy-cross==1.
|
|
14
|
-
|
|
14
|
+
pip_install.install_optional_dependencies(['PyQt5', 'opencv-python', 'PyAudio', 'mpy-cross==1.24.1.post2', 'matplotlib'])
|
|
15
|
+
if sys.platform.startswith("win"):
|
|
16
|
+
pip_install.install_optional_dependencies(['pyreadline3'])
|
|
15
17
|
|
|
16
18
|
# NORMAL CODE ...
|
|
17
19
|
MYPATH = os.path.dirname(__file__)
|
|
@@ -34,6 +36,11 @@ try:
|
|
|
34
36
|
except Exception as e:
|
|
35
37
|
print(f"Optional dependency missing in macroScript: {e}")
|
|
36
38
|
macroScript = None
|
|
39
|
+
try:
|
|
40
|
+
from toolkit.WebRepl import open_webrepl
|
|
41
|
+
except Exception as e:
|
|
42
|
+
print(f"Optional dependency missing in WebRepl: {e}")
|
|
43
|
+
open_webrepl = None
|
|
37
44
|
|
|
38
45
|
|
|
39
46
|
def arg_parse():
|
|
@@ -69,7 +76,10 @@ def arg_parse():
|
|
|
69
76
|
dev_group.add_argument("-gw", "--gateway", action="store_true", help="Start micrOS Gateway rest-api server, Env. vars: API_AUTH='username:password' (optional), GATEWAYIP needed for container deployment only.")
|
|
70
77
|
dev_group.add_argument("-v", "--version", action="store_true", help="Get micrOS version - repo + connected device.")
|
|
71
78
|
dev_group.add_argument("-lint", "--linter", action="store_true", help="Run micrOS system linter (pylint+)")
|
|
79
|
+
dev_group.add_argument("-webrepl", "--open_webrepl", action="store_true", help="(beta) Open webrepl in default browser, micropython repl + file transfers (built-in)")
|
|
72
80
|
dev_group.add_argument("--light", action="store_true", help="Skip optional dependency deployments (low level param: add this as first argument always)")
|
|
81
|
+
dev_group.add_argument("-ut", "--unittest", action="store_true", help="Run micrOS unit tests - for development")
|
|
82
|
+
|
|
73
83
|
|
|
74
84
|
toolkit_group = parser.add_argument_group("Toolkit development")
|
|
75
85
|
toolkit_group.add_argument("--dummy", action="store_true", help="Skip subshell executions - for API logic test.")
|
|
@@ -106,7 +116,13 @@ def install(api_obj):
|
|
|
106
116
|
|
|
107
117
|
def connect(args=None):
|
|
108
118
|
if args is not None and len(args) != 0:
|
|
109
|
-
|
|
119
|
+
# REPARSE IINPUT DUE TO EMBEDDED INPUT PARSER... HACK
|
|
120
|
+
# extract quoted content (without quotes)
|
|
121
|
+
extracted = re.findall(r'(["\'])(.*?)\1', args)
|
|
122
|
+
extracted = [text for _, text in extracted]
|
|
123
|
+
# remove quoted parts from original string
|
|
124
|
+
cleaned = re.sub(r'(["\']).*?\1', '', args)
|
|
125
|
+
arg_list = cleaned.split() + extracted
|
|
110
126
|
socketClient.run(arg_list=arg_list)
|
|
111
127
|
else:
|
|
112
128
|
socketClient.run(arg_list=[])
|
|
@@ -209,6 +225,13 @@ def update_pip_package():
|
|
|
209
225
|
return True if out == 0 else False
|
|
210
226
|
|
|
211
227
|
|
|
228
|
+
def run_unit_tests():
|
|
229
|
+
"""Run micrOS Unit tests under micrOS/utests/*"""
|
|
230
|
+
import unittest
|
|
231
|
+
suite = unittest.defaultTestLoader.discover("micrOS/utests")
|
|
232
|
+
unittest.TextTestRunner(verbosity=2).run(suite)
|
|
233
|
+
|
|
234
|
+
|
|
212
235
|
if __name__ == "__main__":
|
|
213
236
|
# Arg parse
|
|
214
237
|
cmd_args = arg_parse()
|
|
@@ -292,6 +315,10 @@ if __name__ == "__main__":
|
|
|
292
315
|
if micrOSlint is not None:
|
|
293
316
|
sys.exit(micrOSlint.main(verbose=False))
|
|
294
317
|
|
|
318
|
+
if cmd_args.open_webrepl:
|
|
319
|
+
if open_webrepl is not None:
|
|
320
|
+
open_webrepl()
|
|
321
|
+
|
|
295
322
|
if cmd_args.macro:
|
|
296
323
|
if macroScript is None:
|
|
297
324
|
print("macroScript not available :(")
|
|
@@ -299,4 +326,7 @@ if __name__ == "__main__":
|
|
|
299
326
|
executor = macroScript.Executor()
|
|
300
327
|
executor.run_micro_script(cmd_args.macro)
|
|
301
328
|
|
|
329
|
+
if cmd_args.unittest:
|
|
330
|
+
run_unit_tests()
|
|
331
|
+
|
|
302
332
|
sys.exit(0)
|