micrOSDevToolKit 2.1.5__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/.DS_Store +0 -0
- 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-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 +191 -151
- micrOS/source/Auth.py +37 -0
- micrOS/source/Common.py +376 -102
- micrOS/source/Config.py +55 -25
- micrOS/source/Debug.py +54 -193
- micrOS/source/Espnow.py +404 -0
- micrOS/source/Files.py +207 -0
- micrOS/source/Hooks.py +88 -16
- micrOS/source/InterConnect.py +130 -46
- micrOS/source/Interrupts.py +8 -8
- micrOS/source/Logger.py +131 -0
- micrOS/source/Network.py +41 -21
- micrOS/source/Notify.py +74 -198
- micrOS/source/Pacman.py +326 -0
- micrOS/source/Scheduler.py +18 -55
- micrOS/source/Server.py +84 -217
- micrOS/source/Shell.py +103 -93
- micrOS/source/Tasks.py +239 -173
- micrOS/source/Time.py +21 -22
- micrOS/source/Types.py +89 -54
- micrOS/source/Web.py +485 -0
- 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/helpers.py +132 -0
- micrOS/source/micrOS.py +25 -21
- micrOS/source/micrOSloader.py +14 -23
- micrOS/source/microIO.py +94 -57
- toolkit/simulator_lib/LP_darwin.py → micrOS/source/modules/IO_esp32.py +22 -11
- micrOS/source/{IO_esp32c3.py → modules/IO_esp32c3.py} +6 -1
- micrOS/source/modules/IO_esp32c6.py +38 -0
- micrOS/source/{IO_esp32s2.py → modules/IO_esp32s2.py} +6 -1
- micrOS/source/{IO_esp32s3.py → modules/IO_esp32s3.py} +43 -2
- micrOS/source/modules/IO_m5stamp.py +86 -0
- micrOS/source/{IO_qtpy.py → modules/IO_qtpy.py} +28 -18
- micrOS/source/{IO_tinypico.py → modules/IO_tinypico.py} +48 -3
- micrOS/source/modules/LM_L298N.py +161 -0
- {toolkit/workspace/precompiled → micrOS/source/modules}/LM_L9110_DCmotor.py +4 -4
- micrOS/source/{LM_OV2640.py → modules/LM_OV2640.py} +53 -42
- micrOS/source/{LM_VL53L0X.py → modules/LM_VL53L0X.py} +5 -5
- micrOS/source/{LM_aht10.py → modules/LM_aht10.py} +12 -4
- micrOS/source/{LM_bme280.py → modules/LM_bme280.py} +13 -25
- micrOS/source/{LM_buzzer.py → modules/LM_buzzer.py} +42 -40
- micrOS/source/{LM_cct.py → modules/LM_cct.py} +22 -27
- micrOS/source/modules/LM_cluster.py +255 -0
- micrOS/source/{LM_co2.py → modules/LM_co2.py} +13 -6
- micrOS/source/{LM_dht11.py → modules/LM_dht11.py} +13 -29
- micrOS/source/{LM_dht22.py → modules/LM_dht22.py} +13 -28
- micrOS/source/{LM_dimmer.py → modules/LM_dimmer.py} +19 -16
- micrOS/source/modules/LM_distance.py +135 -0
- micrOS/source/{LM_ds18.py → modules/LM_ds18.py} +12 -4
- micrOS/source/{LM_esp32.py → modules/LM_esp32.py} +16 -4
- micrOS/source/modules/LM_espnow.py +53 -0
- micrOS/source/modules/LM_fileserver.py +265 -0
- micrOS/source/{LM_gameOfLife.py → modules/LM_gameOfLife.py} +5 -5
- micrOS/source/{LM_genIO.py → modules/LM_genIO.py} +49 -35
- micrOS/source/modules/LM_haptic.py +111 -0
- micrOS/source/modules/LM_i2c.py +61 -0
- micrOS/source/{LM_i2s_mic.py → modules/LM_i2s_mic.py} +20 -23
- micrOS/source/{LM_ld2410.py → modules/LM_ld2410.py} +3 -3
- micrOS/source/{LM_light_sensor.py → modules/LM_light_sensor.py} +22 -26
- 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} +26 -31
- micrOS/source/{LM_oled.py → modules/LM_oled.py} +28 -20
- micrOS/source/{LM_oled_sh1106.py → modules/LM_oled_sh1106.py} +28 -24
- micrOS/source/{LM_oled_ui.py → modules/LM_oled_ui.py} +132 -174
- micrOS/source/modules/LM_pacman.py +320 -0
- micrOS/source/{LM_presence.py → modules/LM_presence.py} +24 -36
- micrOS/source/modules/LM_qmi8658.py +204 -0
- micrOS/source/{LM_rencoder.py → modules/LM_rencoder.py} +40 -11
- micrOS/source/modules/LM_rest.py +81 -0
- micrOS/source/{LM_rgb.py → modules/LM_rgb.py} +25 -34
- micrOS/source/{LM_rgbcct.py → modules/LM_rgbcct.py} +5 -5
- micrOS/source/{LM_roboarm.py → modules/LM_roboarm.py} +37 -45
- micrOS/source/modules/LM_robustness.py +137 -0
- micrOS/source/{LM_rp2w.py → modules/LM_rp2w.py} +3 -0
- micrOS/source/{LM_sdcard.py → modules/LM_sdcard.py} +3 -0
- micrOS/source/{LM_servo.py → modules/LM_servo.py} +4 -4
- micrOS/source/modules/LM_sound_event.py +751 -0
- micrOS/source/{LM_stepper.py → modules/LM_stepper.py} +8 -8
- micrOS/source/{LM_switch.py → modules/LM_switch.py} +21 -18
- micrOS/source/{LM_system.py → modules/LM_system.py} +96 -59
- micrOS/source/modules/LM_tcs3472.py +187 -0
- micrOS/source/modules/LM_telegram.py +388 -0
- micrOS/source/modules/LM_trackball.py +287 -0
- micrOS/source/modules/LM_veml7700.py +159 -0
- micrOS/source/modules/LM_web.py +38 -0
- micrOS/source/urequests.py +204 -91
- {toolkit/workspace/precompiled → micrOS/source/web}/dashboard.html +9 -4
- micrOS/source/web/editor.js +440 -0
- micrOS/source/web/filesui.html +178 -0
- micrOS/source/web/filesui.js +338 -0
- micrOS/source/{index.html → web/index.html} +44 -2
- micrOS/source/web/uapi.js +103 -0
- micrOS/source/web/udashboard.js +129 -0
- micrOS/source/web/ustyle.css +55 -0
- micrOS/source/web/uwidgets.js +172 -0
- micrOS/source/web/uwidgets_pro.js +99 -0
- micrOS/utests/__init__.py +0 -0
- micrOS/utests/test_scheduler.py +435 -0
- {micrOSDevToolKit-2.1.5.data → microsdevtoolkit-2.26.1.data}/scripts/devToolKit.py +47 -4
- {micrOSDevToolKit-2.1.5.dist-info → microsdevtoolkit-2.26.1.dist-info}/METADATA +392 -279
- microsdevtoolkit-2.26.1.dist-info/RECORD +396 -0
- {micrOSDevToolKit-2.1.5.dist-info → microsdevtoolkit-2.26.1.dist-info}/WHEEL +1 -1
- toolkit/DevEnvCompile.py +63 -33
- toolkit/DevEnvOTA.py +72 -22
- toolkit/DevEnvUSB.py +147 -77
- toolkit/Gateway.py +9 -9
- toolkit/LM_to_compile.dat +12 -4
- toolkit/MicrOSDevEnv.py +129 -51
- toolkit/WebRepl.py +73 -0
- toolkit/dashboard_apps/BackupRestore.py +171 -0
- 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 +2 -7
- 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 +219 -117
- toolkit/dashboard_apps/Template_app.py +12 -19
- 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 +6 -5
- 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 +371 -0
- toolkit/lib/micrOSClient.py +124 -51
- toolkit/lib/micrOSClientHistory.py +156 -0
- toolkit/lib/pip_package_installer.py +31 -4
- toolkit/micrOSdashboard.py +16 -21
- toolkit/micrOSlint.py +28 -10
- toolkit/simulator_lib/.DS_Store +0 -0
- micrOS/source/IO_esp32.py → toolkit/simulator_lib/IO_darwin.py +3 -0
- 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__/camera.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/camera.py +84 -0
- toolkit/simulator_lib/dht.py +1 -1
- toolkit/simulator_lib/framebuf.py +49 -1
- toolkit/simulator_lib/machine.py +32 -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 +138 -46
- toolkit/simulator_lib/uasyncio.py +34 -3
- toolkit/simulator_lib/uos.py +147 -0
- toolkit/simulator_lib/urandom.py +4 -0
- toolkit/simulator_lib/usocket.py +5 -1
- toolkit/simulator_lib/view01.jpg +0 -0
- toolkit/simulator_lib/view02.jpg +0 -0
- toolkit/socketClient.py +43 -23
- toolkit/user_data/webhooks/generic.py +1 -1
- toolkit/user_data/webhooks/macro.py +44 -0
- toolkit/user_data/webhooks/template.macro +20 -0
- 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/modules/IO_esp32.mpy +0 -0
- toolkit/workspace/precompiled/modules/IO_esp32c3.mpy +0 -0
- toolkit/workspace/precompiled/modules/IO_esp32c6.mpy +0 -0
- toolkit/workspace/precompiled/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 +4 -4
- toolkit/workspace/precompiled/modules/LM_OV2640.mpy +0 -0
- toolkit/workspace/precompiled/{LM_VL53L0X.py → modules/LM_VL53L0X.py} +5 -5
- toolkit/workspace/precompiled/modules/LM_aht10.mpy +0 -0
- toolkit/workspace/precompiled/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/modules/LM_co2.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_dht11.mpy +0 -0
- toolkit/workspace/precompiled/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/modules/LM_ds18.mpy +0 -0
- toolkit/workspace/precompiled/{LM_esp32.py → modules/LM_esp32.py} +16 -4
- 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/modules/LM_haptic.mpy +0 -0
- toolkit/workspace/precompiled/modules/LM_i2c.py +61 -0
- 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/modules/LM_oled.mpy +0 -0
- toolkit/workspace/precompiled/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} +40 -11
- 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_rp2w.py → modules/LM_rp2w.py} +3 -0
- toolkit/workspace/precompiled/{LM_sdcard.py → modules/LM_sdcard.py} +3 -0
- toolkit/workspace/precompiled/{LM_servo.mpy → modules/LM_servo.mpy} +0 -0
- toolkit/workspace/precompiled/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/modules/LM_trackball.mpy +0 -0
- toolkit/workspace/precompiled/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 +9 -4
- toolkit/workspace/precompiled/web/editor.js +440 -0
- toolkit/workspace/precompiled/web/filesui.html +178 -0
- toolkit/workspace/precompiled/web/filesui.js +338 -0
- toolkit/workspace/precompiled/{index.html → web/index.html} +44 -2
- toolkit/workspace/precompiled/web/uapi.js +103 -0
- toolkit/workspace/precompiled/web/udashboard.js +129 -0
- toolkit/workspace/precompiled/web/ustyle.css +55 -0
- toolkit/workspace/precompiled/web/uwidgets.js +172 -0
- toolkit/workspace/precompiled/web/uwidgets_pro.js +99 -0
- env/driver_cp210x/CH34XSER_MAC/CH34X_DRV_INSTALL_INSTRUCTIONS.pdf +0 -0
- env/driver_cp210x/CH34XSER_MAC/CH34xVCPDriver.pkg +0 -0
- micrOS/micropython/esp32-20231005-v1.21.0.bin +0 -0
- micrOS/micropython/esp32c3-GENERIC-20240105-v1.22.1.bin +0 -0
- micrOS/micropython/esp32c3-GENERIC-20240222-v1.22.2.bin +0 -0
- micrOS/micropython/esp32s2-GENERIC-20240105-v1.22.1.bin +0 -0
- micrOS/micropython/esp32s2-LOLIN_MINI-20220618-v1.19.1.bin +0 -0
- micrOS/micropython/esp32s3-GENERIC-20240105-v1.22.1.bin +0 -0
- micrOS/micropython/esp32s3_spiram_oct-20231005-v1.21.0.bin +0 -0
- micrOS/micropython/rpi-pico-w-20231005-v1.21.0.uf2 +0 -0
- micrOS/micropython/tinypico-20231005-v1.21.0.bin +0 -0
- micrOS/micropython/tinypico-usbc-UM-20240105-v1.22.1.bin +0 -0
- micrOS/source/LM_L298N_DCmotor.py +0 -86
- micrOS/source/LM_catgame.py +0 -74
- micrOS/source/LM_dashboard_be.py +0 -37
- micrOS/source/LM_demo.py +0 -85
- micrOS/source/LM_distance.py +0 -88
- micrOS/source/LM_i2c.py +0 -44
- micrOS/source/LM_intercon.py +0 -57
- micrOS/source/LM_keychain.py +0 -318
- micrOS/source/LM_lmpacman.py +0 -126
- micrOS/source/LM_neoeffects.py +0 -327
- micrOS/source/LM_pet_feeder.py +0 -76
- micrOS/source/LM_ph_sensor.py +0 -51
- micrOS/source/LM_rest.py +0 -40
- micrOS/source/LM_robustness.py +0 -73
- micrOS/source/LM_telegram.py +0 -96
- micrOS/source/reset.py +0 -11
- micrOS/source/uapi.js +0 -76
- micrOS/source/udashboard.js +0 -137
- micrOS/source/ustyle.css +0 -28
- micrOS/source/uwidgets.js +0 -179
- micrOSDevToolKit-2.1.5.dist-info/RECORD +0 -337
- 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__/LP_darwin.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/LP_darwin.cpython-38.pyc +0 -0
- toolkit/simulator_lib/__pycache__/LP_darwin.cpython-39.pyc +0 -0
- 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_esp32.mpy +0 -0
- toolkit/workspace/precompiled/IO_esp32c3.mpy +0 -0
- toolkit/workspace/precompiled/IO_esp32s2.mpy +0 -0
- toolkit/workspace/precompiled/IO_esp32s3.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_aht10.mpy +0 -0
- toolkit/workspace/precompiled/LM_bme280.mpy +0 -0
- toolkit/workspace/precompiled/LM_catgame.py +0 -74
- toolkit/workspace/precompiled/LM_cct.mpy +0 -0
- toolkit/workspace/precompiled/LM_co2.mpy +0 -0
- toolkit/workspace/precompiled/LM_dashboard_be.py +0 -37
- toolkit/workspace/precompiled/LM_demo.py +0 -85
- toolkit/workspace/precompiled/LM_dht11.mpy +0 -0
- toolkit/workspace/precompiled/LM_dht22.mpy +0 -0
- toolkit/workspace/precompiled/LM_dimmer.mpy +0 -0
- toolkit/workspace/precompiled/LM_distance.py +0 -88
- toolkit/workspace/precompiled/LM_ds18.mpy +0 -0
- toolkit/workspace/precompiled/LM_genIO.mpy +0 -0
- toolkit/workspace/precompiled/LM_i2c.py +0 -44
- 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.mpy +0 -0
- toolkit/workspace/precompiled/LM_oled_sh1106.mpy +0 -0
- toolkit/workspace/precompiled/LM_oled_ui.mpy +0 -0
- toolkit/workspace/precompiled/LM_pet_feeder.py +0 -76
- 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 -73
- 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/reset.mpy +0 -0
- toolkit/workspace/precompiled/uapi.js +0 -76
- toolkit/workspace/precompiled/udashboard.js +0 -137
- toolkit/workspace/precompiled/ustyle.css +0 -28
- toolkit/workspace/precompiled/uwidgets.js +0 -179
- /toolkit/user_data/node_config_archive/.include → /micrOS/source/config/_git.keep +0 -0
- /micrOS/source/{IO_rp2.py → modules/IO_rp2.py} +0 -0
- /micrOS/source/{LM_tinyrgb.py → modules/LM_tinyrgb.py} +0 -0
- {micrOSDevToolKit-2.1.5.dist-info → microsdevtoolkit-2.26.1.dist-info/licenses}/LICENSE +0 -0
- {micrOSDevToolKit-2.1.5.dist-info → microsdevtoolkit-2.26.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,751 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module for sound pattern recognition with an I2S microphone.
|
|
3
|
+
Pattern recognition is achieved by instance-based learning
|
|
4
|
+
(nearest neighbor algorithm) where the user can interactively
|
|
5
|
+
train the algorithm by labeling events. The training starts
|
|
6
|
+
with an empty dataset, and the user needs to label events while
|
|
7
|
+
observing system logs to see if there is a new event. After
|
|
8
|
+
recording a few events, the user may decide to turn on the
|
|
9
|
+
"auto-learn" feature to improve in-sample accuracy, however,
|
|
10
|
+
it may also result in overfitting if certain classes become
|
|
11
|
+
overrepresented.
|
|
12
|
+
|
|
13
|
+
Events may be labeled as None if there are no classes recorded, or
|
|
14
|
+
the algorithm is uncertain about which label to assign to the event.
|
|
15
|
+
Arbitrary number of classes may be created, or one can use binary
|
|
16
|
+
classification where events are labeled as '<class name 1>' or '<class name 2>'.
|
|
17
|
+
|
|
18
|
+
For the usage, execute "sound_event help" or check the section
|
|
19
|
+
"Functions for recording and clearing events from the dataset".
|
|
20
|
+
|
|
21
|
+
----------------
|
|
22
|
+
Example:
|
|
23
|
+
----------------
|
|
24
|
+
picocom --baud 115200 /dev/ttyACM0 <---- the device must be connected through USB
|
|
25
|
+
...
|
|
26
|
+
Terminal ready
|
|
27
|
+
[info] sound_event: {'class': None}
|
|
28
|
+
[info] sound_event: {'class': None}
|
|
29
|
+
[info] sound_event: {'class': None} <---- at this point the user decides to label
|
|
30
|
+
this event by: record_last_event '<label>'
|
|
31
|
+
|
|
32
|
+
[info] sound_event: event of length 8320 will be recorded as "finger_snaps"
|
|
33
|
+
<---- resulting log entry after record_last_event
|
|
34
|
+
|
|
35
|
+
[info] sound_event: {'class': "finger_snaps"}
|
|
36
|
+
<---- resulting log entry after recognizing an event
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
import LM_i2s_mic
|
|
40
|
+
import json
|
|
41
|
+
|
|
42
|
+
from microIO import pinmap_search
|
|
43
|
+
from Common import micro_task, syslog, console
|
|
44
|
+
from Types import resolve
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class Data:
|
|
48
|
+
CONTROL_TASK_TAG = 'sound_event._classifier'
|
|
49
|
+
DATASET = ''
|
|
50
|
+
EVENTS = []
|
|
51
|
+
EVENT_CALLBACKS = set()
|
|
52
|
+
AUTO_LEARN = False
|
|
53
|
+
PERFORMANCE = {}
|
|
54
|
+
DISTANCE_MATRICES = {}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
################################################################################
|
|
58
|
+
# DSP helper functions
|
|
59
|
+
################################################################################
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _amplitude_envelope(samples, frame_size):
|
|
63
|
+
"""
|
|
64
|
+
Calculate max amplitude per frame and return it as a list.
|
|
65
|
+
:param frame_size: size of the signal frame (int)
|
|
66
|
+
:param samples: list of sampled values
|
|
67
|
+
"""
|
|
68
|
+
envelope = []
|
|
69
|
+
for k in range(int(len(samples)/frame_size)):
|
|
70
|
+
amplitude = [abs(s) for s in samples[k*frame_size: min(len(samples), (k+1)*frame_size)]]
|
|
71
|
+
envelope.append(max(amplitude))
|
|
72
|
+
|
|
73
|
+
return envelope
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _rmse(samples, frame_size):
|
|
77
|
+
"""
|
|
78
|
+
Calculate root mean squared energy.
|
|
79
|
+
:param frame_size: size of the signal frame (int)
|
|
80
|
+
:param samples: list of sampled values
|
|
81
|
+
"""
|
|
82
|
+
rmse = []
|
|
83
|
+
for k in range(int(len(samples)/frame_size)):
|
|
84
|
+
rmse_frame = (sum([s**2 for s in samples[k*frame_size: min(len(samples), (k+1)*frame_size)]])/frame_size)**(0.5)
|
|
85
|
+
rmse.append(rmse_frame)
|
|
86
|
+
|
|
87
|
+
return rmse
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _zero_crossing_rate(samples, frame_size):
|
|
91
|
+
"""
|
|
92
|
+
Calculate the rate of the signal crossing the X axis.
|
|
93
|
+
:param frame_size: size of the signal frame (int)
|
|
94
|
+
:param samples: list of sampled values
|
|
95
|
+
"""
|
|
96
|
+
rates = []
|
|
97
|
+
for k in range(int(len(samples)/frame_size)):
|
|
98
|
+
rate = 0
|
|
99
|
+
for i in range(k*frame_size, min(len(samples)-1, (k+1)*frame_size-1)):
|
|
100
|
+
rate += abs(samples[i]/abs(samples[i]) - samples[i+1]/abs(samples[i+1]))
|
|
101
|
+
rates.append(0.5 * rate)
|
|
102
|
+
|
|
103
|
+
return rates
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _crest_factor(samples, frame_size):
|
|
107
|
+
"""
|
|
108
|
+
Calculate Crest factor (peak amplitude divided by RMSE).
|
|
109
|
+
:param frame_size: size of the signal frame (int)
|
|
110
|
+
:param samples: list of sampled values
|
|
111
|
+
"""
|
|
112
|
+
cf = []
|
|
113
|
+
for k in range(int(len(samples)/frame_size)):
|
|
114
|
+
peak = max(samples[k*frame_size: min(len(samples), (k+1)*frame_size)], key=lambda x: abs(x))
|
|
115
|
+
rmse_frame = (sum([s**2 for s in samples[k*frame_size: min(len(samples), (k+1)*frame_size)]])/frame_size)**(0.5)
|
|
116
|
+
cf.append(peak/rmse_frame)
|
|
117
|
+
|
|
118
|
+
return cf
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _analyze_event(samples, frame_size):
|
|
122
|
+
return {
|
|
123
|
+
'name': None,
|
|
124
|
+
'features': {
|
|
125
|
+
'ZCR': {'type': 'time-series', 'data': _zero_crossing_rate(samples,frame_size)},
|
|
126
|
+
'RMSE': {'type': 'time-series', 'data': _rmse(samples,frame_size)},
|
|
127
|
+
'envelope': {'type': 'time-series', 'data': _amplitude_envelope(samples, frame_size)},
|
|
128
|
+
'Crest factor': {'type': 'time-series', 'data': _crest_factor(samples, frame_size)},
|
|
129
|
+
'length': {'type': 'ratio', 'data': len(samples)},
|
|
130
|
+
'max': {'type': 'ratio', 'data': max([abs(s) for s in samples])}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
def _get_feature_values(event, feature):
|
|
135
|
+
return event['features'][feature]['data']
|
|
136
|
+
|
|
137
|
+
def _get_feature_names():
|
|
138
|
+
return ['ZCR', 'RMSE', 'envelope', 'Crest factor', 'length', 'max']
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
######################################################################################
|
|
142
|
+
# Functions for classification (instance-based learning)
|
|
143
|
+
# Distance between time-dependent features is measured in two steps:
|
|
144
|
+
# 1. match the alignment of the signals by maximizing cross-correlation
|
|
145
|
+
# 2. measure mean squared error on the aligned signals
|
|
146
|
+
#
|
|
147
|
+
# Distance between instances is determined by measuring the closest instance by all
|
|
148
|
+
# features separately, and selecting the most frequent class by majority voting.
|
|
149
|
+
######################################################################################
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _calculate_time_series_feature_distances(instances, i, j, distance_matrices):
|
|
153
|
+
"""
|
|
154
|
+
calculate distance matrices time-series data
|
|
155
|
+
:param instances: dict - instances read from dataset
|
|
156
|
+
:param i: int - index of the instance to compare
|
|
157
|
+
:param j: int - index of the other instance to compare
|
|
158
|
+
:param distance_matrices: dict - initialized distance matrices
|
|
159
|
+
"""
|
|
160
|
+
time_series_features = [f for f in instances[0]['features'].keys() if instances[0]['features'][f]['type'] == 'time-series']
|
|
161
|
+
|
|
162
|
+
if not time_series_features:
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
features_i = {}
|
|
166
|
+
features_j = {}
|
|
167
|
+
cross_corr = {}
|
|
168
|
+
l_f = None
|
|
169
|
+
l_g = None
|
|
170
|
+
|
|
171
|
+
for feature in time_series_features:
|
|
172
|
+
features_i[feature] = _get_feature_values(instances[i], feature)
|
|
173
|
+
features_j[feature] = _get_feature_values(instances[j], feature)
|
|
174
|
+
cross_corr[feature] = [0]
|
|
175
|
+
|
|
176
|
+
if l_f is None:
|
|
177
|
+
l_f = len(features_i[feature])
|
|
178
|
+
l_g = len(features_j[feature])
|
|
179
|
+
elif l_f != len(features_i[feature]) or l_g != len(features_j[feature]):
|
|
180
|
+
raise ValueError('Only time-series features of the same length can be computed at once!')
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
cross_corr_range_n = range(-(l_g - 1), l_f)
|
|
184
|
+
|
|
185
|
+
for n in cross_corr_range_n:
|
|
186
|
+
cross_corr_range_m = range(max(0, -n), min(l_f, l_g - n))
|
|
187
|
+
|
|
188
|
+
if len(cross_corr_range_m):
|
|
189
|
+
for feature in time_series_features:
|
|
190
|
+
cross_corr[feature].append(0)
|
|
191
|
+
|
|
192
|
+
for m in cross_corr_range_m:
|
|
193
|
+
for feature in time_series_features:
|
|
194
|
+
cross_corr[feature][-1] += features_i[feature][m] * features_j[feature][m+n]
|
|
195
|
+
|
|
196
|
+
# Best alignment of signals is averaged for all time-dependent features,
|
|
197
|
+
# thereby penalizing misalignment between features.
|
|
198
|
+
n = 0
|
|
199
|
+
for feature in time_series_features:
|
|
200
|
+
argmax_feature_corr = max(range(len(cross_corr[feature])), key=lambda x: cross_corr[feature][x])
|
|
201
|
+
best_alignment = list(cross_corr_range_n)[argmax_feature_corr]
|
|
202
|
+
n += best_alignment/len(time_series_features)
|
|
203
|
+
|
|
204
|
+
n = int(n)
|
|
205
|
+
# MSE (mean-squared error)
|
|
206
|
+
for feature in time_series_features:
|
|
207
|
+
measure = 0
|
|
208
|
+
for m in range(max(0, -n), min(l_f, l_g - n)):
|
|
209
|
+
measure += (features_i[feature][m] - features_j[feature][m + n])**2 / len(range(max(0, -n), min(l_f, l_g - n)))
|
|
210
|
+
distance_matrices[feature][i][j] = measure
|
|
211
|
+
distance_matrices[feature][j][i] = measure # Due to matrix symmetry
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _calculate_ratio_feature_distances(instances, i, j, distance_matrices):
|
|
215
|
+
"""
|
|
216
|
+
calculate distance matrices ratio-type values
|
|
217
|
+
:param instances: dict - instances read from dataset
|
|
218
|
+
:param i: int - index of the instance to compare
|
|
219
|
+
:param j: int - index of the other instance to compare
|
|
220
|
+
:param distance_matrices: dict - initialized distance matrices
|
|
221
|
+
"""
|
|
222
|
+
ratio_features = [f for f in instances[0]['features'].keys() if instances[0]['features'][f]['type'] == 'ratio']
|
|
223
|
+
|
|
224
|
+
if not ratio_features:
|
|
225
|
+
return
|
|
226
|
+
|
|
227
|
+
for feature in ratio_features:
|
|
228
|
+
distance = abs(_get_feature_values(instances[i], feature) - _get_feature_values(instances[j], feature))
|
|
229
|
+
distance_matrices[feature][i][j] = distance
|
|
230
|
+
distance_matrices[feature][j][i] = distance # Due to matrix symmetry
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _calculate_all_sound_feature_distances(instances):
|
|
234
|
+
"""
|
|
235
|
+
calculate distance matrices from scratch,
|
|
236
|
+
should only be used during initialization
|
|
237
|
+
:param instances: dict - instances read from dataset
|
|
238
|
+
"""
|
|
239
|
+
num_instances = len(instances)
|
|
240
|
+
|
|
241
|
+
if num_instances:
|
|
242
|
+
distance_matrices = {k: [] for k in set(instances[0]['features'].keys())}
|
|
243
|
+
else:
|
|
244
|
+
distance_matrices = {k: [] for k in _get_feature_names()}
|
|
245
|
+
|
|
246
|
+
for i in instances:
|
|
247
|
+
# Initialize empty distance matrices
|
|
248
|
+
for feature in distance_matrices.keys():
|
|
249
|
+
distance_matrices[feature].append([None]*num_instances)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
for i_idx in range(len(instances)):
|
|
253
|
+
for j_idx in range(len(instances)):
|
|
254
|
+
if j_idx > i_idx: # Due to matrix symmetry
|
|
255
|
+
continue
|
|
256
|
+
_calculate_time_series_feature_distances(instances, i_idx, j_idx, distance_matrices)
|
|
257
|
+
_calculate_ratio_feature_distances(instances, i_idx, j_idx, distance_matrices)
|
|
258
|
+
|
|
259
|
+
return distance_matrices
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _calculate_sound_feature_distances(instances,
|
|
263
|
+
new_instance,
|
|
264
|
+
distance_matrices_):
|
|
265
|
+
"""
|
|
266
|
+
calculate distance matrices for a new instance
|
|
267
|
+
:param instances: dict - instances read from dataset
|
|
268
|
+
:param new_instance: dict - newly recorded instance to be classified
|
|
269
|
+
:param distances: dict - arbitrary distance matrices
|
|
270
|
+
"""
|
|
271
|
+
distance_matrices = {}
|
|
272
|
+
|
|
273
|
+
for feature in distance_matrices_.keys():
|
|
274
|
+
distance_matrices[feature] = [r.copy() for r in distance_matrices_[feature]]
|
|
275
|
+
|
|
276
|
+
for i in range(len(instances)):
|
|
277
|
+
# Initialize empty columns for the new instance in the distance matrices
|
|
278
|
+
distance_matrices[feature][i].append(0)
|
|
279
|
+
|
|
280
|
+
# Add new instance after the existing ones, and create empty row in the distance matrices
|
|
281
|
+
instances.append(new_instance.copy())
|
|
282
|
+
|
|
283
|
+
for feature in distance_matrices_.keys():
|
|
284
|
+
distance_matrices[feature].append([0]*(len(instances)))
|
|
285
|
+
|
|
286
|
+
for i_idx, i in enumerate(instances):
|
|
287
|
+
_calculate_time_series_feature_distances(instances, i_idx, len(instances)-1, distance_matrices)
|
|
288
|
+
_calculate_ratio_feature_distances(instances, i_idx, len(instances)-1, distance_matrices)
|
|
289
|
+
return distance_matrices
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _classify_instances(instances, distance_matrices):
|
|
293
|
+
"""
|
|
294
|
+
classify all instances based on distance matrices
|
|
295
|
+
:param instances: dict - instances read from dataset
|
|
296
|
+
:param distance_matrices: dict - arbitrary distance matrices
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
nearest_neighbors = []
|
|
300
|
+
majority_votes = []
|
|
301
|
+
|
|
302
|
+
if not len(instances):
|
|
303
|
+
return None
|
|
304
|
+
|
|
305
|
+
for i in range(len(instances)):
|
|
306
|
+
votes = []
|
|
307
|
+
majority_vote = None
|
|
308
|
+
|
|
309
|
+
for d in distance_matrices.values():
|
|
310
|
+
# Distance of an instance from itself is ignored
|
|
311
|
+
votes.append(min(range(len(d[i])), key=lambda x: d[i][x] if x != i else float('inf')))
|
|
312
|
+
nearest_neighbors.append(votes)
|
|
313
|
+
|
|
314
|
+
classes = set(instances[v]['name'] for v in votes)
|
|
315
|
+
class_popularities = {c: sum(c == instances[x]['name'] for x in votes) for c in classes}
|
|
316
|
+
most_popular_class = max(class_popularities.keys(), key=lambda x: class_popularities[x])
|
|
317
|
+
|
|
318
|
+
if sum(class_popularities[c] == class_popularities[most_popular_class] for c in class_popularities.keys()) == 1:
|
|
319
|
+
# There is a majority class
|
|
320
|
+
majority_vote = most_popular_class
|
|
321
|
+
else:
|
|
322
|
+
# There is no majority class
|
|
323
|
+
# Calculate most common reciprocal vote
|
|
324
|
+
reciprocal_scores = []
|
|
325
|
+
for v in votes:
|
|
326
|
+
reciprocal_votes = []
|
|
327
|
+
for d in distance_matrices.values():
|
|
328
|
+
reciprocal_votes.append(min(range(len(d[v])), key=lambda x: d[v][x] if v != x else float('inf')))
|
|
329
|
+
reciprocal_scores.append(sum(instances[r]['name'] == instances[i]['name'] for r in reciprocal_votes))
|
|
330
|
+
|
|
331
|
+
if max(reciprocal_scores) and sum(r == max(reciprocal_scores) for r in reciprocal_scores) == 1:
|
|
332
|
+
reciprocal_candidate = max(range(len(reciprocal_scores)), key=lambda x: reciprocal_scores[x])
|
|
333
|
+
majority_vote = instances[votes[reciprocal_candidate]]['name']
|
|
334
|
+
|
|
335
|
+
majority_votes.append(majority_vote)
|
|
336
|
+
|
|
337
|
+
return nearest_neighbors, majority_votes
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def classify_last_event():
|
|
341
|
+
"""
|
|
342
|
+
assign a class to the last recorded event
|
|
343
|
+
"""
|
|
344
|
+
instances = read_instances()
|
|
345
|
+
if not len(Data.EVENTS):
|
|
346
|
+
return None
|
|
347
|
+
|
|
348
|
+
last_event = Data.EVENTS[-1]
|
|
349
|
+
|
|
350
|
+
distance_matrices = \
|
|
351
|
+
_calculate_sound_feature_distances(instances, last_event, Data.DISTANCE_MATRICES)
|
|
352
|
+
nearest_neighbors, majority_votes = _classify_instances(instances,distance_matrices)
|
|
353
|
+
|
|
354
|
+
if Data.AUTO_LEARN:
|
|
355
|
+
performance_current = Data.PERFORMANCE['in-sample accuracy']
|
|
356
|
+
performance_new = _evaluate_votes(instances,nearest_neighbors,majority_votes)['in-sample accuracy']
|
|
357
|
+
if performance_current < performance_new:
|
|
358
|
+
record_last_event(majority_votes[-1])
|
|
359
|
+
console(f'[info] sound_event: added new instance ({majority_votes[-1]}) to the dataset')
|
|
360
|
+
console(f'[info] sound_event: in-sample accuracy increased from {performance_current} to {performance_new}')
|
|
361
|
+
|
|
362
|
+
return {'class': majority_votes[-1]}
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
################################################################################
|
|
366
|
+
# I/O helper functions
|
|
367
|
+
# Should not be used manually.
|
|
368
|
+
################################################################################
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def read_instances():
|
|
372
|
+
"""
|
|
373
|
+
read stored instances into a dict
|
|
374
|
+
"""
|
|
375
|
+
try:
|
|
376
|
+
with open(Data.DATASET, 'r') as f:
|
|
377
|
+
return json.loads(f.read())
|
|
378
|
+
except OSError:
|
|
379
|
+
syslog(f'[ERR] sound_event: unable to read the dataset')
|
|
380
|
+
return []
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def _write_instances(instances):
|
|
384
|
+
"""
|
|
385
|
+
overwrite instances in the dataset
|
|
386
|
+
:param instances: dict - instances to store
|
|
387
|
+
"""
|
|
388
|
+
with open(Data.DATASET, 'w+') as f:
|
|
389
|
+
f.write(json.dumps(instances))
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def _init_matrices():
|
|
393
|
+
"""
|
|
394
|
+
initialize distance matrices
|
|
395
|
+
"""
|
|
396
|
+
instances = read_instances()
|
|
397
|
+
Data.DISTANCE_MATRICES = \
|
|
398
|
+
_calculate_all_sound_feature_distances(instances)
|
|
399
|
+
|
|
400
|
+
if len(instances):
|
|
401
|
+
Data.PERFORMANCE = _evaluate_dataset(instances,Data.DISTANCE_MATRICES)
|
|
402
|
+
else:
|
|
403
|
+
console('[info] sound_event: dataset is missing, initializing empty dataset')
|
|
404
|
+
_write_instances([])
|
|
405
|
+
Data.PERFORMANCE = {}
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
################################################################################
|
|
409
|
+
# Functions for recording and clearing events from the dataset
|
|
410
|
+
# Can be used during training to save events used for classification.
|
|
411
|
+
################################################################################
|
|
412
|
+
|
|
413
|
+
def record_last_event(label):
|
|
414
|
+
"""
|
|
415
|
+
save the last recorded event in the dataset
|
|
416
|
+
:param label: str - name of the class
|
|
417
|
+
"""
|
|
418
|
+
console('[info] sound_event: event of length %s will be recorded as "%s"' % (_get_feature_values(Data.EVENTS[-1],'length'), label))
|
|
419
|
+
Data.EVENTS[-1]['name'] = label
|
|
420
|
+
try:
|
|
421
|
+
instances = read_instances()
|
|
422
|
+
except Exception as e:
|
|
423
|
+
syslog(f'[ERR] sound_event: could not read instances (override): {e}')
|
|
424
|
+
instances = []
|
|
425
|
+
|
|
426
|
+
instances.append(Data.EVENTS[-1])
|
|
427
|
+
_write_instances(instances)
|
|
428
|
+
_init_matrices()
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def remove_last_instance():
|
|
432
|
+
"""
|
|
433
|
+
remove the last recorded instance from the dataset
|
|
434
|
+
"""
|
|
435
|
+
instances = read_instances()
|
|
436
|
+
instances.pop()
|
|
437
|
+
_write_instances(instances)
|
|
438
|
+
_init_matrices()
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def remove_instance_by_idx(idx):
|
|
442
|
+
"""
|
|
443
|
+
remove an instance with an index from the dataset
|
|
444
|
+
:param idx: int - index of the instance to be deleted from the dataset
|
|
445
|
+
"""
|
|
446
|
+
instances = read_instances()
|
|
447
|
+
del instances[idx]
|
|
448
|
+
_write_instances(instances)
|
|
449
|
+
_init_matrices()
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def remove_classes():
|
|
453
|
+
"""
|
|
454
|
+
remove all instances from the dataset
|
|
455
|
+
"""
|
|
456
|
+
_write_instances([])
|
|
457
|
+
_init_matrices()
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def remove_class(class_name):
|
|
461
|
+
"""
|
|
462
|
+
remove all instances with the specified label
|
|
463
|
+
:param class_name: str - name of the class
|
|
464
|
+
"""
|
|
465
|
+
instances = read_instances()
|
|
466
|
+
new_instances = []
|
|
467
|
+
|
|
468
|
+
for instance in instances:
|
|
469
|
+
if instance['name'] != class_name:
|
|
470
|
+
new_instances.append(instance)
|
|
471
|
+
|
|
472
|
+
_write_instances(new_instances)
|
|
473
|
+
_init_matrices()
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def relabel_class(old_label, new_label):
|
|
477
|
+
"""
|
|
478
|
+
rename class used for labeling instances in the dataset
|
|
479
|
+
:param class_name: str - name of the class
|
|
480
|
+
"""
|
|
481
|
+
instances = read_instances()
|
|
482
|
+
|
|
483
|
+
for instance in instances:
|
|
484
|
+
if instance['name'] == old_label:
|
|
485
|
+
instance['name'] = new_label
|
|
486
|
+
|
|
487
|
+
_write_instances(instances)
|
|
488
|
+
_init_matrices()
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
################################################################################
|
|
492
|
+
# Utility functions
|
|
493
|
+
################################################################################
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def get_classes():
|
|
497
|
+
"""
|
|
498
|
+
get all unique classes (labels) stored in the dataset
|
|
499
|
+
"""
|
|
500
|
+
instances = read_instances()
|
|
501
|
+
return set([i['name'] for i in instances])
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
def get_events():
|
|
505
|
+
"""
|
|
506
|
+
return all recorded events
|
|
507
|
+
"""
|
|
508
|
+
return Data.EVENTS
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def _evaluate_votes(instances, nearest_neighbors, majority_votes):
|
|
512
|
+
"""
|
|
513
|
+
evaluate classification performance of nearest neighbor results
|
|
514
|
+
with multiple features
|
|
515
|
+
:param instances: dict - instances read from dataset
|
|
516
|
+
:param nearest_neighbors: list - nearest neighbors per feature for each instance
|
|
517
|
+
:param majority_votes: list - names of the classes assigned to the instance
|
|
518
|
+
"""
|
|
519
|
+
instances_by_class = {}
|
|
520
|
+
hits_by_class = {}
|
|
521
|
+
accuracy = 0
|
|
522
|
+
|
|
523
|
+
for idx, neighbors in enumerate(nearest_neighbors):
|
|
524
|
+
majority_vote = majority_votes[idx]
|
|
525
|
+
ground_truth = instances[idx]['name']
|
|
526
|
+
|
|
527
|
+
accuracy += (majority_vote == ground_truth)
|
|
528
|
+
|
|
529
|
+
if not ground_truth in instances_by_class.keys():
|
|
530
|
+
instances_by_class[ground_truth] = []
|
|
531
|
+
instances_by_class[ground_truth].append(majority_vote == ground_truth)
|
|
532
|
+
|
|
533
|
+
if not ground_truth in hits_by_class.keys():
|
|
534
|
+
hits_by_class[ground_truth] = 0
|
|
535
|
+
hits_by_class[ground_truth] += sum([instances[neighbor]['name'] == ground_truth for neighbor in neighbors])/len(neighbors)
|
|
536
|
+
|
|
537
|
+
return {
|
|
538
|
+
'in-sample accuracy': accuracy/len(instances),
|
|
539
|
+
'class accuracies': {c: sum(instances_by_class[c])/len(instances_by_class[c])
|
|
540
|
+
for c in instances_by_class.keys()},
|
|
541
|
+
'class consistency': {c: hits_by_class[c]/len(instances_by_class[c])
|
|
542
|
+
for c in hits_by_class.keys()}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
def _evaluate_dataset(instances,distance_matrices):
|
|
547
|
+
"""
|
|
548
|
+
evaluate the in-sample classification performance
|
|
549
|
+
:param instances: dict - instances read from dataset
|
|
550
|
+
:param distance_matrices: dict - arbitrary distance matrices
|
|
551
|
+
"""
|
|
552
|
+
|
|
553
|
+
nearest_neighbors, majority_votes = _classify_instances(instances, distance_matrices)
|
|
554
|
+
return _evaluate_votes(instances, nearest_neighbors, majority_votes)
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def get_performance():
|
|
558
|
+
"""
|
|
559
|
+
return the in-sample classification performance
|
|
560
|
+
"""
|
|
561
|
+
return Data.PERFORMANCE
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
################################################################################
|
|
565
|
+
# Event loop functions
|
|
566
|
+
################################################################################
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
def autolearn(enabled = True):
|
|
570
|
+
"""
|
|
571
|
+
enable/disable automated learning, use it when there are already labeled
|
|
572
|
+
instances in the dataset
|
|
573
|
+
:param enabled: bool
|
|
574
|
+
"""
|
|
575
|
+
Data.AUTO_LEARN = enabled
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
def subscribe(callback, event):
|
|
579
|
+
"""
|
|
580
|
+
subscribe to notifications of events
|
|
581
|
+
:callback: func - callback function
|
|
582
|
+
:param event: str - event to get notification for (use 'all' to be notified for all events)
|
|
583
|
+
"""
|
|
584
|
+
Data.EVENT_CALLBACKS.add((callback, event))
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def _notify(event):
|
|
588
|
+
"""
|
|
589
|
+
notification function to invoke callbacks
|
|
590
|
+
:param event: str - class of the currently recorded event
|
|
591
|
+
"""
|
|
592
|
+
for callback, observed_event in Data.EVENT_CALLBACKS:
|
|
593
|
+
if event == observed_event or observed_event == 'all':
|
|
594
|
+
callback(event)
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
def _detect_event(samples, frame_size, pause_frame_count, rmse_threshold = 0.1):
|
|
598
|
+
"""
|
|
599
|
+
segment and classify events in samples while maintaining a pointer within
|
|
600
|
+
the raw samples to indicate the last processed sample of an event so that
|
|
601
|
+
the rest can be processed later
|
|
602
|
+
:param samples: list - raw audio samples
|
|
603
|
+
:param frame_size: int - size of an audio frame
|
|
604
|
+
:param pause_frame_count: int - how many pause frame to count for separating events
|
|
605
|
+
:param rmse_threshold: float - RMSE value above which to detect an event (0-1)
|
|
606
|
+
"""
|
|
607
|
+
rmse_frames = _rmse(samples, frame_size)
|
|
608
|
+
current_event = []
|
|
609
|
+
events_features = []
|
|
610
|
+
event_pointer = 0
|
|
611
|
+
pause_frames = 0
|
|
612
|
+
|
|
613
|
+
# Audio segmentation based on RMSE, and extraction of features
|
|
614
|
+
for i in range(len(rmse_frames)):
|
|
615
|
+
if rmse_frames[i] >= rmse_threshold:
|
|
616
|
+
if not len(current_event):
|
|
617
|
+
event_pointer = i*frame_size
|
|
618
|
+
current_event.extend(samples[i*frame_size : (i+1)*frame_size])
|
|
619
|
+
pause_frames = 0
|
|
620
|
+
else:
|
|
621
|
+
pause_frames += 1
|
|
622
|
+
if len(current_event) and pause_frames <= pause_frame_count:
|
|
623
|
+
current_event.extend(samples[i*frame_size : (i+1)*frame_size])
|
|
624
|
+
else:
|
|
625
|
+
event_pointer = (i+1)*frame_size
|
|
626
|
+
|
|
627
|
+
# Finalize and classify event if there are enough pause frames
|
|
628
|
+
if len(current_event) and pause_frames >= pause_frame_count:
|
|
629
|
+
events_features.append(_analyze_event(current_event, frame_size))
|
|
630
|
+
current_event = []
|
|
631
|
+
pause_frames = 0
|
|
632
|
+
|
|
633
|
+
return events_features, event_pointer
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
async def __control_task(capture_duration_ms,
|
|
637
|
+
max_event_duration_ms,
|
|
638
|
+
frame_size_ms,
|
|
639
|
+
pause_duration_ms,
|
|
640
|
+
event_buffer_length):
|
|
641
|
+
"""
|
|
642
|
+
micro task for event detection
|
|
643
|
+
:param capture_duration_ms: int - duration of samples to capture at once
|
|
644
|
+
:param max_event_duration_ms: int - maximum duration of an event
|
|
645
|
+
:param frame_size: int - size of the audio frames used for features
|
|
646
|
+
:param pause_duration_ms: int - duration of pause frames to distinguish events
|
|
647
|
+
:param event_buffer_length: int - number of events to store at once
|
|
648
|
+
"""
|
|
649
|
+
with micro_task(tag=Data.CONTROL_TASK_TAG) as my_task:
|
|
650
|
+
samples = []
|
|
651
|
+
frame_size = int((frame_size_ms/1000)*LM_i2s_mic.Data.SAMPLING_RATE)
|
|
652
|
+
pause_duration = int(pause_duration_ms/frame_size_ms)
|
|
653
|
+
|
|
654
|
+
while True:
|
|
655
|
+
try:
|
|
656
|
+
new_samples = LM_i2s_mic.decode(await LM_i2s_mic._capture(capture_duration_ms/1000))
|
|
657
|
+
samples.extend(new_samples)
|
|
658
|
+
|
|
659
|
+
events, event_pointer = _detect_event(samples, frame_size, pause_duration)
|
|
660
|
+
# Store unprocessed samples to be extended by future samples
|
|
661
|
+
samples = samples[event_pointer:]
|
|
662
|
+
|
|
663
|
+
for event in events:
|
|
664
|
+
Data.EVENTS.append(event)
|
|
665
|
+
last_event_label = classify_last_event()
|
|
666
|
+
_notify(last_event_label['class'])
|
|
667
|
+
console(f'[info] sound_event: {last_event_label}')
|
|
668
|
+
|
|
669
|
+
# Discard old samples if the number of stored samples exceeds a threshold
|
|
670
|
+
max_samples = int((max_event_duration_ms/1000)*LM_i2s_mic.Data.SAMPLING_RATE)
|
|
671
|
+
if len(samples) > max_samples:
|
|
672
|
+
samples = samples[-max_samples:]
|
|
673
|
+
|
|
674
|
+
# Wait for new samples to be taken
|
|
675
|
+
ms_period = int(len(new_samples)/LM_i2s_mic.Data.SAMPLING_RATE)
|
|
676
|
+
await my_task.feed(sleep_ms=ms_period)
|
|
677
|
+
Data.EVENTS = Data.EVENTS[-event_buffer_length:]
|
|
678
|
+
except Exception as e:
|
|
679
|
+
console(f'[ERR] sound_event: {e}')
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
def load(dataset = 'sound_events.pds',
|
|
683
|
+
capture_duration_ms=192,
|
|
684
|
+
max_event_duration_ms = 3000,
|
|
685
|
+
frame_size_ms=80,
|
|
686
|
+
pause_duration_ms=500,
|
|
687
|
+
event_buffer_length=1,
|
|
688
|
+
sd_storage=False):
|
|
689
|
+
"""
|
|
690
|
+
start micro task for event detection, and mount SD storage if enabled
|
|
691
|
+
:param capture_duration_ms: int - duration of samples to capture at once
|
|
692
|
+
:param max_event_duration_ms: int - maximum duration of an event
|
|
693
|
+
:param frame_size_ms: int - duration of the audio frames used for features
|
|
694
|
+
:param event_buffer_length: int - number of events to store at once
|
|
695
|
+
:param sd_storage: bool - use SD card storage for the dataset
|
|
696
|
+
"""
|
|
697
|
+
if sd_storage:
|
|
698
|
+
from vfs import mount
|
|
699
|
+
from machine import SDCard
|
|
700
|
+
mount(SDCard(),"/sd")
|
|
701
|
+
Data.DATASET = f'/sd/{dataset}'
|
|
702
|
+
else:
|
|
703
|
+
Data.DATASET = dataset
|
|
704
|
+
|
|
705
|
+
_init_matrices()
|
|
706
|
+
micro_task(tag=Data.CONTROL_TASK_TAG,
|
|
707
|
+
task=__control_task(capture_duration_ms,
|
|
708
|
+
max_event_duration_ms,
|
|
709
|
+
frame_size_ms,
|
|
710
|
+
pause_duration_ms,
|
|
711
|
+
event_buffer_length))
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
#######################
|
|
715
|
+
# Helper LM functions #
|
|
716
|
+
#######################
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
def pinmap():
|
|
720
|
+
"""
|
|
721
|
+
[i] micrOS LM naming convention
|
|
722
|
+
Shows logical pins - pin number(s) used by this Load module
|
|
723
|
+
- info which pins to use for this application
|
|
724
|
+
:return dict: pin name (str) - pin value (int) pairs
|
|
725
|
+
"""
|
|
726
|
+
return pinmap_search(['i2s_sck', 'i2s_ws', 'i2s_sd'])
|
|
727
|
+
|
|
728
|
+
|
|
729
|
+
def help(widgets=False):
|
|
730
|
+
"""
|
|
731
|
+
[i] micrOS LM naming convention - built-in help message
|
|
732
|
+
:return tuple:
|
|
733
|
+
(widgets=False) list of functions implemented by this application
|
|
734
|
+
(widgets=True) list of widget json for UI generation
|
|
735
|
+
"""
|
|
736
|
+
return resolve(('load dataset=\'sound_events.pds\' capture_duration_ms=192 max_event_duration_ms=3000'\
|
|
737
|
+
'frame_size_ms=80 pause_duration_ms=500 event_buffer_length=1 sd_storage=False ',\
|
|
738
|
+
'TEXTBOX classify_last_event',\
|
|
739
|
+
'read_instances',\
|
|
740
|
+
'record_last_event label',\
|
|
741
|
+
'remove_last_instance',\
|
|
742
|
+
'remove_instance_by_idx idx',\
|
|
743
|
+
'remove_classes',\
|
|
744
|
+
'remove_class class_name',\
|
|
745
|
+
'relabel_class old_label new_label',\
|
|
746
|
+
'get_classes',\
|
|
747
|
+
'get_events',\
|
|
748
|
+
'TEXTBOX get_performance',\
|
|
749
|
+
'autolearn enabled=True',\
|
|
750
|
+
'subscribe callback event',\
|
|
751
|
+
'pinmap'), widgets=widgets)
|