From 02b1be44f8bbcfcef81833adfb5c6d47abe12766 Mon Sep 17 00:00:00 2001 From: admin Date: Wed, 19 Mar 2025 11:55:34 -0700 Subject: [PATCH] Add new files for icons, images, documentation, configuration, and initial code structure --- .gitignore | 5 + .vscode/extensions.json | 10 + .vscode/settings.json | 20 + Backup/data/Test1.txt | 1 + Backup/data/Test2.txt | 8 + Backup/data/anim-profiles.json | 499 + Backup/data/cfg/!instructions.txt | 15 + Backup/data/cfg/anim-list.json | 25 + Backup/data/cfg/anim-profiles.json | 499 + Backup/data/cfg/anim-props.json | 97 + Backup/data/cfg/app-events.json | 70 + Backup/data/cfg/ble.json | 9 + Backup/data/cfg/buzzer.json | 81 + Backup/data/cfg/firmware.json | 8 + Backup/data/cfg/led-devices.json | 39 + Backup/data/cfg/relays.json | 41 + Backup/data/cfg/system.json | 37 + Backup/data/cfg/touch-pins.json | 30 + Backup/data/cfg/trx433.json | 10 + Backup/data/cfg/wifi.json | 20 + Backup/data/favicon.ico | Bin 0 -> 1150 bytes Backup/data/img/atalogo.png | Bin 0 -> 16690 bytes Backup/data/img/favicon-32x32.png | Bin 0 -> 2428 bytes Backup/data/www/appcontrol.html | 617 ++ Backup/data/www/bleconfig.html | 16 + Backup/data/www/boothconfig.html | 11 + Backup/data/www/edit.html | 144 + Backup/data/www/failed.html | 24 + Backup/data/www/filemanager.html | 297 + Backup/data/www/global-style.css | 74 + Backup/data/www/home.html | 185 + Backup/data/www/info.html | 11 + Backup/data/www/navbar.html | 56 + Backup/data/www/ok.html | 24 + Backup/data/www/wifiportal.html | 128 + ToDo.txt | 15 + boards/wroom-32S3-N8R2.json | 51 + boards/wroom-32d-8mb.json | 37 + data/ata-boothifier-upgrade.html | 508 + data/boards/board14.json | 30 + data/boards/board15.json | 30 + data/boards/test.json | 30 + data/booths/custom.json | 135 + data/booths/helio-flare.json | 137 + data/booths/helio-posh.json | 134 + data/booths/helio-sport.json | 135 + data/booths/light-stik.json | 138 + data/booths/lumia-m.json | 140 + data/booths/lumia-spectra.json | 135 + data/booths/lumia-xl.json | 159 + data/booths/m1.json | 135 + data/booths/marquee.json | 135 + data/booths/roamer-big.json | 136 + data/booths/roamer.json | 136 + data/booths/testbooth.json | 135 + data/css/global-style.css | 79 + data/css/nav.css | 72 + data/favicon.ico | Bin 0 -> 1150 bytes data/flashstik-reg.html | 472 + data/images/atalogo.png | Bin 0 -> 16690 bytes data/images/favicon-32x32.png | Bin 0 -> 2428 bytes data/js/event-box.js | 442 + data/js/fwUoload.js | 64 + data/js/hue-select.js | 244 + data/js/jquery-3.7.1.js | 8673 +++++++++++++++++ data/system/luma-stiks.json | 16 + data/system/readme.txt | 5 + data/system/system.json | 4 + data/system/tunes.json | 70 + data/system/update.json | 4 + data/system/wifi.json | 16 + data/www/about.html | 169 + data/www/edit.html | 191 + data/www/edit_old.html | 151 + data/www/failed.html | 33 + data/www/files.html | 276 + data/www/home.html | 303 + data/www/index.html | 29 + data/www/lights.html | 527 + data/www/navbar.html | 17 + data/www/ok.html | 34 + data/www/setup.html | 385 + data/www/upgrade.html | 234 + data/www/wifi.html | 159 + diagram.json | 8 + docs/!instructions.txt | 18 + docs/DSLRBooth Mode Wifi no bluetooth.md | 2 + docs/Manual Mode.md | 3 + docs/Sys Report.txt | 51 + docs/Todo (add).doc | 22 + docs/Todo.(fixes)doc | 10 + docs/cli commands.txt | 23 + esp32s3_atabooth_8mb.code-workspace | 74 + firmware_update/GenUpdate.py | 165 + firmware_update/UploadToGoogle.py | 152 + .../latest/data/ata-boothifier-upgrade.html | 501 + .../latest/data/css/global-style.css | 79 + firmware_update/latest/data/css/nav.css | 72 + firmware_update/latest/data/favicon.ico | Bin 0 -> 1150 bytes .../latest/data/images/atalogo.png | Bin 0 -> 16690 bytes .../latest/data/images/favicon-32x32.png | Bin 0 -> 2428 bytes firmware_update/latest/data/js/event-box.js | 442 + firmware_update/latest/data/js/fwUoload.js | 64 + firmware_update/latest/data/js/hue-select.js | 244 + .../latest/data/js/jquery-3.7.1.js | 8673 +++++++++++++++++ firmware_update/latest/data/system/tunes.json | 70 + .../latest/data/system/update.json | 4 + firmware_update/latest/data/www/about.html | 169 + firmware_update/latest/data/www/edit.html | 191 + firmware_update/latest/data/www/edit_old.html | 151 + firmware_update/latest/data/www/failed.html | 33 + firmware_update/latest/data/www/files.html | 276 + firmware_update/latest/data/www/home.html | 303 + firmware_update/latest/data/www/index.html | 29 + firmware_update/latest/data/www/lights.html | 527 + firmware_update/latest/data/www/navbar.html | 17 + firmware_update/latest/data/www/ok.html | 34 + firmware_update/latest/data/www/setup.html | 385 + firmware_update/latest/data/www/upgrade.html | 234 + firmware_update/latest/data/www/wifi.html | 159 + firmware_update/latest/firmware.bin | Bin 0 -> 1409568 bytes firmware_update/latest/update.json | 164 + .../loyal-column-439819-e3-8cddff2ee2c2.json | 13 + include/ATALights.h | 77 + include/Animations.h | 42 + include/AppUpgrade.h | 175 + include/AppVersion.h | 47 + include/BLE_SP110E.h | 34 + include/BLE_UpdateService.h | 8 + include/BleServer.h | 5 + include/ColorPalettes.h | 45 + include/JsonConstrain.h | 13 + include/OnEveryN.h | 37 + include/PWM_Output.h | 42 + include/Ramp_Lights.h | 29 + include/UriDecode.h | 21 + include/_FreeRTOSConfig.h | 30 + include/global.h | 58 + include/my_board.h | 37 + include/my_buttons.h | 29 + include/my_buzzer.h | 41 + include/my_oled.h | 17 + include/my_tsensor.h | 23 + include/my_wifi.h | 42 + include/system.h | 94 + .../.github/workflows/build_linux.yml | 182 + .../.github/workflows/build_windows.yml | 174 + lib/AnyRtttl/.gitignore | 4 + lib/AnyRtttl/.piopm | 1 + lib/AnyRtttl/AUTHORS | 5 + lib/AnyRtttl/CHANGES | 32 + lib/AnyRtttl/CMakeLists.txt | 160 + lib/AnyRtttl/INSTALL.md | 105 + lib/AnyRtttl/LICENSE | 21 + lib/AnyRtttl/README.md | 523 + lib/AnyRtttl/appveyor.yml | 81 + lib/AnyRtttl/ci/appveyor/UploadJUnitFiles.bat | 4 + lib/AnyRtttl/ci/appveyor/UploadJUnitFiles.ps1 | 39 + .../ci/appveyor/arduino_build_sketch.bat | 11 + .../ci/appveyor/arduino_install_libraries.bat | 11 + lib/AnyRtttl/ci/appveyor/build_library.bat | 11 + lib/AnyRtttl/ci/appveyor/emulate_appveyor.bat | 23 + .../ci/appveyor/install_arduinocli.bat | 11 + .../ci/appveyor/install_googletest.bat | 11 + .../ci/appveyor/install_rapidassist.bat | 11 + lib/AnyRtttl/ci/appveyor/install_this.bat | 11 + .../ci/appveyor/install_win32arduino.bat | 11 + lib/AnyRtttl/ci/appveyor/test_script.bat | 11 + lib/AnyRtttl/ci/appveyor/zip_me.bat | 29 + lib/AnyRtttl/ci/generic/patch.py | 70 + .../ci/generic/win32arduino.pattern.txt | 2 + .../ci/generic/win32arduino.replace.txt | 10 + .../ci/github/arduino_build_sketch.bat | 11 + .../ci/github/arduino_build_sketch.sh | 12 + .../ci/github/arduino_install_libraries.bat | 11 + .../ci/github/arduino_install_libraries.sh | 12 + lib/AnyRtttl/ci/github/build_library.bat | 11 + lib/AnyRtttl/ci/github/build_library.sh | 12 + lib/AnyRtttl/ci/github/install_arduinocli.bat | 15 + lib/AnyRtttl/ci/github/install_arduinocli.sh | 17 + lib/AnyRtttl/ci/github/install_googletest.bat | 11 + lib/AnyRtttl/ci/github/install_googletest.sh | 12 + .../ci/github/install_rapidassist.bat | 11 + lib/AnyRtttl/ci/github/install_rapidassist.sh | 12 + lib/AnyRtttl/ci/github/install_this.bat | 11 + lib/AnyRtttl/ci/github/install_this.sh | 12 + .../ci/github/install_win32arduino.bat | 11 + .../ci/github/install_win32arduino.sh | 12 + lib/AnyRtttl/ci/github/maketestbadge.py | 165 + lib/AnyRtttl/ci/github/test_script.bat | 11 + lib/AnyRtttl/ci/github/test_script.sh | 12 + .../ci/github/tests_not_available.badge.json | 8 + lib/AnyRtttl/ci/linux/arduino_build_sketch.sh | 30 + .../ci/linux/arduino_install_libraries.sh | 30 + lib/AnyRtttl/ci/linux/build_library.sh | 38 + lib/AnyRtttl/ci/linux/install_arduinocli.sh | 33 + lib/AnyRtttl/ci/linux/install_googletest.sh | 56 + lib/AnyRtttl/ci/linux/install_rapidassist.sh | 56 + lib/AnyRtttl/ci/linux/install_this.sh | 19 + lib/AnyRtttl/ci/linux/install_win32arduino.sh | 64 + lib/AnyRtttl/ci/linux/test_script.sh | 33 + .../ci/windows/arduino_build_sketch.bat | 33 + .../ci/windows/arduino_install_libraries.bat | 33 + lib/AnyRtttl/ci/windows/build_library.bat | 48 + .../ci/windows/install_arduinocli.bat | 31 + .../ci/windows/install_googletest.bat | 81 + .../ci/windows/install_rapidassist.bat | 68 + lib/AnyRtttl/ci/windows/install_this.bat | 46 + .../ci/windows/install_win32arduino.bat | 76 + lib/AnyRtttl/ci/windows/test_script.bat | 41 + lib/AnyRtttl/examples.cpp.in | 14 + lib/AnyRtttl/examples/Basic/Basic.ino | 22 + .../BlockingProgramMemoryRtttl.ino | 35 + .../examples/BlockingRtttl/BlockingRtttl.ino | 31 + .../BlockingWithNonBlocking.ino | 44 + .../NonBlockingProgramMemoryRtttl.ino | 41 + .../NonBlockingRtttl/NonBlockingRtttl.ino | 38 + .../NonBlockingStopBeforeEnd.ino | 42 + .../examples/Play10Bits/Play10Bits.ino | 44 + .../examples/Play16Bits/Play16Bits.ino | 25 + .../examples/Rtttl2Code/Rtttl2Code.ino | 52 + lib/AnyRtttl/keywords.txt | 16 + lib/AnyRtttl/library.properties | 9 + lib/AnyRtttl/src/anyrtttl.cpp | 672 ++ lib/AnyRtttl/src/anyrtttl.h | 258 + lib/AnyRtttl/src/binrtttl.cpp | 136 + lib/AnyRtttl/src/binrtttl.h | 107 + lib/AnyRtttl/src/pitches.h | 222 + lib/README | 46 + partitions_8mb_ota_2mb_ee.cvs | 9 + platformio.ini | 39 + sdkconfig.defaults | 1 + src/ATALights.cpp | 478 + src/Animations.cpp | 571 ++ src/AppUpgrade.cpp | 618 ++ src/BLE_SP110E.cpp | 389 + src/BLE_UpdateService.cpp | 151 + src/BleServer.cpp | 68 + src/ColorPalettes.cpp | 12 + src/JsonConstrain.cpp | 122 + src/PWM_Output.cpp | 96 + src/Ramp_Lights.cpp | 62 + src/common/HSVTable.cpp | 175 + src/common/HSVTable.h | 23 + src/common/LEDStrip.cpp | 473 + src/common/LEDStrip.h | 117 + src/common/color_tools.cpp | 69 + src/common/color_tools.h | 38 + src/common/fileSystem.cpp | 153 + src/common/fileSystem.h | 28 + src/common/luma-stiks.cpp | 249 + src/common/luma-stiks.h | 63 + src/common/neo_colors.h | 138 + src/global.cpp | 369 + src/main.cpp | 595 ++ src/my_board.cpp | 92 + src/my_buttons.cpp | 137 + src/my_buzzer.cpp | 109 + src/my_oled.cpp | 125 + src/my_tsensor.cpp | 48 + src/my_wifi.cpp | 1396 +++ temporary/BLE-FlashStick-Service.h | 4 + temporary/BLE-FlaskStick-Service.cpp | 157 + temporary/EventBoxTest.html | 82 + temporary/Temp/ATALights2.cpp | 178 + temporary/Temp/BTSerial.cpp | 240 + temporary/Temp/BTSerial.h | 16 + temporary/Temp/MyNeoPixelBus.cpp | 47 + temporary/Temp/command_processor.cpp | 71 + temporary/Temp/command_processor.h | 31 + temporary/Temp/firmware_html.h | 215 + temporary/Temp/githubCert.h | 30 + temporary/Temp/led_animation.cpp | 1023 ++ temporary/Temp/led_animation.h | 205 + temporary/Temp/led_strip.cpp | 564 ++ temporary/Temp/led_strip.h | 128 + temporary/Temp/luma_master.cpp | 111 + temporary/Temp/luma_master.h | 24 + temporary/Temp/my_wifi.cpp | 1451 +++ temporary/Temp/my_wifi.h | 66 + temporary/Temp/otaupdate.cpp | 96 + temporary/Temp/otaupdate.h | 14 + temporary/Temp/rf_transceiver.cpp | 44 + temporary/Temp/rf_transceiver.h | 23 + temporary/appcontrol old.html | 623 ++ temporary/bak/cfg/!instructions.txt | 13 + temporary/bak/cfg/anim-list.json | 105 + temporary/bak/cfg/anim-profiles.json | 1203 +++ temporary/bak/cfg/anim-settings.json | 97 + temporary/bak/cfg/app-events.json | 90 + temporary/bak/cfg/ble.json | 9 + temporary/bak/cfg/buzzer.json | 73 + temporary/bak/cfg/firmware.json | 8 + temporary/bak/cfg/led-devices.json | 42 + temporary/bak/cfg/luma-stiks.json | 14 + temporary/bak/cfg/relays.json | 41 + temporary/bak/cfg/system.json | 37 + temporary/bak/cfg/touch-pins.json | 30 + temporary/bak/cfg/trx433.json | 10 + temporary/bak/cfg/wifi.json | 26 + temporary/bak/www/edit.html | 144 + temporary/bak/www/edit2.html | 170 + temporary/bak/www/event-box.js | 420 + temporary/bak/www/failed.html | 24 + temporary/bak/www/files.html | 311 + temporary/bak/www/global-style.css | 74 + temporary/bak/www/home.html | 271 + temporary/bak/www/hue-select.js | 275 + temporary/bak/www/label.html | 56 + temporary/bak/www/lights.html | 452 + temporary/bak/www/navbar.html | 55 + temporary/bak/www/ok.html | 24 + temporary/bak/www/setup.html | 355 + temporary/bak/www/wifi.html | 127 + temporary/bleconfig.html | 16 + temporary/colorPicket.html | 31 + temporary/eventTest.htm | 145 + temporary/gridstyles.css | 6 + temporary/gridtest.html | 12 + temporary/hue-select-min.js | 1 + temporary/lights-old.html | 755 ++ temporary/log.html | 10 + temporary/neo_colors.h | 78 + temporary/styles.css | 50 + temporary/upgrade.html | 228 + test/README | 11 + webSock/AppUpgrade.cpp | 522 + webSock/AppUpgrade.h | 156 + webSock/my_wifi.cpp | 1164 +++ webSock/my_wifi.h | 38 + webSock/upgrade.html | 193 + web_ble.html | 113 + wokwi.toml | 6 + z_old/HSVTable.cpp | 175 + z_old/HSVTable.h | 23 + z_old/LEDStrip.cpp | 473 + z_old/LEDStrip.h | 117 + z_old/color_tools.cpp | 69 + z_old/color_tools.h | 38 + z_old/fileSystem.cpp | 74 + z_old/fileSystem.h | 23 + z_old/my_board.cpp | 249 + z_old/my_board.h | 162 + z_old/my_buttons.cpp | 92 + z_old/my_buttons.h | 34 + z_old/my_buzzer.cpp | 123 + z_old/neo_colors.h | 138 + z_old/old/anim-list.json | 105 + z_old/old/anim-profile-common.json | 9 + z_old/old/anim-profile1.json | 149 + z_old/old/anim-profile2.json | 149 + z_old/old/anim-profile3.json | 149 + z_old/old/anim-profile4.json | 149 + z_old/old/anim-profile5.json | 149 + z_old/old/anim-profile6.json | 149 + z_old/old/anim-profile7.json | 149 + z_old/old/anim-profile8.json | 149 + z_old/old/anim-profiles copy.json | 1203 +++ z_old/old/anim-settings.json | 97 + z_old/old/app-events.json | 90 + z_old/old/ble.json | 9 + z_old/old/board.json | 63 + z_old/old/led-devices.json | 42 + z_old/old/ramp-lights.json | 23 + z_old/old/relays.json | 41 + z_old/old/system.json | 146 + z_old/old/touch-pins.json | 30 + z_old/wifi_temp.cpp | 1449 +++ 368 files changed, 64038 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json create mode 100644 Backup/data/Test1.txt create mode 100644 Backup/data/Test2.txt create mode 100644 Backup/data/anim-profiles.json create mode 100644 Backup/data/cfg/!instructions.txt create mode 100644 Backup/data/cfg/anim-list.json create mode 100644 Backup/data/cfg/anim-profiles.json create mode 100644 Backup/data/cfg/anim-props.json create mode 100644 Backup/data/cfg/app-events.json create mode 100644 Backup/data/cfg/ble.json create mode 100644 Backup/data/cfg/buzzer.json create mode 100644 Backup/data/cfg/firmware.json create mode 100644 Backup/data/cfg/led-devices.json create mode 100644 Backup/data/cfg/relays.json create mode 100644 Backup/data/cfg/system.json create mode 100644 Backup/data/cfg/touch-pins.json create mode 100644 Backup/data/cfg/trx433.json create mode 100644 Backup/data/cfg/wifi.json create mode 100644 Backup/data/favicon.ico create mode 100644 Backup/data/img/atalogo.png create mode 100644 Backup/data/img/favicon-32x32.png create mode 100644 Backup/data/www/appcontrol.html create mode 100644 Backup/data/www/bleconfig.html create mode 100644 Backup/data/www/boothconfig.html create mode 100644 Backup/data/www/edit.html create mode 100644 Backup/data/www/failed.html create mode 100644 Backup/data/www/filemanager.html create mode 100644 Backup/data/www/global-style.css create mode 100644 Backup/data/www/home.html create mode 100644 Backup/data/www/info.html create mode 100644 Backup/data/www/navbar.html create mode 100644 Backup/data/www/ok.html create mode 100644 Backup/data/www/wifiportal.html create mode 100644 ToDo.txt create mode 100644 boards/wroom-32S3-N8R2.json create mode 100644 boards/wroom-32d-8mb.json create mode 100644 data/ata-boothifier-upgrade.html create mode 100644 data/boards/board14.json create mode 100644 data/boards/board15.json create mode 100644 data/boards/test.json create mode 100644 data/booths/custom.json create mode 100644 data/booths/helio-flare.json create mode 100644 data/booths/helio-posh.json create mode 100644 data/booths/helio-sport.json create mode 100644 data/booths/light-stik.json create mode 100644 data/booths/lumia-m.json create mode 100644 data/booths/lumia-spectra.json create mode 100644 data/booths/lumia-xl.json create mode 100644 data/booths/m1.json create mode 100644 data/booths/marquee.json create mode 100644 data/booths/roamer-big.json create mode 100644 data/booths/roamer.json create mode 100644 data/booths/testbooth.json create mode 100644 data/css/global-style.css create mode 100644 data/css/nav.css create mode 100644 data/favicon.ico create mode 100644 data/flashstik-reg.html create mode 100644 data/images/atalogo.png create mode 100644 data/images/favicon-32x32.png create mode 100644 data/js/event-box.js create mode 100644 data/js/fwUoload.js create mode 100644 data/js/hue-select.js create mode 100644 data/js/jquery-3.7.1.js create mode 100644 data/system/luma-stiks.json create mode 100644 data/system/readme.txt create mode 100644 data/system/system.json create mode 100644 data/system/tunes.json create mode 100644 data/system/update.json create mode 100644 data/system/wifi.json create mode 100644 data/www/about.html create mode 100644 data/www/edit.html create mode 100644 data/www/edit_old.html create mode 100644 data/www/failed.html create mode 100644 data/www/files.html create mode 100644 data/www/home.html create mode 100644 data/www/index.html create mode 100644 data/www/lights.html create mode 100644 data/www/navbar.html create mode 100644 data/www/ok.html create mode 100644 data/www/setup.html create mode 100644 data/www/upgrade.html create mode 100644 data/www/wifi.html create mode 100644 diagram.json create mode 100644 docs/!instructions.txt create mode 100644 docs/DSLRBooth Mode Wifi no bluetooth.md create mode 100644 docs/Manual Mode.md create mode 100644 docs/Sys Report.txt create mode 100644 docs/Todo (add).doc create mode 100644 docs/Todo.(fixes)doc create mode 100644 docs/cli commands.txt create mode 100644 esp32s3_atabooth_8mb.code-workspace create mode 100644 firmware_update/GenUpdate.py create mode 100644 firmware_update/UploadToGoogle.py create mode 100644 firmware_update/latest/data/ata-boothifier-upgrade.html create mode 100644 firmware_update/latest/data/css/global-style.css create mode 100644 firmware_update/latest/data/css/nav.css create mode 100644 firmware_update/latest/data/favicon.ico create mode 100644 firmware_update/latest/data/images/atalogo.png create mode 100644 firmware_update/latest/data/images/favicon-32x32.png create mode 100644 firmware_update/latest/data/js/event-box.js create mode 100644 firmware_update/latest/data/js/fwUoload.js create mode 100644 firmware_update/latest/data/js/hue-select.js create mode 100644 firmware_update/latest/data/js/jquery-3.7.1.js create mode 100644 firmware_update/latest/data/system/tunes.json create mode 100644 firmware_update/latest/data/system/update.json create mode 100644 firmware_update/latest/data/www/about.html create mode 100644 firmware_update/latest/data/www/edit.html create mode 100644 firmware_update/latest/data/www/edit_old.html create mode 100644 firmware_update/latest/data/www/failed.html create mode 100644 firmware_update/latest/data/www/files.html create mode 100644 firmware_update/latest/data/www/home.html create mode 100644 firmware_update/latest/data/www/index.html create mode 100644 firmware_update/latest/data/www/lights.html create mode 100644 firmware_update/latest/data/www/navbar.html create mode 100644 firmware_update/latest/data/www/ok.html create mode 100644 firmware_update/latest/data/www/setup.html create mode 100644 firmware_update/latest/data/www/upgrade.html create mode 100644 firmware_update/latest/data/www/wifi.html create mode 100644 firmware_update/latest/firmware.bin create mode 100644 firmware_update/latest/update.json create mode 100644 firmware_update/loyal-column-439819-e3-8cddff2ee2c2.json create mode 100644 include/ATALights.h create mode 100644 include/Animations.h create mode 100644 include/AppUpgrade.h create mode 100644 include/AppVersion.h create mode 100644 include/BLE_SP110E.h create mode 100644 include/BLE_UpdateService.h create mode 100644 include/BleServer.h create mode 100644 include/ColorPalettes.h create mode 100644 include/JsonConstrain.h create mode 100644 include/OnEveryN.h create mode 100644 include/PWM_Output.h create mode 100644 include/Ramp_Lights.h create mode 100644 include/UriDecode.h create mode 100644 include/_FreeRTOSConfig.h create mode 100644 include/global.h create mode 100644 include/my_board.h create mode 100644 include/my_buttons.h create mode 100644 include/my_buzzer.h create mode 100644 include/my_oled.h create mode 100644 include/my_tsensor.h create mode 100644 include/my_wifi.h create mode 100644 include/system.h create mode 100644 lib/AnyRtttl/.github/workflows/build_linux.yml create mode 100644 lib/AnyRtttl/.github/workflows/build_windows.yml create mode 100644 lib/AnyRtttl/.gitignore create mode 100644 lib/AnyRtttl/.piopm create mode 100644 lib/AnyRtttl/AUTHORS create mode 100644 lib/AnyRtttl/CHANGES create mode 100644 lib/AnyRtttl/CMakeLists.txt create mode 100644 lib/AnyRtttl/INSTALL.md create mode 100644 lib/AnyRtttl/LICENSE create mode 100644 lib/AnyRtttl/README.md create mode 100644 lib/AnyRtttl/appveyor.yml create mode 100644 lib/AnyRtttl/ci/appveyor/UploadJUnitFiles.bat create mode 100644 lib/AnyRtttl/ci/appveyor/UploadJUnitFiles.ps1 create mode 100644 lib/AnyRtttl/ci/appveyor/arduino_build_sketch.bat create mode 100644 lib/AnyRtttl/ci/appveyor/arduino_install_libraries.bat create mode 100644 lib/AnyRtttl/ci/appveyor/build_library.bat create mode 100644 lib/AnyRtttl/ci/appveyor/emulate_appveyor.bat create mode 100644 lib/AnyRtttl/ci/appveyor/install_arduinocli.bat create mode 100644 lib/AnyRtttl/ci/appveyor/install_googletest.bat create mode 100644 lib/AnyRtttl/ci/appveyor/install_rapidassist.bat create mode 100644 lib/AnyRtttl/ci/appveyor/install_this.bat create mode 100644 lib/AnyRtttl/ci/appveyor/install_win32arduino.bat create mode 100644 lib/AnyRtttl/ci/appveyor/test_script.bat create mode 100644 lib/AnyRtttl/ci/appveyor/zip_me.bat create mode 100644 lib/AnyRtttl/ci/generic/patch.py create mode 100644 lib/AnyRtttl/ci/generic/win32arduino.pattern.txt create mode 100644 lib/AnyRtttl/ci/generic/win32arduino.replace.txt create mode 100644 lib/AnyRtttl/ci/github/arduino_build_sketch.bat create mode 100644 lib/AnyRtttl/ci/github/arduino_build_sketch.sh create mode 100644 lib/AnyRtttl/ci/github/arduino_install_libraries.bat create mode 100644 lib/AnyRtttl/ci/github/arduino_install_libraries.sh create mode 100644 lib/AnyRtttl/ci/github/build_library.bat create mode 100644 lib/AnyRtttl/ci/github/build_library.sh create mode 100644 lib/AnyRtttl/ci/github/install_arduinocli.bat create mode 100644 lib/AnyRtttl/ci/github/install_arduinocli.sh create mode 100644 lib/AnyRtttl/ci/github/install_googletest.bat create mode 100644 lib/AnyRtttl/ci/github/install_googletest.sh create mode 100644 lib/AnyRtttl/ci/github/install_rapidassist.bat create mode 100644 lib/AnyRtttl/ci/github/install_rapidassist.sh create mode 100644 lib/AnyRtttl/ci/github/install_this.bat create mode 100644 lib/AnyRtttl/ci/github/install_this.sh create mode 100644 lib/AnyRtttl/ci/github/install_win32arduino.bat create mode 100644 lib/AnyRtttl/ci/github/install_win32arduino.sh create mode 100644 lib/AnyRtttl/ci/github/maketestbadge.py create mode 100644 lib/AnyRtttl/ci/github/test_script.bat create mode 100644 lib/AnyRtttl/ci/github/test_script.sh create mode 100644 lib/AnyRtttl/ci/github/tests_not_available.badge.json create mode 100644 lib/AnyRtttl/ci/linux/arduino_build_sketch.sh create mode 100644 lib/AnyRtttl/ci/linux/arduino_install_libraries.sh create mode 100644 lib/AnyRtttl/ci/linux/build_library.sh create mode 100644 lib/AnyRtttl/ci/linux/install_arduinocli.sh create mode 100644 lib/AnyRtttl/ci/linux/install_googletest.sh create mode 100644 lib/AnyRtttl/ci/linux/install_rapidassist.sh create mode 100644 lib/AnyRtttl/ci/linux/install_this.sh create mode 100644 lib/AnyRtttl/ci/linux/install_win32arduino.sh create mode 100644 lib/AnyRtttl/ci/linux/test_script.sh create mode 100644 lib/AnyRtttl/ci/windows/arduino_build_sketch.bat create mode 100644 lib/AnyRtttl/ci/windows/arduino_install_libraries.bat create mode 100644 lib/AnyRtttl/ci/windows/build_library.bat create mode 100644 lib/AnyRtttl/ci/windows/install_arduinocli.bat create mode 100644 lib/AnyRtttl/ci/windows/install_googletest.bat create mode 100644 lib/AnyRtttl/ci/windows/install_rapidassist.bat create mode 100644 lib/AnyRtttl/ci/windows/install_this.bat create mode 100644 lib/AnyRtttl/ci/windows/install_win32arduino.bat create mode 100644 lib/AnyRtttl/ci/windows/test_script.bat create mode 100644 lib/AnyRtttl/examples.cpp.in create mode 100644 lib/AnyRtttl/examples/Basic/Basic.ino create mode 100644 lib/AnyRtttl/examples/BlockingProgramMemoryRtttl/BlockingProgramMemoryRtttl.ino create mode 100644 lib/AnyRtttl/examples/BlockingRtttl/BlockingRtttl.ino create mode 100644 lib/AnyRtttl/examples/BlockingWithNonBlocking/BlockingWithNonBlocking.ino create mode 100644 lib/AnyRtttl/examples/NonBlockingProgramMemoryRtttl/NonBlockingProgramMemoryRtttl.ino create mode 100644 lib/AnyRtttl/examples/NonBlockingRtttl/NonBlockingRtttl.ino create mode 100644 lib/AnyRtttl/examples/NonBlockingStopBeforeEnd/NonBlockingStopBeforeEnd.ino create mode 100644 lib/AnyRtttl/examples/Play10Bits/Play10Bits.ino create mode 100644 lib/AnyRtttl/examples/Play16Bits/Play16Bits.ino create mode 100644 lib/AnyRtttl/examples/Rtttl2Code/Rtttl2Code.ino create mode 100644 lib/AnyRtttl/keywords.txt create mode 100644 lib/AnyRtttl/library.properties create mode 100644 lib/AnyRtttl/src/anyrtttl.cpp create mode 100644 lib/AnyRtttl/src/anyrtttl.h create mode 100644 lib/AnyRtttl/src/binrtttl.cpp create mode 100644 lib/AnyRtttl/src/binrtttl.h create mode 100644 lib/AnyRtttl/src/pitches.h create mode 100644 lib/README create mode 100644 partitions_8mb_ota_2mb_ee.cvs create mode 100644 platformio.ini create mode 100644 sdkconfig.defaults create mode 100644 src/ATALights.cpp create mode 100644 src/Animations.cpp create mode 100644 src/AppUpgrade.cpp create mode 100644 src/BLE_SP110E.cpp create mode 100644 src/BLE_UpdateService.cpp create mode 100644 src/BleServer.cpp create mode 100644 src/ColorPalettes.cpp create mode 100644 src/JsonConstrain.cpp create mode 100644 src/PWM_Output.cpp create mode 100644 src/Ramp_Lights.cpp create mode 100644 src/common/HSVTable.cpp create mode 100644 src/common/HSVTable.h create mode 100644 src/common/LEDStrip.cpp create mode 100644 src/common/LEDStrip.h create mode 100644 src/common/color_tools.cpp create mode 100644 src/common/color_tools.h create mode 100644 src/common/fileSystem.cpp create mode 100644 src/common/fileSystem.h create mode 100644 src/common/luma-stiks.cpp create mode 100644 src/common/luma-stiks.h create mode 100644 src/common/neo_colors.h create mode 100644 src/global.cpp create mode 100644 src/main.cpp create mode 100644 src/my_board.cpp create mode 100644 src/my_buttons.cpp create mode 100644 src/my_buzzer.cpp create mode 100644 src/my_oled.cpp create mode 100644 src/my_tsensor.cpp create mode 100644 src/my_wifi.cpp create mode 100644 temporary/BLE-FlashStick-Service.h create mode 100644 temporary/BLE-FlaskStick-Service.cpp create mode 100644 temporary/EventBoxTest.html create mode 100644 temporary/Temp/ATALights2.cpp create mode 100644 temporary/Temp/BTSerial.cpp create mode 100644 temporary/Temp/BTSerial.h create mode 100644 temporary/Temp/MyNeoPixelBus.cpp create mode 100644 temporary/Temp/command_processor.cpp create mode 100644 temporary/Temp/command_processor.h create mode 100644 temporary/Temp/firmware_html.h create mode 100644 temporary/Temp/githubCert.h create mode 100644 temporary/Temp/led_animation.cpp create mode 100644 temporary/Temp/led_animation.h create mode 100644 temporary/Temp/led_strip.cpp create mode 100644 temporary/Temp/led_strip.h create mode 100644 temporary/Temp/luma_master.cpp create mode 100644 temporary/Temp/luma_master.h create mode 100644 temporary/Temp/my_wifi.cpp create mode 100644 temporary/Temp/my_wifi.h create mode 100644 temporary/Temp/otaupdate.cpp create mode 100644 temporary/Temp/otaupdate.h create mode 100644 temporary/Temp/rf_transceiver.cpp create mode 100644 temporary/Temp/rf_transceiver.h create mode 100644 temporary/appcontrol old.html create mode 100644 temporary/bak/cfg/!instructions.txt create mode 100644 temporary/bak/cfg/anim-list.json create mode 100644 temporary/bak/cfg/anim-profiles.json create mode 100644 temporary/bak/cfg/anim-settings.json create mode 100644 temporary/bak/cfg/app-events.json create mode 100644 temporary/bak/cfg/ble.json create mode 100644 temporary/bak/cfg/buzzer.json create mode 100644 temporary/bak/cfg/firmware.json create mode 100644 temporary/bak/cfg/led-devices.json create mode 100644 temporary/bak/cfg/luma-stiks.json create mode 100644 temporary/bak/cfg/relays.json create mode 100644 temporary/bak/cfg/system.json create mode 100644 temporary/bak/cfg/touch-pins.json create mode 100644 temporary/bak/cfg/trx433.json create mode 100644 temporary/bak/cfg/wifi.json create mode 100644 temporary/bak/www/edit.html create mode 100644 temporary/bak/www/edit2.html create mode 100644 temporary/bak/www/event-box.js create mode 100644 temporary/bak/www/failed.html create mode 100644 temporary/bak/www/files.html create mode 100644 temporary/bak/www/global-style.css create mode 100644 temporary/bak/www/home.html create mode 100644 temporary/bak/www/hue-select.js create mode 100644 temporary/bak/www/label.html create mode 100644 temporary/bak/www/lights.html create mode 100644 temporary/bak/www/navbar.html create mode 100644 temporary/bak/www/ok.html create mode 100644 temporary/bak/www/setup.html create mode 100644 temporary/bak/www/wifi.html create mode 100644 temporary/bleconfig.html create mode 100644 temporary/colorPicket.html create mode 100644 temporary/eventTest.htm create mode 100644 temporary/gridstyles.css create mode 100644 temporary/gridtest.html create mode 100644 temporary/hue-select-min.js create mode 100644 temporary/lights-old.html create mode 100644 temporary/log.html create mode 100644 temporary/neo_colors.h create mode 100644 temporary/styles.css create mode 100644 temporary/upgrade.html create mode 100644 test/README create mode 100644 webSock/AppUpgrade.cpp create mode 100644 webSock/AppUpgrade.h create mode 100644 webSock/my_wifi.cpp create mode 100644 webSock/my_wifi.h create mode 100644 webSock/upgrade.html create mode 100644 web_ble.html create mode 100644 wokwi.toml create mode 100644 z_old/HSVTable.cpp create mode 100644 z_old/HSVTable.h create mode 100644 z_old/LEDStrip.cpp create mode 100644 z_old/LEDStrip.h create mode 100644 z_old/color_tools.cpp create mode 100644 z_old/color_tools.h create mode 100644 z_old/fileSystem.cpp create mode 100644 z_old/fileSystem.h create mode 100644 z_old/my_board.cpp create mode 100644 z_old/my_board.h create mode 100644 z_old/my_buttons.cpp create mode 100644 z_old/my_buttons.h create mode 100644 z_old/my_buzzer.cpp create mode 100644 z_old/neo_colors.h create mode 100644 z_old/old/anim-list.json create mode 100644 z_old/old/anim-profile-common.json create mode 100644 z_old/old/anim-profile1.json create mode 100644 z_old/old/anim-profile2.json create mode 100644 z_old/old/anim-profile3.json create mode 100644 z_old/old/anim-profile4.json create mode 100644 z_old/old/anim-profile5.json create mode 100644 z_old/old/anim-profile6.json create mode 100644 z_old/old/anim-profile7.json create mode 100644 z_old/old/anim-profile8.json create mode 100644 z_old/old/anim-profiles copy.json create mode 100644 z_old/old/anim-settings.json create mode 100644 z_old/old/app-events.json create mode 100644 z_old/old/ble.json create mode 100644 z_old/old/board.json create mode 100644 z_old/old/led-devices.json create mode 100644 z_old/old/ramp-lights.json create mode 100644 z_old/old/relays.json create mode 100644 z_old/old/system.json create mode 100644 z_old/old/touch-pins.json create mode 100644 z_old/wifi_temp.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..080e70d --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..2bfceab --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,20 @@ +{ + // ...existing code... + + // Live Server Configuration + "liveServer.settings.root": "/data", + "liveServer.settings.port": 5500, + "liveServer.settings.cors": true, + "liveServer.settings.mount": [ + ["/", "./data"] + ], + "liveServer.settings.CustomBrowser": "chrome", + "liveServer.settings.NoBrowser": false, + "liveServer.settings.ignoreFiles": [ + ".vscode/**", + "**/*.scss", + "**/*.sass" + ], + "liveServer.settings.wait": 100, + "liveServer.settings.host": "localhost" +} \ No newline at end of file diff --git a/Backup/data/Test1.txt b/Backup/data/Test1.txt new file mode 100644 index 0000000..fad425a --- /dev/null +++ b/Backup/data/Test1.txt @@ -0,0 +1 @@ +asdfada \ No newline at end of file diff --git a/Backup/data/Test2.txt b/Backup/data/Test2.txt new file mode 100644 index 0000000..be80832 --- /dev/null +++ b/Backup/data/Test2.txt @@ -0,0 +1,8 @@ +adfasdfadfa +adfasdfadfaadfa +sd + +adfasdfadfaadfaa + + +afas \ No newline at end of file diff --git a/Backup/data/anim-profiles.json b/Backup/data/anim-profiles.json new file mode 100644 index 0000000..e949d44 --- /dev/null +++ b/Backup/data/anim-profiles.json @@ -0,0 +1,499 @@ +{ + "countdown": { + "min": "10", + "max": "75", + "hold": "700", + "ramp": "650" + }, + "profile-index": 0, + "profiles": [ + { + "name": "Profile test", + "events": [ + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": "5", + "speed": "10" + }, + { + "anim": 1, + "colmain": "#000020", + "colbase": "#000020", + "density": "25", + "speed": "50" + }, + { + "anim": 2, + "colmain": "#000020", + "colbase": "#000020", + "density": "35", + "speed": "65" + }, + { + "anim": 3, + "colmain": "#000020", + "colbase": "#000020", + "density": "45", + "speed": "75" + }, + { + "anim": 4, + "colmain": "#000020", + "colbase": "#000020", + "density": "5", + "speed": "10" + }, + { + "anim": 4, + "colmain": "#000020", + "colbase": "#000020", + "density": "5", + "speed": "10" + }, + { + "anim": 4, + "colmain": "#000020", + "colbase": "#000020", + "density": "5", + "speed": "10" + }, + { + "anim": 4, + "colmain": "#000020", + "colbase": "#000020", + "density": "5", + "speed": "70" + } + ] + }, + { + "name": "Profile 1", + "events": [ + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + } + ] + }, + { + "name": "Profile 2", + "events": [ + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + } + ] + }, + { + "name": "Profile 3", + "events": [ + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + } + ] + }, + { + "name": "Profile 4", + "events": [ + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + } + ] + }, + { + "name": "Profile 5", + "events": [ + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + } + ] + }, + { + "name": "Profile 6", + "events": [ + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + } + ] + }, + { + "name": "Profile 7", + "events": [ + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 1, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 70 + } + ] + } + ] +} \ No newline at end of file diff --git a/Backup/data/cfg/!instructions.txt b/Backup/data/cfg/!instructions.txt new file mode 100644 index 0000000..5301313 --- /dev/null +++ b/Backup/data/cfg/!instructions.txt @@ -0,0 +1,15 @@ + +Choose Control App: + 1) Open app-events.json + 2) Change index to corresponding app + a. 0=DSLRBooth, 1=...., 2=..... + + +Front Constant Light: ( to enable the light) + 1) Open led-devices.json + 2) Change "front-light", "en" to true + + +Rear Constant Light: + 1) Open led-devices.json + 2) Change "rear-light", "en" to true \ No newline at end of file diff --git a/Backup/data/cfg/anim-list.json b/Backup/data/cfg/anim-list.json new file mode 100644 index 0000000..87bc231 --- /dev/null +++ b/Backup/data/cfg/anim-list.json @@ -0,0 +1,25 @@ +{ + "whitefills":[ + "Bottom/Up Fill", + "Snake Fill", + "Tick Fill", + "Smooth Brighten", + "Cycle All", + "Random Select" + ], + "animations":[ + "Rainbow", + "Hue Spectrum Mirrored", + "Meteors Hue", + "Dashes", + "Sectors", + "Fire (red) mirrored", + "Fire (blue) mirrored", + "Fire (green) mirrored", + "Strobe", + "Twinkle", + "Rain", + "Stacking", + "Snake (Hue Range)" + ] +} \ No newline at end of file diff --git a/Backup/data/cfg/anim-profiles.json b/Backup/data/cfg/anim-profiles.json new file mode 100644 index 0000000..8d7f6e5 --- /dev/null +++ b/Backup/data/cfg/anim-profiles.json @@ -0,0 +1,499 @@ +{ + "countdown":{ + "min": 10, + "max": 75, + "hold": 700, + "ramp": 650 + }, + "profile-index": 0, + "profiles":[ + { + "name": "Profile test", + "events":[ + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 1, + "colmain": "#000020", + "colbase": "#000020", + "density": 25, + "speed": 50 + }, + { + "anim": 2, + "colmain": "#000020", + "colbase": "#000020", + "density": 35, + "speed": 65 + }, + { + "anim": 3, + "colmain": "#000020", + "colbase": "#000020", + "density": 45, + "speed": 75 + }, + { + "anim": 4, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 4, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 4, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 4, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 70 + } + ] + }, + { + "name": "Profile 1", + "events":[ + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + } + ] + }, + { + "name": "Profile 2", + "events":[ + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + } + ] + }, + { + "name": "Profile 3", + "events":[ + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + } + ] + }, + { + "name": "Profile 4", + "events":[ + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + } + ] + }, + { + "name": "Profile 5", + "events":[ + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + } + ] + }, + { + "name": "Profile 6", + "events":[ + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + } + ] + }, + { + "name": "Profile 7", + "events":[ + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 0, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 10 + }, + { + "anim": 1, + "colmain": "#000020", + "colbase": "#000020", + "density": 5, + "speed": 70 + } + ] + } + ] +} \ No newline at end of file diff --git a/Backup/data/cfg/anim-props.json b/Backup/data/cfg/anim-props.json new file mode 100644 index 0000000..3889b0e --- /dev/null +++ b/Backup/data/cfg/anim-props.json @@ -0,0 +1,97 @@ +{ + "twinkle":{ + "col1": "#000000", + "col2": "#000000", + "density": 1, + "speed": 25, + "pwr": 255 + }, + "Strobe":{ + "col1": "#000000", + "col2": "#000000", + "density": 1, + "speed": 25, + "pwr": 255 + }, + "Solid":{ + "col1": "#000000", + "col2": "#000000", + "density": 1, + "pwr": 255 + }, + "Fade":{ + "col1": "#000000", + "range": 128, + "speed": 25, + "pwr": 255 + }, + "HueSwirl":{ + "col1": "#000000", + "range": 128, + "speed": 25, + "pwr": 255 + }, + "Meteors":{ + "col1": "#000000", + "col2": "#000000", + "range": 1, + "density": 1, + "speed": 25, + "pwr": 255 + }, + "Dashes":{ + "col1": "#000000", + "col2": "#000000", + "col3": "#000000", + "segments": 1, + "density": 1, + "speed": 25, + "pwr": 255 + }, + "DashesRange":{ + "col1": "#000000", + "col2": "#000000", + "range": 128, + "segments": 1, + "density": 1, + "speed": 25, + "pwr": 255 + }, + "Fire":{ + "col1": "#000000", + "cool": 25, + "spark": 25, + "speed": 25, + "pwr": 255 + }, + "Rain":{ + "col1": "#000000", + "col2": "#000000", + "range": 128, + "density": 1, + "speed": 25, + "pwr": 255 + }, + "Stacking":{ + "col1": "#000000", + "col2": "#000000", + "range": 128, + "speed": 25, + "pwr": 255 + }, + "Snake":{ + "col1": "#000000", + "col2": "#000000", + "range": 128, + "speed": 25, + "pwr": 255 + }, + "Theater":{ + "col1": "#000000", + "col2": "#000000", + "range": 128, + "speed": 25, + "step": 20, + "pwr": 255 + } +} \ No newline at end of file diff --git a/Backup/data/cfg/app-events.json b/Backup/data/cfg/app-events.json new file mode 100644 index 0000000..ecf4922 --- /dev/null +++ b/Backup/data/cfg/app-events.json @@ -0,0 +1,70 @@ +{ + "index": 0, + "apps":[ + { + "name": "DSLR Booth Configuration", + "events":[ + "White Fill / Countdown", + "Experience Selection", + "Select Sharing Screen", + "", + "", + "", + "", + "" + ] + }, + { + "name": "FotoFliqs Configuration", + "events":[ + "White Fill / Countdown", + "Experience Selection", + "Preview", + "Notice!", + "Idle / Pause", + "Advertisement", + "Printing", + "Phone Input / QR Code / Apple Drop" + ] + }, + { + "name": "TouchPics Configuration", + "events":[ + "White Fill / Countdown", + "Experience Selection", + "Preview", + "Notice!", + "Idle / Pause", + "Advertisement", + "Printing", + "Phone Input / QR Code / Apple Drop" + ] + }, + { + "name": "FotoZap Configuration", + "events":[ + "White Fill / Countdown", + "Experience Selection", + "Select Sharing Screen", + "", + "", + "", + "", + "" + ] + }, + { + "name": "Twineit Configuration", + "events":[ + "White Fill / Countdown", + "Experience Selection", + "Select Sharing Screen", + "", + "", + "", + "", + "" + ] + } + ] +} \ No newline at end of file diff --git a/Backup/data/cfg/ble.json b/Backup/data/cfg/ble.json new file mode 100644 index 0000000..b1af60b --- /dev/null +++ b/Backup/data/cfg/ble.json @@ -0,0 +1,9 @@ +{ + "en": "true", + "core": 1, + "device-name": "ATA_COMM", + "key": "123456", + "service_uuid": "6E400001-B5A3-F393-E0A9-E50E24DCCA9E", + "char-uuid-rx": "6E400002-B5A3-F393-E0A9-E50E24DCCA9E", + "char-uuid-tx": "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" +} \ No newline at end of file diff --git a/Backup/data/cfg/buzzer.json b/Backup/data/cfg/buzzer.json new file mode 100644 index 0000000..48c7c2d --- /dev/null +++ b/Backup/data/cfg/buzzer.json @@ -0,0 +1,81 @@ +{ + "en":true, + "boot": + { + "cycles": 1, + "pause": 0, + "tune": "Boot:d=16,o=5,b=112:32p,f,g,a,b,b#" + }, + "restart": + { + "cycles": 1, + "pause": 0, + "tune": "Boot:d=16,o=5,b=112:32p,f,g,a,b,b#" + }, + "wifi-conn": + { + "cycles": 1, + "pause": 0, + "tune": "WifiConnected:d=16,o=5,b=112:32p,f,a#,c6,f,a#,c6,f,a#,c6,f,a#,c6,f,a#,c6,f,a#,c6" + }, + "wifi-disc": + { + "cycles": 1, + "pause": 0, + "tune": "Boot:d=16,o=5,b=112:32p,f,g,a,b,b#" + }, + "ble-conn": + { + "cycles": 1, + "pause":0, + "tune": "Boot:d=16,o=5,b=112:32p,f,g,a,b,b#" + }, + "ble-disc": + { + "cycles": 1, + "pause": 0, + "tune": "Boot:d=16,o=5,b=112:32p,f,g,a,b,b#" + }, + "click": + { + "cycles": 1, + "pause": 0, + "tune": "Click:d=16,o=5,b=112:32p,f,g,a,b,b#" + }, + "error": + { + "cycles": 1, + "pause": 0, + "tune": "Click:d=16,o=5,b=112:32p,f,g,a,b,b#" + }, + "success": + { + "cycles": 1, + "pause": 0, + "tune": "Click:d=16,o=5,b=112:32p,f,g,a,b,b#" + }, + "download": + { + "cycles": 1, + "pause": 0, + "tune": "Click:d=16,o=5,b=112:32p,f,g,a,b,b#" + }, + "waiting": + { + "cycles": 1, + "pause": 0, + "tune": "Click:d=16,o=5,b=112:32p,f,g,a,b,b#" + }, + "beep": + { + "cycles": 1, + "pause": 0, + "tune": "Click:d=16,o=5,b=112:32p,f,g,a,b,b#" + }, + "test": + { + "cycles": 1, + "pause": 0, + "tune": "Boot:d=16,o=5,b=112:32p,f,g,a,b,b#" + } +} \ No newline at end of file diff --git a/Backup/data/cfg/firmware.json b/Backup/data/cfg/firmware.json new file mode 100644 index 0000000..d691b5d --- /dev/null +++ b/Backup/data/cfg/firmware.json @@ -0,0 +1,8 @@ +{ + "firm-index": 0, + "firm-locations":[ + "http://mylocation1", + "http://mylocation2", + "http://mylocation3" + ] +} \ No newline at end of file diff --git a/Backup/data/cfg/led-devices.json b/Backup/data/cfg/led-devices.json new file mode 100644 index 0000000..f4caab1 --- /dev/null +++ b/Backup/data/cfg/led-devices.json @@ -0,0 +1,39 @@ +{ + "strip1": { + "en": true, + "size": 138, + "chip": "SK6812", + "rgb-order": "gbr", + "shift":0, + "offset": 0, + "bright": 200, + "pin": 3, + "i2s-ch": 0, + "core": 0 + }, + "strip2": { + "en": false, + "size": 20, + "chip": "SK6812", + "rgb-order": "grb", + "shift":0, + "offset": 0, + "bright": 200, + "pin": 46, + "i2s-ch": 1, + "core": 0 + }, + "front-light": { + "en": false, + "relay": 0, + "min": 20, + "max": 100, + "core": 1 + }, + "rear-light": { + "en": false, + "relay": 1, + "min": 20, + "max": 100 + } +} \ No newline at end of file diff --git a/Backup/data/cfg/relays.json b/Backup/data/cfg/relays.json new file mode 100644 index 0000000..73ee664 --- /dev/null +++ b/Backup/data/cfg/relays.json @@ -0,0 +1,41 @@ +{ +"relays": + [ + { + "pin": 45, + "freq": 500, + "min": 0, + "max": 100, + "default": 5, + "vision": false, + "deltarate": 0 + }, + { + "pin": 48, + "freq": 500, + "min": 0, + "max": 100, + "default": 10, + "vision": false, + "deltarate": 0 + }, + { + "pin": 47, + "freq": 500, + "min": 0, + "max": 100, + "default": 20, + "vision": true, + "deltarate": 0 + }, + { + "pin": 21, + "freq": 500, + "min": 0, + "max": 100, + "default": 40, + "vision": true, + "deltarate": 0 + } + ] +} \ No newline at end of file diff --git a/Backup/data/cfg/system.json b/Backup/data/cfg/system.json new file mode 100644 index 0000000..8cc367d --- /dev/null +++ b/Backup/data/cfg/system.json @@ -0,0 +1,37 @@ +{ + "buttons": [ + { + "en": true, + "pin": 8 + }, + { + "en": true, + "pin": 19 + }, + { + "en": true, + "pin": 0 + } + ], + "oled": { + "en": false, + "height": 64, + "width": 128 + }, + "fan": { + "en": false, + "relay": 3 + }, + "t-sensor": { + "en": false, + "addr": 72, + "sp1": 85, + "fan-pwr1": 50, + "sp2": 90, + "fan-pwr2": 90, + "hyst": 1 + }, + "adc": { + "ain1_factor": 1.0 + } +} diff --git a/Backup/data/cfg/touch-pins.json b/Backup/data/cfg/touch-pins.json new file mode 100644 index 0000000..d556439 --- /dev/null +++ b/Backup/data/cfg/touch-pins.json @@ -0,0 +1,30 @@ +{ + "touchpins": + [ + { + "en": true, + "pin": 23, + "min": 100 + }, + { + "en": true, + "pin": 23, + "min": 100 + }, + { + "en": true, + "pin": 23, + "min": 100 + }, + { + "en": true, + "pin": 23, + "min": 100 + }, + { + "en": true, + "pin": 23, + "min": 100 + } + ] +} \ No newline at end of file diff --git a/Backup/data/cfg/trx433.json b/Backup/data/cfg/trx433.json new file mode 100644 index 0000000..6f9cac0 --- /dev/null +++ b/Backup/data/cfg/trx433.json @@ -0,0 +1,10 @@ +{ +"rx433": { + "en": "true", + "pin": 38 + }, + "tx433": { + "en": "true", + "pin": 16 + } +} \ No newline at end of file diff --git a/Backup/data/cfg/wifi.json b/Backup/data/cfg/wifi.json new file mode 100644 index 0000000..e5b01b9 --- /dev/null +++ b/Backup/data/cfg/wifi.json @@ -0,0 +1,20 @@ +{ + "wifi": + { + "en": true, + "ap-ssid": "ATA-AP", + "ap-pass": "123456", + "mdns-name": "atadev", + "host-name": "ATADeviceXX" + }, + "cred1": + { + "ssid": "DPWifi", + "pass": "dave3.14159" + }, + "cred2": + { + "ssid": "Default", + "pass": "password" + } +} \ No newline at end of file diff --git a/Backup/data/favicon.ico b/Backup/data/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..3030261a3e26621938f916036d0f10aff5f0e208 GIT binary patch literal 1150 zcmaKrZA?>V6vvMdT9jlnny85yF!%-%Gt|w@eK5E!Vl=uhDgy`D;1orXv6#7_qEUfX zDMY0-OkO7#2aRCV7FvtYa*>x(u%%ror8uilYztPQQ1tF!G)80My*c;hKF|F<=k0%p zX5gMPhwu!hgMLKwiD(rJOIUfkju#R3K6e@ad`y~DJN2epxdUf=7Yt_ULtH{l$XI^M z!l^=409)Ef?6sy(j%ZDGQRlH?>Nx7)bf*o58c#e<=+7`;d6uQGcI9d$<03{hS)xd^ z)f9)fHl=fW>IEckKQw(#W8xSw2Vao-fIG1Duq#TNYQJgtvMf^eV zzZm|Pdqm9A@p5TUx8lOOiryVnc|+@p_|C8}fnznvV&8D<_P*Yv+&A%?YX8fPU1@_{ z_zedi3w{^)=kz-uB+aXU?zdbwZx5{Q*+W;K zZJ6deQXHgtJ3oGypAgK?ZseZ@z6|v%!~6&^KQT{lm8@uMJn65KXHyfCHqCzcFr{0` zcHW5j?f1w0&V_y!gJ1G0|KU7up^C#^V2KsCeu&V>mqb=BQNv84GiQi0W_+~$(MRS0 O_VJJJUmxQMBKjZfDN?=w literal 0 HcmV?d00001 diff --git a/Backup/data/img/atalogo.png b/Backup/data/img/atalogo.png new file mode 100644 index 0000000000000000000000000000000000000000..40f3ee677364375e76d185ba6271c3c85d3b2c1a GIT binary patch literal 16690 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 C#3g+I literal 0 HcmV?d00001 diff --git a/Backup/data/www/appcontrol.html b/Backup/data/www/appcontrol.html new file mode 100644 index 0000000..94b3533 --- /dev/null +++ b/Backup/data/www/appcontrol.html @@ -0,0 +1,617 @@ +{{NAVBAR}} + + + + App Control Configuration + + + + + + +

App Control Configuration

+ +
+ Saved Animation Profiles + + + + +
+
+ + +    + + +     + +
+
+ + +
+ + +
+ Countdown Animation ( White Fill ) + + + + + + + + + + + + + + +
+
+ +
+
+ +
+ +
+
+
+
+ +
+
+ +
+
+
+
+ +
+ +
+ Event0 + + + + + + + + + +
+
+ +
+
+   +
+
+   +
+
+ +
+
+ +
+ +
+
+ +
+ +
+ Event1 + + + + + + + + + +
+
+ +
+
+   +
+
+   +
+
+ +
+
+ +
+ +
+
+ +
+ +
+ Event2 + + + + + + + + + +
+
+ +
+
+   +
+
+   +
+
+ +
+
+ +
+ +
+
+ +
+ +
+ Event3 + + + + + + + + + +
+
+ +
+
+   +
+
+   +
+
+ +
+
+ +
+ +
+
+ +
+ +
+ Event4 + + + + + + + + + +
+
+ +
+
+   +
+
+   +
+
+ +
+
+ +
+ +
+
+ +
+ +
+ Event5 + + + + + + + + + +
+
+ +
+
+   +
+
+   +
+
+ +
+
+ +
+ +
+
+ +
+ +
+ Event6 + + + + + + + + + +
+
+ +
+
+   +
+
+   +
+
+ +
+
+ +
+ +
+
+ +
+ +
+ Event7 + + + + + + + + + +
+
+ +
+
+   +
+
+   +
+
+ +
+
+ +
+ +
+
+ + + + diff --git a/Backup/data/www/bleconfig.html b/Backup/data/www/bleconfig.html new file mode 100644 index 0000000..fac456f --- /dev/null +++ b/Backup/data/www/bleconfig.html @@ -0,0 +1,16 @@ +{{NAVBAR}} + + + + BLE Config + + + + + + +

App Control - Bluetooth Configuration

+ + \ No newline at end of file diff --git a/Backup/data/www/boothconfig.html b/Backup/data/www/boothconfig.html new file mode 100644 index 0000000..90427a1 --- /dev/null +++ b/Backup/data/www/boothconfig.html @@ -0,0 +1,11 @@ +{{NAVBAR}} + + + + + Photobooth Configuration + + +

Photobooth Configuration

+ + \ No newline at end of file diff --git a/Backup/data/www/edit.html b/Backup/data/www/edit.html new file mode 100644 index 0000000..f21f454 --- /dev/null +++ b/Backup/data/www/edit.html @@ -0,0 +1,144 @@ +{{NAVBAR}} + + + + Edit file + + + + + +

Edit file

+
+ Editing file: {{SAVE_PATH_INPUT}} +
+
+
+ +
+
+ {{SAVE_PATH_INPUT}} + + + + +
+
+
+ + + + + + \ No newline at end of file diff --git a/Backup/data/www/failed.html b/Backup/data/www/failed.html new file mode 100644 index 0000000..8850b8d --- /dev/null +++ b/Backup/data/www/failed.html @@ -0,0 +1,24 @@ +{{NAVBAR}} + + + + Update Failed + + + + + +
+

The update has failed.

+
+ +
+ + \ No newline at end of file diff --git a/Backup/data/www/filemanager.html b/Backup/data/www/filemanager.html new file mode 100644 index 0000000..8c96201 --- /dev/null +++ b/Backup/data/www/filemanager.html @@ -0,0 +1,297 @@ +{{NAVBAR}} + + + + File Manager + + + + + + + +

File Manager

+ +
+ File list +
+

  Total storage: {{FS_TOTAL_BYTES}}, Used: {{FS_USED_BYTES}}, Still available: {{FS_FREE_BYTES}}

+
+ + {{LISTED_FILES}} +
Listing: Size:
+
+
+ +
+ +
+ File upload +
+
+ + + +
+ +    + +
+
+
+ +
+ +
+ Edit file +
+
+ + + +
+
+
+
+ +
+ +
+ Delete file +
+
+ + + +
+
+
+
+ +
+ + + +

Firmware/File System Update

+ +
+ Firmware Update (Local) | Firmware Ver: {{FIRM_VER}} | fwataVxxx.bin or lfsataVxxx.bin +
+
+ + + + + +
+ + + +
+ +
+ + +
+ + + +
+
+
+ +
+ + +
+
+
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/Backup/data/www/global-style.css b/Backup/data/www/global-style.css new file mode 100644 index 0000000..fa8ada7 --- /dev/null +++ b/Backup/data/www/global-style.css @@ -0,0 +1,74 @@ +body { + /*background-color: #f7f7f7;*/ + font-family: Tahoma, Arial, sans-serif; + font-size: small; + margin: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; +} +h1 { + margin-top: 0; +} +input{ + cursor:pointer; +} +input[type="number"]{ + width: 100px; +} +#submit { + width:120px; +} +select{ + width:180px; +} +#select-path { + width:250px; +} +#select-dir { + width:90px; +} +#spacer-50 { + height: 50px; +} +#spacer-20 { + height: 20px; +} +#spacer-10 { + height: 10px; +} +table { + /*background-color: #dddddd;*/ + border-collapse: collapse; + width:640px; + margin: 0 auto; + overflow: visible; +} +td, th { + /*border: 1px solid #dddddd;*/ + text-align: left; + padding: 2px; +} +#first_td_th { + width:400px; +} +fieldset { + width:700px; + /*background-color: #f7f7f7;*/ + border-radius: 10px; + border-color: blue; +} +#format-notice { + color: #ff0000; +} +legend { + display: flex; + justify-content: center; + background-color:white; + background-blend-mode: darken; + border-radius: 10px; + padding: 1px 8px 2px 8px; + border-style: solid; + border-width: 1.0; +} \ No newline at end of file diff --git a/Backup/data/www/home.html b/Backup/data/www/home.html new file mode 100644 index 0000000..1b6e7b2 --- /dev/null +++ b/Backup/data/www/home.html @@ -0,0 +1,185 @@ +{{NAVBAR}} + + + + + + + Welcome + + + + +

ATA Booth Summary

+ +
+ Booth Overview +
+ + + + + + + + + + + + + + + + + + + + + + +
+ Mode: Lumia + + LED Strip1: Active + + Front Light: Active +
+
+
+ App: DSLRBooth + + LED Strip2: Active + + Rear Light: Active +
+
+
+ ... + + ... + + OLED: Active +
+
+
+ +
+ +
+ System Info: +
+ + + + + + + + + + + + + + + + + + + + + + +
+ Firmware Ver: 1.0 + + Flash Size: + + RAM Size: +
+
+
+ Booth T°: 80.3F + + Flash Free: + + Used RAM: +
+
+
+ Setpoint T°: 85F + + ... + + Free RAM: +
+
+
+ +
+ +
+ Network / WiFi +
+ + + + + + + + + + + + + + +
+ IP Addr: 192.XXX.XXX.XXX + + MAC Addr: + + WiFi SSID: +
+
+
+ WiFi RSSi: + + WiFi Ch: + + Wifi Encryp: +
+
+
+ +
+ +
+ Bluetooth LE +
+ + + + + + + +
+ Active: true + + Connected: true + + Name: ATADevXX +
+
+
+ + \ No newline at end of file diff --git a/Backup/data/www/info.html b/Backup/data/www/info.html new file mode 100644 index 0000000..5ddcf4c --- /dev/null +++ b/Backup/data/www/info.html @@ -0,0 +1,11 @@ +{{NAVBAR}} + + + + + Welcome + + +

System Information

+ + \ No newline at end of file diff --git a/Backup/data/www/navbar.html b/Backup/data/www/navbar.html new file mode 100644 index 0000000..460c215 --- /dev/null +++ b/Backup/data/www/navbar.html @@ -0,0 +1,56 @@ + + + + + + +
+ +
+ + \ No newline at end of file diff --git a/Backup/data/www/ok.html b/Backup/data/www/ok.html new file mode 100644 index 0000000..11be6e0 --- /dev/null +++ b/Backup/data/www/ok.html @@ -0,0 +1,24 @@ +{{NAVBAR}} + + + + Update Success + + + + + +
+

The update was successful.

+
+ +
+ + \ No newline at end of file diff --git a/Backup/data/www/wifiportal.html b/Backup/data/www/wifiportal.html new file mode 100644 index 0000000..f6bf4d4 --- /dev/null +++ b/Backup/data/www/wifiportal.html @@ -0,0 +1,128 @@ +{{NAVBAR}} + + + + + WiFi Credentials + + + +

Enter WiFi Credentials

+
+ + + + + + Image not found +
+ + + + diff --git a/ToDo.txt b/ToDo.txt new file mode 100644 index 0000000..16628d5 --- /dev/null +++ b/ToDo.txt @@ -0,0 +1,15 @@ +1 - Second Strip Animation + a - How to synchronize the 2 Animation + b - Set Default directions? + +2 - Wifi Server + +3 - OTA Upgrade + +4 - menu + a - file manager & editor + b - wifi credentials + c - firmware update + d - mode selection page + i - booth config and reboot + ii - \ No newline at end of file diff --git a/boards/wroom-32S3-N8R2.json b/boards/wroom-32S3-N8R2.json new file mode 100644 index 0000000..9fd8b41 --- /dev/null +++ b/boards/wroom-32S3-N8R2.json @@ -0,0 +1,51 @@ +{ + "build": { + "arduino":{ + "ldscript": "esp32s3_out.ld", + "partitions": "default_8MB.csv" + }, + "core": "esp32", + "extra_flags": [ + "-DARDUINO_ESP32S3_DEV", + "-DARDUINO_USB_MODE=1", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1", + "-DBOARD_HAS_PSRAM" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [ + [ + "0x303A", + "0x1001" + ] + ], + "mcu": "esp32s3", + "variant": "esp32s3" + }, + "connectivity": [ + "wifi" + ], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": [ + "esp-builtin" + ], + "openocd_target": "esp32s3.cfg" + }, + "frameworks": [ + "arduino", + "espidf" + ], + "name": "My ESP32-S3-DevKitN8R2 (8 MB QD, 2M PSRAM)", + "upload": { + "flash_size": "8MB", + "maximum_ram_size": 524288, + "maximum_size": 8388608, + "require_upload_port": true, + "speed": 460800 + }, + "url": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/hw-reference/esp32s3/user-guide-devkitc-1.html", + "vendor": "Espressif" +} diff --git a/boards/wroom-32d-8mb.json b/boards/wroom-32d-8mb.json new file mode 100644 index 0000000..b39c73d --- /dev/null +++ b/boards/wroom-32d-8mb.json @@ -0,0 +1,37 @@ +{ + "build": { + "arduino":{ + "ldscript": "esp32_out.ld" + }, + "core": "esp32", + "extra_flags": "-DARDUINO_ESP32_DEV", + "f_cpu": "240000000L", + "f_flash": "40000000L", + "flash_mode": "dio", + "mcu": "esp32", + "variant": "esp32" + }, + "connectivity": [ + "wifi", + "bluetooth", + "ethernet", + "can" + ], + "debug": { + "openocd_board": "esp-wroom-32.cfg" + }, + "frameworks": [ + "arduino", + "espidf" + ], + "name": "WROOM-32D DEV MODULE", + "upload": { + "flash_size": "8MB", + "maximum_ram_size": 327680, + "maximum_size": 8388608, + "require_upload_port": true, + "speed": 460800 + }, + "url": "https://en.wikipedia.org/wiki/ESP32", + "vendor": "Consius" + } \ No newline at end of file diff --git a/data/ata-boothifier-upgrade.html b/data/ata-boothifier-upgrade.html new file mode 100644 index 0000000..3327522 --- /dev/null +++ b/data/ata-boothifier-upgrade.html @@ -0,0 +1,508 @@ + + + + + + ATA Firmware Update + + + + +

ATA Firmware Update

+ + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+ +
+ + +
+ + +
+ + + + +
+ + +
+ + +
+ + +
+ + +
+
+ + +
+ +
+ + + + diff --git a/data/boards/board14.json b/data/boards/board14.json new file mode 100644 index 0000000..025edf3 --- /dev/null +++ b/data/boards/board14.json @@ -0,0 +1,30 @@ +{ + "rgb1": 3, + "rgb2": 46, + "btn1": 8, + "btn2": 19, + "btn3": 0, + "buzzer": 37, + "touch1": 9, + "touch2": 10, + "touch3": 11, + "touch4": 12, + "touch5": 13, + "shield": 9, + "relay1": 1, + "relay2": 2, + "relay3": 3, + "relay4": 4, + "stat1": 17, + "stat2": 18, + "adc1": 20, + "oled_dc": 7, + "oled_rst": 6, + "oled_mosi": 5, + "oled_sck": 4, + "oled_cs": 15, + "ext1": 35, + "ext2": 36, + "rf433tx": 38, + "rf433rx": 16 +} \ No newline at end of file diff --git a/data/boards/board15.json b/data/boards/board15.json new file mode 100644 index 0000000..c092deb --- /dev/null +++ b/data/boards/board15.json @@ -0,0 +1,30 @@ +{ + "rgb1": 3, + "rgb2": 9, + "btn1": 8, + "btn2": 18, + "btn3": 0, + "buzzer": 42, + "touch1": 11, + "touch2": 12, + "touch3": 13, + "touch4": 21, + "touch5": -1, + "shield": 14, + "relay1": 38, + "relay2": 48, + "relay3": 47, + "relay4": 21, + "stat1": 39, + "stat2": 37, + "adc1": 10, + "oled_dc": 7, + "oled_rst": 6, + "oled_mosi": 5, + "oled_sck": 4, + "oled_cs": 15, + "ext1": 41, + "ext2": -1, + "rf433tx": 40, + "rf433rx": 16 +} \ No newline at end of file diff --git a/data/boards/test.json b/data/boards/test.json new file mode 100644 index 0000000..97bfaa6 --- /dev/null +++ b/data/boards/test.json @@ -0,0 +1,30 @@ +{ + "rgb1": 3, + "rgb2": 38, + "btn1": 8, + "btn2": 19, + "btn3": -1, + "buzzer": -1, + "touch1": -1, + "touch2": -1, + "touch3": -1, + "touch4": -1, + "touch5": -1, + "shield": -1, + "relay1": 1, + "relay2": 2, + "relay3": 3, + "relay4": 4, + "stat1": -1, + "stat2": -1, + "adc1": 10, + "oled_dc": 7, + "oled_rst": 6, + "oled_mosi": 5, + "oled_sck": 4, + "oled_cs": 15, + "ext1": -1, + "ext2": -1, + "rf433tx": -1, + "rf433rx": -1 +} \ No newline at end of file diff --git a/data/booths/custom.json b/data/booths/custom.json new file mode 100644 index 0000000..e01c81a --- /dev/null +++ b/data/booths/custom.json @@ -0,0 +1,135 @@ +{ + "mode": 0, + "buttons": + [ + { + "en": true + }, + { + "en": true + }, + { + "en": true + } + ], +"pwmout": + [ + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + } + ], + "ramp-lights": + [ + { + "en": true, + "relay-index": 0, + "button-index": 0, + "min": 5.0, + "max": 100.0, + "step": 1.5, + "skip-count": 5, + "vision": true + }, + { + "en": true, + "relay-index": 1, + "button-index": 1, + "min": 5.0, + "max": 100.0, + "step": 1.5, + "skip-count": 5, + "vision": true + } + ], + "oled": { + "en": false, + "height": 64, + "width": 128 + }, + "t-sensor": { + "en": true, + "addr": 72, + "sp1": 85.0, + "fan-pwr1": 50.0, + "sp2": 90.0, + "fan-pwr2": 100.0, + "hyst": 1.0, + "relay": 3, + "interval": 5000 + }, + "adc": { + "ain1_factor": 1.0 + }, + "status-led":{ + "interval": 250 + }, + "strips": + [ + { + "en": true, + "size": 138, + "chip": "WS6812", + "rgb-order": "rgb", + "shift":-27, + "offset": 0, + "power-div": 0, + "i2s-ch": 0, + "core": 1 + }, + { + "en": true, + "size": 30, + "chip": "WS6812", + "rgb-order": "rgb", + "shift":-27, + "offset": 0, + "power-div": 0, + "i2s-ch": 0, + "core": 1 + } + ], + "rx433": { + "en": "true" + }, + "tx433": { + "en": "true" + }, + "ble":{ + "en": true, + "name": "Ata_Lights", + "core": 1 + } +} diff --git a/data/booths/helio-flare.json b/data/booths/helio-flare.json new file mode 100644 index 0000000..f40c0e9 --- /dev/null +++ b/data/booths/helio-flare.json @@ -0,0 +1,137 @@ +{ + "mode": 0, + "buttons": + [ + { + "en": true + }, + { + "en": true + }, + { + "en": true + } + ], +"pwmout": + [ + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + } + ], + "ramp-lights": + [ + { + "en": true, + "relay-index": 0, + "button-index": 0, + "min": 5.0, + "max": 100.0, + "step": 1.5, + "skip-count": 5, + "vision": true + }, + { + "en": true, + "relay-index": 1, + "button-index": 1, + "min": 5.0, + "max": 100.0, + "step": 1.5, + "skip-count": 5, + "vision": true + } + ], + "oled": { + "en": false, + "height": 64, + "width": 128 + }, + "t-sensor": { + "en": true, + "addr": 72, + "sp1": 85.0, + "fan-pwr1": 50.0, + "sp2": 90.0, + "fan-pwr2": 100.0, + "hyst": 1.0, + "relay": 3, + "interval": 5000 + }, + "adc": { + "ain1_factor": 1.0 + }, + "status-led":{ + "interval": 250 + }, + "strips": + [ + { + "en": true, + "size": 168, + "chip": "SK6812", + "rgb-order": "RGB", + "shift":-42, + "offset": 0, + "power-div": 0, + "i2s-ch": 0, + "core": 1, + "bright": 200 + }, + { + "en": true, + "size": 20, + "chip": "SK6812", + "rgb-order": "RGB", + "shift":0, + "offset": 0, + "power-div": 0, + "i2s-ch": 0, + "core": 1, + "bright": 200 + } + ], + "rx433": { + "en": "true" + }, + "tx433": { + "en": "true" + }, + "ble":{ + "en": true, + "name": "Ata_Lights", + "core": 1 + } +} diff --git a/data/booths/helio-posh.json b/data/booths/helio-posh.json new file mode 100644 index 0000000..396c345 --- /dev/null +++ b/data/booths/helio-posh.json @@ -0,0 +1,134 @@ +{ + "buttons": + [ + { + "en": true + }, + { + "en": true + }, + { + "en": true + } + ], +"pwmout": + [ + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + } + ], + "ramp-lights": + [ + { + "en": true, + "relay-index": 0, + "button-index": 0, + "min": 5.0, + "max": 100.0, + "step": 1.5, + "skip-count": 5, + "vision": true + }, + { + "en": true, + "relay-index": 1, + "button-index": 1, + "min": 5.0, + "max": 100.0, + "step": 1.5, + "skip-count": 5, + "vision": true + } + ], + "oled": { + "en": false, + "height": 64, + "width": 128 + }, + "t-sensor": { + "en": true, + "addr": 72, + "sp1": 85.0, + "fan-pwr1": 50.0, + "sp2": 90.0, + "fan-pwr2": 100.0, + "hyst": 1.0, + "relay": 3, + "interval": 5000 + }, + "adc": { + "ain1_factor": 1.0 + }, + "status-led":{ + "interval": 250 + }, + "strips": + [ + { + "en": true, + "size": 138, + "chip": "SK6812", + "rgb-order": "rgb", + "shift":-27, + "offset": 0, + "power-div": 0, + "i2s-ch": 0, + "core": 1 + }, + { + "en": true, + "size": 30, + "chip": "SK6812", + "rgb-order": "rgb", + "shift":-27, + "offset": 0, + "power-div": 0, + "i2s-ch": 0, + "core": 1 + } + ], + "rx433": { + "en": "true" + }, + "tx433": { + "en": "true" + }, + "ble":{ + "en": true, + "name": "Ata_Lights", + "core": 1 + } +} diff --git a/data/booths/helio-sport.json b/data/booths/helio-sport.json new file mode 100644 index 0000000..46efdbe --- /dev/null +++ b/data/booths/helio-sport.json @@ -0,0 +1,135 @@ +{ + "mode": 0, + "buttons": + [ + { + "en": true + }, + { + "en": true + }, + { + "en": true + } + ], +"pwmout": + [ + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + } + ], + "ramp-lights": + [ + { + "en": true, + "relay-index": 0, + "button-index": 0, + "min": 5.0, + "max": 100.0, + "step": 1.5, + "skip-count": 5, + "vision": true + }, + { + "en": true, + "relay-index": 1, + "button-index": 1, + "min": 5.0, + "max": 100.0, + "step": 1.5, + "skip-count": 5, + "vision": true + } + ], + "oled": { + "en": false, + "height": 64, + "width": 128 + }, + "t-sensor": { + "en": true, + "addr": 72, + "sp1": 85.0, + "fan-pwr1": 50.0, + "sp2": 90.0, + "fan-pwr2": 100.0, + "hyst": 1.0, + "relay": 3, + "interval": 5000 + }, + "adc": { + "ain1_factor": 1.0 + }, + "status-led":{ + "interval": 250 + }, + "strips": + [ + { + "en": true, + "size": 138, + "chip": "SK6812", + "rgb-order": "rgb", + "shift":-27, + "offset": 0, + "power-div": 0, + "i2s-ch": 0, + "core": 1 + }, + { + "en": true, + "size": 30, + "chip": "SK6812", + "rgb-order": "rgb", + "shift":-27, + "offset": 0, + "power-div": 0, + "i2s-ch": 0, + "core": 1 + } + ], + "rx433": { + "en": "true" + }, + "tx433": { + "en": "true" + }, + "ble":{ + "en": true, + "name": "Ata_Lights", + "core": 1 + } +} diff --git a/data/booths/light-stik.json b/data/booths/light-stik.json new file mode 100644 index 0000000..824685e --- /dev/null +++ b/data/booths/light-stik.json @@ -0,0 +1,138 @@ +{ + "mode": "stik", + "buttons": + [ + { + "en": true + }, + { + "en": true + }, + { + "en": true + } + ], + "pwmout": + [ + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + } + ], + "ramp-lights": + [ + { + "en": true, + "relay-index": 0, + "button-index": 0, + "min": 5.0, + "max": 100.0, + "step": 1.5, + "skip-count": 5, + "vision": true + }, + { + "en": true, + "relay-index": 1, + "button-index": 1, + "min": 5.0, + "max": 100.0, + "step": 1.5, + "skip-count": 5, + "vision": true + } + ], + "oled": { + "en": false, + "height": 64, + "width": 128 + }, + "t-sensor": { + "en": true, + "addr": 72, + "sp1": 85.0, + "fan-pwr1": 50.0, + "sp2": 90.0, + "fan-pwr2": 100.0, + "hyst": 1.0, + "relay": 3, + "interval": 5000 + }, + "adc": { + "ain1_factor": 1.0 + }, + "status-led":{ + "interval": 250 + }, + "strips": + [ + { + "en": true, + "size": 144, + "chip": "SK6812", + "rgb-order": "RGB", + "shift":0, + "offset": 0, + "power-div": 0, + "i2s-ch": 0, + "core": 1, + "bright": 200 + }, + { + "en": false, + "size": 20, + "chip": "SK6812", + "rgb-order": "RGB", + "shift":0, + "offset": 0, + "power-div": 0, + "i2s-ch": 0, + "core": 1, + "bright": 200 + } + ], + "rx433": { + "en": "true" + }, + "tx433": { + "en": "true" + }, + "ble":{ + "en": true, + "name": "FlashStik", + "core": 1 + } + } + \ No newline at end of file diff --git a/data/booths/lumia-m.json b/data/booths/lumia-m.json new file mode 100644 index 0000000..2f66e2c --- /dev/null +++ b/data/booths/lumia-m.json @@ -0,0 +1,140 @@ +{ + "mode": 0, + "buttons": + [ + { + "en": true + }, + { + "en": true + }, + { + "en": true + } + ], +"pwmout": + [ + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + } + ], + "ramp-lights": + [ + { + "en": true, + "relay-index": 0, + "button-index": 0, + "min": 5.0, + "max": 100.0, + "step": 1.5, + "skip-count": 5, + "vision": true + }, + { + "en": true, + "relay-index": 1, + "button-index": 1, + "min": 5.0, + "max": 100.0, + "step": 1.5, + "skip-count": 5, + "vision": true + } + ], + "oled": { + "en": false, + "height": 64, + "width": 128 + }, + "t-sensor": { + "en": true, + "addr": 72, + "sp1": 85.0, + "fan-pwr1": 50.0, + "sp2": 90.0, + "fan-pwr2": 100.0, + "hyst": 1.0, + "relay": 3, + "interval": 5000 + }, + "adc": { + "ain1_factor": 1.0 + }, + "status-led":{ + "interval": 250 + }, + "buzzer":{ + "en": true + }, + "strips": + [ + { + "en": true, + "size": 168, + "chip": "SK6812", + "rgb-order": "RGB", + "shift": 0, + "offset": 0, + "power-div": 0, + "i2s-ch": 0, + "core": 1, + "bright": 200 + }, + { + "en": true, + "size": 30, + "chip": "SK6812", + "rgb-order": "RGB", + "shift": 0, + "offset": 0, + "power-div": 0, + "i2s-ch": 0, + "core": 1, + "bright": 200 + } + ], + "rx433": { + "en": "true" + }, + "tx433": { + "en": "true" + }, + "ble":{ + "en": true, + "name": "Ata_Lights", + "core": 1 + } +} diff --git a/data/booths/lumia-spectra.json b/data/booths/lumia-spectra.json new file mode 100644 index 0000000..7a39950 --- /dev/null +++ b/data/booths/lumia-spectra.json @@ -0,0 +1,135 @@ +{ + "mode": 0, + "buttons": + [ + { + "en": true + }, + { + "en": true + }, + { + "en": true + } + ], +"pwmout": + [ + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + } + ], + "ramp-lights": + [ + { + "en": true, + "relay-index": 0, + "button-index": 0, + "min": 5.0, + "max": 100.0, + "step": 1.5, + "skip-count": 5, + "vision": true + }, + { + "en": true, + "relay-index": 1, + "button-index": 1, + "min": 5.0, + "max": 100.0, + "step": 1.5, + "skip-count": 5, + "vision": true + } + ], + "oled": { + "en": false, + "height": 64, + "width": 128 + }, + "t-sensor": { + "en": true, + "addr": 72, + "sp1": 85.0, + "fan-pwr1": 50.0, + "sp2": 90.0, + "fan-pwr2": 100.0, + "hyst": 1.0, + "relay": 3, + "interval": 5000 + }, + "adc": { + "ain1_factor": 1.0 + }, + "status-led":{ + "interval": 250 + }, + "strips": + [ + { + "en": true, + "size": 138, + "chip": "SK6812", + "rgb-order": "rgb", + "shift":-27, + "offset": 0, + "power-div": 0, + "i2s-ch": 0, + "core": 1 + }, + { + "en": true, + "size": 30, + "chip": "SK6812", + "rgb-order": "rgb", + "shift":-27, + "offset": 0, + "power-div": 0, + "i2s-ch": 0, + "core": 1 + } + ], + "rx433": { + "en": "true" + }, + "tx433": { + "en": "true" + }, + "ble":{ + "en": true, + "name": "Ata_Lights", + "core": 1 + } +} diff --git a/data/booths/lumia-xl.json b/data/booths/lumia-xl.json new file mode 100644 index 0000000..3a814b3 --- /dev/null +++ b/data/booths/lumia-xl.json @@ -0,0 +1,159 @@ +{ + "mode": 0, + "buttons": + [ + { + "en": true + }, + { + "en": true + }, + { + "en": true + } + ], +"pwmout": + [ + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + } + ], + "ramp-lights": + [ + { + "en": true, + "relay-index": 0, + "button-index": 0, + "min": 5.0, + "max": 100.0, + "step": 1.5, + "skip-count": 5, + "vision": true + }, + { + "en": true, + "relay-index": 1, + "button-index": 1, + "min": 5.0, + "max": 100.0, + "step": 1.5, + "skip-count": 5, + "vision": true + } + ], + "oled": { + "en": false, + "height": 64, + "width": 128 + }, + "t-sensor": { + "en": true, + "addr": 72, + "sp1": 85.0, + "fan-pwr1": 50.0, + "sp2": 90.0, + "fan-pwr2": 100.0, + "hyst": 1.0, + "relay": 3, + "interval": 5000 + }, + "adc": { + "ain1_factor": 1.0 + }, + "status-led":{ + "interval": 250 + }, + "strips": + [ + { + "en": true, + "size": 138, + "chip": "SK6812", + "rgb-order": "rgb", + "shift":-27, + "offset": 0, + "power-div": 0, + "i2s-ch": 0, + "core": 1 + }, + { + "en": true, + "size": 30, + "chip": "SK6812", + "rgb-order": "rgb", + "shift":-27, + "offset": 0, + "power-div": 0, + "i2s-ch": 0, + "core": 1 + } + ], + "rx433": { + "en": "true" + }, + "tx433": { + "en": "true" + }, + "ble":{ + "en": true, + "name": "Ata_Lights", + "core": 1 + }, + "wifi-client":{ + "en": true, + "mdns-name": "atadev", + "ssid": "padillaguest", + "pass": "padillaguest" + }, + "wifi-ap":{ + "ssid": "ATA_AP", + "append-id": true, + "pass": "12345678", + "ip": [192,168,10.1], + "gateway": [192,168,10,200], + "subnet": [255,255,255,0] + }, + "animation1":{ + "white-max-time": 30, + "white-min": 20, + "white-max": 100, + "toggle-cycles": 2, + "comet-size": 0.2, + "comet-fade": 96, + "fire-cooling": 66, + "fire-sparking": 62 + } +} diff --git a/data/booths/m1.json b/data/booths/m1.json new file mode 100644 index 0000000..7a39950 --- /dev/null +++ b/data/booths/m1.json @@ -0,0 +1,135 @@ +{ + "mode": 0, + "buttons": + [ + { + "en": true + }, + { + "en": true + }, + { + "en": true + } + ], +"pwmout": + [ + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + } + ], + "ramp-lights": + [ + { + "en": true, + "relay-index": 0, + "button-index": 0, + "min": 5.0, + "max": 100.0, + "step": 1.5, + "skip-count": 5, + "vision": true + }, + { + "en": true, + "relay-index": 1, + "button-index": 1, + "min": 5.0, + "max": 100.0, + "step": 1.5, + "skip-count": 5, + "vision": true + } + ], + "oled": { + "en": false, + "height": 64, + "width": 128 + }, + "t-sensor": { + "en": true, + "addr": 72, + "sp1": 85.0, + "fan-pwr1": 50.0, + "sp2": 90.0, + "fan-pwr2": 100.0, + "hyst": 1.0, + "relay": 3, + "interval": 5000 + }, + "adc": { + "ain1_factor": 1.0 + }, + "status-led":{ + "interval": 250 + }, + "strips": + [ + { + "en": true, + "size": 138, + "chip": "SK6812", + "rgb-order": "rgb", + "shift":-27, + "offset": 0, + "power-div": 0, + "i2s-ch": 0, + "core": 1 + }, + { + "en": true, + "size": 30, + "chip": "SK6812", + "rgb-order": "rgb", + "shift":-27, + "offset": 0, + "power-div": 0, + "i2s-ch": 0, + "core": 1 + } + ], + "rx433": { + "en": "true" + }, + "tx433": { + "en": "true" + }, + "ble":{ + "en": true, + "name": "Ata_Lights", + "core": 1 + } +} diff --git a/data/booths/marquee.json b/data/booths/marquee.json new file mode 100644 index 0000000..7a39950 --- /dev/null +++ b/data/booths/marquee.json @@ -0,0 +1,135 @@ +{ + "mode": 0, + "buttons": + [ + { + "en": true + }, + { + "en": true + }, + { + "en": true + } + ], +"pwmout": + [ + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + } + ], + "ramp-lights": + [ + { + "en": true, + "relay-index": 0, + "button-index": 0, + "min": 5.0, + "max": 100.0, + "step": 1.5, + "skip-count": 5, + "vision": true + }, + { + "en": true, + "relay-index": 1, + "button-index": 1, + "min": 5.0, + "max": 100.0, + "step": 1.5, + "skip-count": 5, + "vision": true + } + ], + "oled": { + "en": false, + "height": 64, + "width": 128 + }, + "t-sensor": { + "en": true, + "addr": 72, + "sp1": 85.0, + "fan-pwr1": 50.0, + "sp2": 90.0, + "fan-pwr2": 100.0, + "hyst": 1.0, + "relay": 3, + "interval": 5000 + }, + "adc": { + "ain1_factor": 1.0 + }, + "status-led":{ + "interval": 250 + }, + "strips": + [ + { + "en": true, + "size": 138, + "chip": "SK6812", + "rgb-order": "rgb", + "shift":-27, + "offset": 0, + "power-div": 0, + "i2s-ch": 0, + "core": 1 + }, + { + "en": true, + "size": 30, + "chip": "SK6812", + "rgb-order": "rgb", + "shift":-27, + "offset": 0, + "power-div": 0, + "i2s-ch": 0, + "core": 1 + } + ], + "rx433": { + "en": "true" + }, + "tx433": { + "en": "true" + }, + "ble":{ + "en": true, + "name": "Ata_Lights", + "core": 1 + } +} diff --git a/data/booths/roamer-big.json b/data/booths/roamer-big.json new file mode 100644 index 0000000..90face0 --- /dev/null +++ b/data/booths/roamer-big.json @@ -0,0 +1,136 @@ +{ + "mode": "roamer", + "button": 0, + "buttons": + [ + { + "en": true + }, + { + "en": true + }, + { + "en": true + } + ], +"pwmout": + [ + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + } + ], + "ramp-lights": + [ + { + "en": true, + "relay-index": 0, + "button-index": 0, + "min": 5.0, + "max": 100.0, + "step": 1.5, + "skip-count": 5, + "vision": true + }, + { + "en": true, + "relay-index": 1, + "button-index": 1, + "min": 5.0, + "max": 100.0, + "step": 1.5, + "skip-count": 5, + "vision": true + } + ], + "oled": { + "en": false, + "height": 64, + "width": 128 + }, + "t-sensor": { + "en": true, + "addr": 72, + "sp1": 85.0, + "fan-pwr1": 50.0, + "sp2": 90.0, + "fan-pwr2": 100.0, + "hyst": 1.0, + "relay": 3, + "interval": 5000 + }, + "adc": { + "ain1_factor": 1.0 + }, + "status-led":{ + "interval": 250 + }, + "strips": + [ + { + "en": true, + "size": 168, + "chip": "SK6812", + "rgb-order": "rgb", + "shift":-42, + "offset": 0, + "power-div": 0, + "i2s-ch": 0, + "core": 1 + }, + { + "en": false, + "size": 30, + "chip": "SK6812", + "rgb-order": "rgb", + "shift":-27, + "offset": 0, + "power-div": 0, + "i2s-ch": 0, + "core": 1 + } + ], + "rx433": { + "en": "true" + }, + "tx433": { + "en": "true" + }, + "ble":{ + "en": true, + "name": "Ata_Lights", + "core": 1 + } +} diff --git a/data/booths/roamer.json b/data/booths/roamer.json new file mode 100644 index 0000000..146b248 --- /dev/null +++ b/data/booths/roamer.json @@ -0,0 +1,136 @@ +{ + "mode": "roamer", + "button": 0, + "buttons": + [ + { + "en": true + }, + { + "en": true + }, + { + "en": true + } + ], +"pwmout": + [ + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + } + ], + "ramp-lights": + [ + { + "en": true, + "relay-index": 0, + "button-index": 0, + "min": 5.0, + "max": 100.0, + "step": 1.5, + "skip-count": 5, + "vision": true + }, + { + "en": true, + "relay-index": 1, + "button-index": 1, + "min": 5.0, + "max": 100.0, + "step": 1.5, + "skip-count": 5, + "vision": true + } + ], + "oled": { + "en": false, + "height": 64, + "width": 128 + }, + "t-sensor": { + "en": true, + "addr": 72, + "sp1": 85.0, + "fan-pwr1": 50.0, + "sp2": 90.0, + "fan-pwr2": 100.0, + "hyst": 1.0, + "relay": 3, + "interval": 5000 + }, + "adc": { + "ain1_factor": 1.0 + }, + "status-led":{ + "interval": 250 + }, + "strips": + [ + { + "en": true, + "size": 138, + "chip": "SK6812", + "rgb-order": "rgb", + "shift":-27, + "offset": 0, + "power-div": 0, + "i2s-ch": 0, + "core": 1 + }, + { + "en": false, + "size": 30, + "chip": "SK6812", + "rgb-order": "rgb", + "shift":-27, + "offset": 0, + "power-div": 0, + "i2s-ch": 0, + "core": 1 + } + ], + "rx433": { + "en": "true" + }, + "tx433": { + "en": "true" + }, + "ble":{ + "en": true, + "name": "Ata_Lights", + "core": 1 + } +} diff --git a/data/booths/testbooth.json b/data/booths/testbooth.json new file mode 100644 index 0000000..9f33075 --- /dev/null +++ b/data/booths/testbooth.json @@ -0,0 +1,135 @@ +{ + "mode": 0, + "buttons": + [ + { + "en": true + }, + { + "en": true + }, + { + "en": true + } + ], +"pwmout": + [ + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + } + ], + "ramp-lights": + [ + { + "en": false, + "relay-index": 0, + "button-index": 0, + "min": 5.0, + "max": 100.0, + "step": 1.5, + "skip-count": 5, + "vision": true + }, + { + "en": false, + "relay-index": 1, + "button-index": 1, + "min": 5.0, + "max": 100.0, + "step": 1.5, + "skip-count": 5, + "vision": true + } + ], + "oled": { + "en": false, + "height": 64, + "width": 128 + }, + "t-sensor": { + "en": false, + "addr": 72, + "sp1": 85.0, + "fan-pwr1": 50.0, + "sp2": 90.0, + "fan-pwr2": 100.0, + "hyst": 1.0, + "relay": 3, + "interval": 5000 + }, + "adc": { + "ain1_factor": 1.0 + }, + "status-led":{ + "interval": 250 + }, + "strips": + [ + { + "en": true, + "size": 138, + "chip": "WS6812", + "rgb-order": "rgb", + "shift":-27, + "offset": 0, + "power-div": 0, + "i2s-ch": 0, + "core": 1 + }, + { + "en": true, + "size": 30, + "chip": "WS6812", + "rgb-order": "rgb", + "shift":-27, + "offset": 0, + "power-div": 0, + "i2s-ch": 0, + "core": 1 + } + ], + "rx433": { + "en": false + }, + "tx433": { + "en": false + }, + "ble":{ + "en": true, + "name": "Ata_Lights", + "core": 1 + } +} diff --git a/data/css/global-style.css b/data/css/global-style.css new file mode 100644 index 0000000..a4edce2 --- /dev/null +++ b/data/css/global-style.css @@ -0,0 +1,79 @@ +body { + /*background-color: #f7f7f7;*/ + font-family: Tahoma, Arial, sans-serif; + font-size: small; + margin: 0; + display: flex; + min-height: 100vh; +} +.main-container { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + padding: 20px; +} +h1 { + margin-top: 0; +} +input{ + cursor:pointer; +} +input[type="number"]{ + width: 100px; +} +#submit { + width:120px; +} +select{ + width:160px; +} +#select-path { + width:250px; +} +#select-dir { + width:90px; +} +#spacer-50 { + height: 50px; +} +#spacer-20 { + height: 20px; +} +#spacer-10 { + height: 10px; +} +table { + /*background-color: #dddddd;*/ + border-collapse: collapse; + width:600px; + margin: 0 auto; + overflow: visible; +} +td, th { + /*border: 1px solid #dddddd;*/ + text-align: left; + padding: 2px; +} +#first_td_th { + width:400px; +} +fieldset { + width:620px; + /*background-color: #f7f7f7;*/ + border-radius: 10px; + border-color: blue; +} +#format-notice { + color: #ff0000; +} +legend { + display: flex; + justify-content: center; + background-color:white; + background-blend-mode: darken; + border-radius: 10px; + padding: 1px 8px 2px 8px; + border-style: solid; + border-width: 1.0; +} \ No newline at end of file diff --git a/data/css/nav.css b/data/css/nav.css new file mode 100644 index 0000000..f74f175 --- /dev/null +++ b/data/css/nav.css @@ -0,0 +1,72 @@ +.navbar { + width: 100%; + background-color: black; + border-bottom: 2px solid white; + display: flex; + justify-content: flex-start; + align-items: center; + position: sticky; + top: 0; + z-index: 1000; + } + .navbar-left { + padding: 0 10px; + } + .navbar ul { + list-style-type: none; + margin: 0; + padding: 0; + display: flex; + align-items: center; + position: relative; /* Ensure relative positioning for the submenu */ + } + .navbar ul li { + float: left; + position: relative; /* Ensure relative positioning for the submenu */ + } + .navbar ul li a { + display: block; + color: white; + text-align: center; + padding: 14px 16px; + text-decoration: none; + border-right: 1px solid white; + } + .navbar ul li a:hover { + background-color: grey; + } + .navbar ul li a.disabled { + color: grey; + pointer-events: none; + } + .navbar ul .submenu { + display: none; + position: absolute; + top: 100%; + left: 0; + background-color: black; + list-style-type: none; + margin: 0; + padding: 0; + border-top: 2px solid white; + z-index: 1000; + } + .navbar ul .submenu li { + float: none; + border-right: none; + } + .navbar ul .submenu li a { + padding: 10px 16px; + border-bottom: 1px solid white; + } + .navbar ul .submenu li a:hover { + background-color: grey; + } + .navbar ul li:hover > .submenu { + display: block; + } + .nav-image { + height: 40px; + width: auto; + } + \ No newline at end of file diff --git a/data/favicon.ico b/data/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..3030261a3e26621938f916036d0f10aff5f0e208 GIT binary patch literal 1150 zcmaKrZA?>V6vvMdT9jlnny85yF!%-%Gt|w@eK5E!Vl=uhDgy`D;1orXv6#7_qEUfX zDMY0-OkO7#2aRCV7FvtYa*>x(u%%ror8uilYztPQQ1tF!G)80My*c;hKF|F<=k0%p zX5gMPhwu!hgMLKwiD(rJOIUfkju#R3K6e@ad`y~DJN2epxdUf=7Yt_ULtH{l$XI^M z!l^=409)Ef?6sy(j%ZDGQRlH?>Nx7)bf*o58c#e<=+7`;d6uQGcI9d$<03{hS)xd^ z)f9)fHl=fW>IEckKQw(#W8xSw2Vao-fIG1Duq#TNYQJgtvMf^eV zzZm|Pdqm9A@p5TUx8lOOiryVnc|+@p_|C8}fnznvV&8D<_P*Yv+&A%?YX8fPU1@_{ z_zedi3w{^)=kz-uB+aXU?zdbwZx5{Q*+W;K zZJ6deQXHgtJ3oGypAgK?ZseZ@z6|v%!~6&^KQT{lm8@uMJn65KXHyfCHqCzcFr{0` zcHW5j?f1w0&V_y!gJ1G0|KU7up^C#^V2KsCeu&V>mqb=BQNv84GiQi0W_+~$(MRS0 O_VJJJUmxQMBKjZfDN?=w literal 0 HcmV?d00001 diff --git a/data/flashstik-reg.html b/data/flashstik-reg.html new file mode 100644 index 0000000..7cef51f --- /dev/null +++ b/data/flashstik-reg.html @@ -0,0 +1,472 @@ + + + + + + ATA Light Stick Reg + + + + +

ATA Flash-Stick Link/Registration

+ + +
+ + +
+ +
+ + +
+ +
+ + + +
+ + + + + + + diff --git a/data/images/atalogo.png b/data/images/atalogo.png new file mode 100644 index 0000000000000000000000000000000000000000..40f3ee677364375e76d185ba6271c3c85d3b2c1a GIT binary patch literal 16690 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 C#3g+I literal 0 HcmV?d00001 diff --git a/data/js/event-box.js b/data/js/event-box.js new file mode 100644 index 0000000..cb01b1b --- /dev/null +++ b/data/js/event-box.js @@ -0,0 +1,442 @@ +class EventBox extends HTMLElement { + constructor() { + super(); + this.attachShadow({ mode: 'open' }); + this.shadowRoot.innerHTML = ` + +
+
+ Event0
+
+
+ + +
+
+ +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+ +
+
+
+ +
+
+ `; + this.Title = this.shadowRoot.querySelector('#center-text'); + this.hueSelect = this.shadowRoot.querySelector('#hue-selector'); + this.AnimationList = this.shadowRoot.getElementById('animation-list'); + this.Speed = this.shadowRoot.getElementById('speed'); + this.HueRange = this.shadowRoot.querySelector('#huerange'); + this.Param1 = this.shadowRoot.querySelector('#param1'); + this.Param2 = this.shadowRoot.querySelector('#param2'); + this.Check1 = this.shadowRoot.querySelector('#check1'); + this.Check2 = this.shadowRoot.querySelector('#check2'); + this.Check3 = this.shadowRoot.querySelector('#check3'); + this.Check4 = this.shadowRoot.querySelector('#check4'); + + this.AnimationListLabel = this.shadowRoot.querySelector('#list-label'); + this.SpeedLabel = this.shadowRoot.querySelector('#speed-label'); + this.HueRangeLabel = this.shadowRoot.querySelector('#huerange-label'); + this.Param1Label = this.shadowRoot.getElementById('param1-label'); + this.Param2Label = this.shadowRoot.getElementById('param2-label'); + this.Check1Label = this.shadowRoot.getElementById('check1-label'); + this.Check2Label = this.shadowRoot.getElementById('check2-label'); + this.Check3Label = this.shadowRoot.getElementById('check3-label'); + this.Check4Label = this.shadowRoot.getElementById('check4-label'); + + this.AnimationPropsJson; + + this.SpeedCaption = ""; + this.HueRangeCaption = ""; + this.Param1Caption = ""; + this.Param2Caption = ""; + this.Check1Caption = ""; + this.Check2Caption = ""; + this.Check3Caption = ""; + + this.setSpeedCaption(`Speed: `); + this.Speed.min = 0; + this.Speed.max = 100; + + this.setHueRangeCaption(`Hue Range: `); + this.HueRange.min = 0; + this.HueRange.max = 360; + + this.setParam1Caption(`Param1: `); + this.Param1.min = 0; + this.Param1.max = 100; + + this.setParam2Caption(`Param2: `); + this.Param2.min = 0; + this.Param2.max = 100; + + this.setCheck1Caption(`Check1`); + this.setCheck2Caption(`Check2`); + this.setCheck3Caption(`Check3`); + this.setCheck4Caption(`50% Lum`); + + this.Index = 0; + + this.Speed.addEventListener('input', () => { this.updateSpeedLabel(); }); + this.HueRange.addEventListener('input', () => { this.updateHueRangeLabel(); }); + this.Param1.addEventListener('input', () => { this.updateParam1Label(); }); + this.Param2.addEventListener('input', () => { this.updateParam2Label(); }); + + this.TryButton = this.shadowRoot.querySelector('#try-button'); + this.TryButton.addEventListener('click', () => { + this.handleTryButtonClick(); + }); + + this.AnimationList.addEventListener('change', this.handleAnimationListChange.bind(this)); + } + + setIndex(i){ this.Index = i; } + getIndex(){ return this.Index; } + updateSpeedLabel(){ + if(!this.Speed.hidden){ + this.SpeedLabel.innerHTML = this.SpeedCaption + this.getSpeedValue(); + } + } + updateHueRangeLabel(){ + if(!this.HueRange.hidden){ + this.HueRangeLabel.innerHTML = this.HueRangeCaption + this.getHueRangeValue(); + } + } + updateParam1Label(){ + if(!this.Param1.hidden){ + this.Param1Label.innerHTML = this.Param1Caption + this.getParam1Value(); + } + } + updateParam2Label(){ + if(!this.Param2.hidden){ + this.Param2Label.innerHTML = this.Param2Caption + this.getParam2Value(); + } + } + setTitle(text){ + this.Title.textContent = text; + } + addOptionToList(value, text){ + const optionElement = document.createElement('option'); + optionElement.value = value; + optionElement.textContent = text; + this.AnimationList.appendChild(optionElement); + } + setHidden(hidden){ this.hidden = hidden; } + getHidden(){ return this.hidden; } + setHueValue(value){ this.hueSelect.setHue(value); } + getHueValue(){ return this.hueSelect.getSelectedHue(); } + setSpeedValue(value){ + this.Speed.value = value; + this.updateSpeedLabel(); + } + setSpeedCaption(caption){ + let hid = false; + if(caption.trim() === ""){ + hid = true; + } + this.SpeedCaption = caption; + this.SpeedLabel.innerHTML = caption; + this.Speed.hidden = hid; + this.SpeedLabel.hidden = hid; + } + getSpeedValue(){ return this.Speed.value; } + setHueRangeValue(value){ + this.HueRange.value = value; + this.updateHueRangeLabel() + } + setHueRangeCaption(caption){ + let hid = false; + if(caption.trim() === ""){ + hid = true; + } + this.HueRangeCaption = caption; + this.HueRangeLabel.innerHTML = caption; + this.HueRange.hidden = hid; + this.HueRangeLabel.hidden = hid; + } + getHueRangeValue(){ return this.HueRange.value; } + setParam1Value(value){ + this.Param1.value = value; + this.updateParam1Label() + } + setParam1Caption(caption){ + let hid = false; + if(caption.trim() === ""){ + hid = true; + } + this.Param1Caption = caption; + this.Param1Label.innerHTML = caption; + this.Param1.hidden = hid; + this.Param1Label.hidden = hid; + } + getParam1Value(){ return this.Param1.value; } + setParam2Value(value){ + this.Param2.value = value; + this.updateParam2Label() + } + setParam2Caption(caption){ + let hid = false; + if(caption.trim() === ""){ + hid = true; + } + this.Param2Caption = caption; + this.Param2Label.innerHTML = caption; + this.Param2.hidden = hid; + this.Param2Label.hidden = hid; + } + getParam2Value(){ return this.Param2.value; } + + setCheck1Value(value){ this.Check1.checked = value; } + setCheck1Caption(caption){ + let hid = false; + if(caption.trim() === ""){ + hid = true; + } + this.Check1Label.innerHTML = caption; + this.Check1.hidden = hid; + this.Check1Label.hidden = hid; + } + getCheck1Value(){ return this.Check1.checked; } + + setCheck2Value(value){ this.Check2.checked = value; } + setCheck2Caption(caption){ + let hid = false; + if(caption.trim() === ""){ + hid = true; + } + this.Check2Label.innerHTML = caption; + this.Check2.hidden = hid; + this.Check2Label.hidden = hid; + } + getCheck2Value(){ return this.Check2.checked; } + + setCheck3Value(value){ this.Check3.checked = value; } + setCheck3Caption(caption){ + let hid = false; + if(caption.trim() === ""){ + hid = true; + } + this.Check3Label.innerHTML = caption; + this.Check3.hidden = hid; + this.Check3Label.hidden = hid; + } + getCheck3Value(){ return this.Check3.checked; } + + setCheck4Value(value){ this.Check4.checked = value; } + setCheck4Caption(caption){ + let hid = false; + if(caption.trim() === ""){ + hid = true; + } + this.Check4Label.innerHTML = caption; + this.Check4.hidden = hid; + this.Check4Label.hidden = hid; + } + getCheck4Value(){ return this.Check4.checked; } + + setAnimationIndex(index){ + this.AnimationList.selectedIndex = index; + const changeEvent = new Event('change'); + this.AnimationList.dispatchEvent(changeEvent); + } + getAnimationIndex(){ + return this.AnimationList.selectedIndex; + } + + handleTryButtonClick() { + const eventIndexValue = this.getIndex(); + const animIndexValue = this.getAnimationIndex(); + const hueValue = this.getHueValue(); + const speedValue = this.getSpeedValue(); + const colorRangeValue = this.getHueRangeValue(); + const param1Value = this.getParam1Value(); + const param2Value = this.getParam2Value(); + const check1Value = this.getCheck1Value(); + const check2Value = this.getCheck2Value(); + const check3Value = this.getCheck3Value(); + const check4Value = this.getCheck4Value(); + + this.dispatchEvent(new CustomEvent('tryClick', { + detail: { + eventIndex: eventIndexValue, + animIndex: animIndexValue, + hue: hueValue, + speed: speedValue, + colorRange: colorRangeValue, + param1: param1Value, + param2: param2Value, + check1: check1Value, + check2: check2Value, + check3: check3Value, + check4: check4Value + } + })); + } + + setAnimationCaptions(propsJson){ + this.AnimationPropsJson = propsJson; + + //add options to list + let x = 0; + this.AnimationPropsJson.forEach(props => { + this.addOptionToList(x, props.name); + x++; + }); + + } + + handleAnimationListChange(){ + const selectedIndex = this.getAnimationIndex(); + const selectedProps = this.AnimationPropsJson[selectedIndex]; + + this.updateControlProps(selectedProps); + } + + updateControlProps(props){ + this.setSpeedCaption(props.speed); + let s = props['hue-range']; + this.setHueRangeCaption(s || ""); + this.setParam1Caption(props.param1 || ""); + this.setParam2Caption(props.param2 || ""); + this.setCheck1Caption(props.check1 || ""); + this.setCheck2Caption(props.check2 || ""); + this.setCheck3Caption(props.check3 || ""); + this.setCheck4Caption(props.check4 || ""); + + this.updateSpeedLabel(); + this.updateHueRangeLabel(); + this.updateParam1Label(); + this.updateParam2Label(); + } + +} + +customElements.define('event-box', EventBox); + \ No newline at end of file diff --git a/data/js/fwUoload.js b/data/js/fwUoload.js new file mode 100644 index 0000000..446f3b4 --- /dev/null +++ b/data/js/fwUoload.js @@ -0,0 +1,64 @@ +async function uploadFile(event) { + event.preventDefault(); + + const file = fileInput.files[0]; + if (!file) { + alert("Please select a file."); + return; + } + + // Existing file validation code... + + const formData = new FormData(); + formData.append('file', file); + + // Create a custom reader function + const reader = file.stream().getReader(); + const totalLength = file.size; + + let uploaded = 0; + + // Create a readable stream and use the custom reader function + const uploadStream = new ReadableStream({ + async start(controller) { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + uploaded += value.length; + let percentUploaded = (uploaded / totalLength * 100).toFixed(2); + + // Update progress bar and label + progressBar.value = percentUploaded; + progressLabel.innerHTML = `${percentUploaded}%`; + + controller.enqueue(value); + } + controller.close(); + }, + }); + + // Create a new Request with the readable stream as body + const req = new Request('/update', { + method: 'POST', + body: uploadStream, + headers: { + // Add any relevant headers here + }, + }); + + try { + const response = await fetch(req); + + if (response.ok) { + alert('Upload completed!'); + progressLabel.innerHTML = "Completed!"; + } else { + alert('An error occurred during the upload.'); + progressLabel.innerHTML = "Error!"; + } + } catch (error) { + console.error('Upload failed:', error); + } + } + \ No newline at end of file diff --git a/data/js/hue-select.js b/data/js/hue-select.js new file mode 100644 index 0000000..ca067d1 --- /dev/null +++ b/data/js/hue-select.js @@ -0,0 +1,244 @@ + +class HueSelect extends HTMLElement { + constructor() { + super(); + this.attachShadow({ mode: 'open' }); + this.shadowRoot.innerHTML = ` + + + `; + + this.currentHue = 0; + this.colorList; + this.hueLabel = this.shadowRoot.getElementById('hue-label'); + + const selectedColorDiv = this.shadowRoot.getElementById('selectedColor'); + const colorPatch = selectedColorDiv.querySelector('.color-patch'); + + selectedColorDiv.addEventListener('click', () => { + const dropdownContent = this.shadowRoot.querySelector('.dropdown-content'); + dropdownContent.style.display = dropdownContent.style.display === 'block' ? 'none' : 'block'; + }); + + this.createColorOptions(); + this.setHue(0); + } + + generateColors() { + const colors = []; + for (let hue = 0; hue <= 360; hue += 10) { + if (hue == 360) { hue = 359; } + colors.push(hue); + } + + colors.push(-1); + colors.push(-2); + return colors; + } + + createColorOption(hue) { + const colorOption = document.createElement('div'); + colorOption.classList.add('color-option'); + + const colorPatch = document.createElement('span'); + colorPatch.classList.add('color-patch'); + + const colorText = document.createElement('span'); + colorText.classList.add('color-text'); + colorText.textContent = hue; + + const rgbHex = document.createElement('span'); + rgbHex.classList.add('rgb-hex'); + + if (hue === -2) { + colorPatch.style.backgroundColor = 'rgb(0,0,0)'; + rgbHex.innerHTML = '  #000000'; + } else if (hue === -1) { + colorPatch.style.backgroundColor = 'rgb(255,255,255)'; + rgbHex.innerHTML = '  #FFFFFF'; + } else { + colorPatch.style.backgroundColor = `hsl(${hue}, 100%, 50%)`; + const hexColor = this.hslToRgb(hue, 100, 50).toUpperCase(); + rgbHex.innerHTML = `  ${hexColor}`; + } + + colorOption.appendChild(colorPatch); + colorOption.appendChild(colorText); + colorOption.appendChild(rgbHex); + + colorOption.addEventListener('click', () => this.handleColorSelection(hue)); + + return colorOption; + } + + createColorOptions() { + const dropdownContent = this.shadowRoot.querySelector('.dropdown-content'); + this.colorList = this.generateColors(); + + this.colorList.forEach(hue => { + const colorOption = this.createColorOption(hue); + dropdownContent.appendChild(colorOption); + }); + } + + // Function to convert HSL to RGB + hslToRgb(h, s, l) { + h /= 360; + s /= 100; + l /= 100; + let r, g, b; + if (s === 0) { + r = g = b = l; // achromatic + } else { + const hue2rgb = (p, q, t) => { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; + return p; + }; + const q = l < 0.5 ? l * (1 + s) : l + s - l * s; + const p = 2 * l - q; + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + const toHex = (x) => { + const hex = Math.round(x * 255).toString(16); + return hex.length === 1 ? '0' + hex : hex; + }; + return `#${toHex(r)}${toHex(g)}${toHex(b)}`; + } + + handleColorSelection(hue) { + this.currentHue = hue; + const selectedColorDiv = this.shadowRoot.getElementById('selectedColor'); + const colorPatch = selectedColorDiv.querySelector('.color-patch'); + + // Update the color patch and hue label + const rgb = this.getRGBfromHue(hue); + colorPatch.style.backgroundColor = rgb; + this.setHueLabel(hue); + this.hideDropdown(); + + this.dispatchEvent(new CustomEvent('change', { detail: { hue, rgb } })); + } + + // Method to get the hue value of the selected item + getSelectedHue() { + return this.currentHue; + } + + getSelectedRGB() { + const selectedColorText = this.shadowRoot.getElementById('selectedColor').textContent.trim(); + return parseFloat(selectedColorText); + } + + getRGBfromHue(hue){ + if(hue == -1){ + return '#FFFFFF'; + }else if(hue == -2){ + return '#000000'; + }else{ + return this.hslToRgb(hue, 100, 50);; + } + } + + // Method to get the RGB value of the selected item + getSelectedRgb() { + const selectedHue = this.getSelectedHue(); + return getRGBfromHue(selectedHue); + } + + setHue(hue) { + // Round the input hue to the nearest 10th + let roundedHue = hue; + if(hue === -1 || hue === -2){ + roundedHue = hue; + }else{ + roundedHue = Math.round(hue / 10) * 10; + if (roundedHue >= 360) { + roundedHue = 359; + }else if (roundedHue < 0) { + roundedHue = 0; + } + } + + this.currentHue = roundedHue; + + this.colorList.forEach(colorVal => { + if (colorVal === roundedHue) { + this.handleColorSelection(roundedHue); + } + }); + + this.hideDropdown(); + } + + hideDropdown() { + const dropdownContent = this.shadowRoot.querySelector('.dropdown-content'); + dropdownContent.style.display = 'none'; + } + + setHueLabel(value){ + this.hueLabel.textContent = "Hue: " + value; + } + + + } + + customElements.define('hue-select', HueSelect); + \ No newline at end of file diff --git a/data/js/jquery-3.7.1.js b/data/js/jquery-3.7.1.js new file mode 100644 index 0000000..f7d84ab --- /dev/null +++ b/data/js/jquery-3.7.1.js @@ -0,0 +1,8673 @@ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +"use strict"; + +var arr = []; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var flat = arr.flat ? function( array ) { + return arr.flat.call( array ); +} : function( array ) { + return arr.concat.apply( [], array ); +}; + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + +var isFunction = function isFunction( obj ) { + + return typeof obj === "function" && typeof obj.nodeType !== "number" && + typeof obj.item !== "function"; + }; + +var isWindow = function isWindow( obj ) { + return obj != null && obj === obj.window; + }; + +var document = window.document; + + var preservedScriptAttributes = { + type: true, + src: true, + nonce: true, + noModule: true + }; + + function DOMEval( code, node, doc ) { + doc = doc || document; + + var i, val, + script = doc.createElement( "script" ); + + script.text = code; + if ( node ) { + for ( i in preservedScriptAttributes ) { + + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); + } + } + } + doc.head.appendChild( script ).parentNode.removeChild( script ); + } + +function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} + +var version = "3.7.1", + + rhtmlSuffix = /HTML$/i, + + jQuery = function( selector, context ) { + + return new jQuery.fn.init( selector, context ); + }; + +jQuery.fn = jQuery.prototype = { + + jquery: version, + + constructor: jQuery, + + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + get: function( num ) { + + if ( num == null ) { + return slice.call( this ); + } + + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + pushStack: function( elems ) { + + var ret = jQuery.merge( this.constructor(), elems ); + + ret.prevObject = this; + + return ret; + }, + + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + even: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return ( i + 1 ) % 2; + } ) ); + }, + + odd: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return i % 2; + } ) ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + if ( typeof target === "boolean" ) { + deep = target; + + target = arguments[ i ] || {}; + i++; + } + + if ( typeof target !== "object" && !isFunction( target ) ) { + target = {}; + } + + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + if ( ( options = arguments[ i ] ) != null ) { + + for ( name in options ) { + copy = options[ name ]; + + if ( name === "__proto__" || target === copy ) { + continue; + } + + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; + + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; + } else { + clone = src; + } + copyIsArray = false; + + target[ name ] = jQuery.extend( deep, clone, copy ); + + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + return target; +}; + +jQuery.extend( { + + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isPlainObject: function( obj ) { + var proto, Ctor; + + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + if ( !proto ) { + return true; + } + + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + globalEval: function( code, options, doc ) { + DOMEval( code, { nonce: options && options.nonce }, doc ); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + text: function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + + while ( ( node = elem[ i++ ] ) ) { + + ret += jQuery.text( node ); + } + } + if ( nodeType === 1 || nodeType === 11 ) { + return elem.textContent; + } + if ( nodeType === 9 ) { + return elem.documentElement.textContent; + } + if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + + return ret; + }, + + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + isXMLDoc: function( elem ) { + var namespace = elem && elem.namespaceURI, + docElem = elem && ( elem.ownerDocument || elem ).documentElement; + + return !rhtmlSuffix.test( namespace || docElem && docElem.nodeName || "HTML" ); + }, + + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + return flat( ret ); + }, + + guid: 1, + + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), + function( _i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); + } ); + +function isArrayLike( obj ) { + + var length = !!obj && "length" in obj && obj.length, + type = toType( obj ); + + if ( isFunction( obj ) || isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +} +var pop = arr.pop; + +var sort = arr.sort; + +var splice = arr.splice; + +var whitespace = "[\\x20\\t\\r\\n\\f]"; + +var rtrimCSS = new RegExp( + "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", + "g" +); + +jQuery.contains = function( a, b ) { + var bup = b && b.parentNode; + + return a === bup || !!( bup && bup.nodeType === 1 && ( + + a.contains ? + a.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + ) ); +}; + +var rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g; + +function fcssescape( ch, asCodePoint ) { + if ( asCodePoint ) { + + if ( ch === "\0" ) { + return "\uFFFD"; + } + + return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + return "\\" + ch; +} + +jQuery.escapeSelector = function( sel ) { + return ( sel + "" ).replace( rcssescape, fcssescape ); +}; + +var preferredDoc = document, + pushNative = push; + +( function() { + +var i, + Expr, + outermostContext, + sortInput, + hasDuplicate, + push = pushNative, + + document, + documentElement, + documentIsHTML, + rbuggyQSA, + matches, + + expando = jQuery.expando, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + nonnativeSelectorCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|" + + "loop|multiple|open|readonly|required|scoped", + + identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", + + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + + "*([*^$|!~]?=)" + whitespace + + + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + + whitespace + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + + ".*" + + ")\\)|)", + + rwhitespace = new RegExp( whitespace + "+", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rleadingCombinator = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + + whitespace + "*" ), + rdescend = new RegExp( whitespace + "|>" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + ID: new RegExp( "^#(" + identifier + ")" ), + CLASS: new RegExp( "^\\.(" + identifier + ")" ), + TAG: new RegExp( "^(" + identifier + "|[*])" ), + ATTR: new RegExp( "^" + attributes ), + PSEUDO: new RegExp( "^" + pseudos ), + CHILD: new RegExp( + "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + bool: new RegExp( "^(?:" + booleans + ")$", "i" ), + + needsContext: new RegExp( "^" + whitespace + + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\([^\\r\\n\\f])", "g" ), + funescape = function( escape, nonHex ) { + var high = "0x" + escape.slice( 1 ) - 0x10000; + + if ( nonHex ) { + + return nonHex; + } + + return high < 0 ? + String.fromCharCode( high + 0x10000 ) : + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + unloadHandler = function() { + setDocument(); + }, + + inDisabledFieldset = addCombinator( + function( elem ) { + return elem.disabled === true && nodeName( elem, "fieldset" ); + }, + { dir: "parentNode", next: "legend" } + ); + +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +try { + push.apply( + ( arr = slice.call( preferredDoc.childNodes ) ), + preferredDoc.childNodes + ); + + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { + apply: function( target, els ) { + pushNative.apply( target, slice.call( els ) ); + }, + call: function( target ) { + pushNative.apply( target, slice.call( arguments, 1 ) ); + } + }; +} + +function find( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + nodeType = context ? context.nodeType : 9; + + results = results || []; + + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + if ( !seed ) { + setDocument( context ); + context = context || document; + + if ( documentIsHTML ) { + + if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { + + if ( ( m = match[ 1 ] ) ) { + + if ( nodeType === 9 ) { + if ( ( elem = context.getElementById( m ) ) ) { + + if ( elem.id === m ) { + push.call( results, elem ); + return results; + } + } else { + return results; + } + + } else { + + if ( newContext && ( elem = newContext.getElementById( m ) ) && + find.contains( context, elem ) && + elem.id === m ) { + + push.call( results, elem ); + return results; + } + } + + } else if ( match[ 2 ] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + } else if ( ( m = match[ 3 ] ) && context.getElementsByClassName ) { + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + if ( !nonnativeSelectorCache[ selector + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) ) { + + newSelector = selector; + newContext = context; + + if ( nodeType === 1 && + ( rdescend.test( selector ) || rleadingCombinator.test( selector ) ) ) { + + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + + if ( newContext != context || !support.scope ) { + + if ( ( nid = context.getAttribute( "id" ) ) ) { + nid = jQuery.escapeSelector( nid ); + } else { + context.setAttribute( "id", ( nid = expando ) ); + } + } + + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + + toSelector( groups[ i ] ); + } + newSelector = groups.join( "," ); + } + + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + + return select( selector.replace( rtrimCSS, "$1" ), context, results, seed ); +} + +function createCache() { + var keys = []; + + function cache( key, value ) { + + if ( keys.push( key + " " ) > Expr.cacheLength ) { + + delete cache[ keys.shift() ]; + } + return ( cache[ key + " " ] = value ); + } + return cache; +} + +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +function assert( fn ) { + var el = document.createElement( "fieldset" ); + + try { + return !!fn( el ); + } catch ( e ) { + return false; + } finally { + + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + + el = null; + } +} + +function createInputPseudo( type ) { + return function( elem ) { + return nodeName( elem, "input" ) && elem.type === type; + }; +} + +function createButtonPseudo( type ) { + return function( elem ) { + return ( nodeName( elem, "input" ) || nodeName( elem, "button" ) ) && + elem.type === type; + }; +} + +function createDisabledPseudo( disabled ) { + + return function( elem ) { + + if ( "form" in elem ) { + + if ( elem.parentNode && elem.disabled === false ) { + + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + return elem.isDisabled === disabled || + + elem.isDisabled !== !disabled && + inDisabledFieldset( elem ) === disabled; + } + + return elem.disabled === disabled; + + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + return false; + }; +} + +function createPositionalPseudo( fn ) { + return markFunction( function( argument ) { + argument = +argument; + return markFunction( function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + while ( i-- ) { + if ( seed[ ( j = matchIndexes[ i ] ) ] ) { + seed[ j ] = !( matches[ j ] = seed[ j ] ); + } + } + } ); + } ); +} + +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +function setDocument( node ) { + var subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + document = doc; + documentElement = document.documentElement; + documentIsHTML = !jQuery.isXMLDoc( document ); + + matches = documentElement.matches || + documentElement.webkitMatchesSelector || + documentElement.msMatchesSelector; + + if ( documentElement.msMatchesSelector && + + preferredDoc != document && + ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { + + subWindow.addEventListener( "unload", unloadHandler ); + } + + support.getById = assert( function( el ) { + documentElement.appendChild( el ).id = jQuery.expando; + return !document.getElementsByName || + !document.getElementsByName( jQuery.expando ).length; + } ); + + support.disconnectedMatch = assert( function( el ) { + return matches.call( el, "*" ); + } ); + + support.scope = assert( function() { + return document.querySelectorAll( ":scope" ); + } ); + + support.cssHas = assert( function() { + try { + document.querySelector( ":has(*,:jqfake)" ); + return false; + } catch ( e ) { + return true; + } + } ); + + if ( support.getById ) { + Expr.filter.ID = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute( "id" ) === attrId; + }; + }; + Expr.find.ID = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter.ID = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode( "id" ); + return node && node.value === attrId; + }; + }; + + Expr.find.ID = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + + elems = context.getElementsByName( id ); + i = 0; + while ( ( elem = elems[ i++ ] ) ) { + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + Expr.find.TAG = function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + } else { + return context.querySelectorAll( tag ); + } + }; + + Expr.find.CLASS = function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + rbuggyQSA = []; + + assert( function( el ) { + + var input; + + documentElement.appendChild( el ).innerHTML = + "" + + ""; + + if ( !el.querySelectorAll( "[selected]" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push( "~=" ); + } + + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push( ".#.+[+~]" ); + } + + if ( !el.querySelectorAll( ":checked" ).length ) { + rbuggyQSA.push( ":checked" ); + } + + input = document.createElement( "input" ); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + documentElement.appendChild( el ).disabled = true; + if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + input = document.createElement( "input" ); + input.setAttribute( "name", "" ); + el.appendChild( input ); + if ( !el.querySelectorAll( "[name='']" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + + whitespace + "*(?:''|\"\")" ); + } + } ); + + if ( !support.cssHas ) { + + rbuggyQSA.push( ":has" ); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); + + sortOrder = function( a, b ) { + + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + 1; + + if ( compare & 1 || + ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { + + if ( a === document || a.ownerDocument == preferredDoc && + find.contains( preferredDoc, a ) ) { + return -1; + } + + if ( b === document || b.ownerDocument == preferredDoc && + find.contains( preferredDoc, b ) ) { + return 1; + } + + return sortInput ? + ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + }; + + return document; +} + +find.matches = function( expr, elements ) { + return find( expr, null, null, elements ); +}; + +find.matchesSelector = function( elem, expr ) { + setDocument( elem ); + + if ( documentIsHTML && + !nonnativeSelectorCache[ expr + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + if ( ret || support.disconnectedMatch || + + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch ( e ) { + nonnativeSelectorCache( expr, true ); + } + } + + return find( expr, document, null, [ elem ] ).length > 0; +}; + +find.contains = function( context, elem ) { + + if ( ( context.ownerDocument || context ) != document ) { + setDocument( context ); + } + return jQuery.contains( context, elem ); +}; + +find.attr = function( elem, name ) { + + if ( ( elem.ownerDocument || elem ) != document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + if ( val !== undefined ) { + return val; + } + + return elem.getAttribute( name ); +}; + +find.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +jQuery.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + hasDuplicate = !support.sortStable; + sortInput = !support.sortStable && slice.call( results, 0 ); + sort.call( results, sortOrder ); + + if ( hasDuplicate ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + splice.call( results, duplicates[ j ], 1 ); + } + } + + sortInput = null; + + return results; +}; + +jQuery.fn.uniqueSort = function() { + return this.pushStack( jQuery.uniqueSort( slice.apply( this ) ) ); +}; + +Expr = jQuery.expr = { + + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + ATTR: function( match ) { + match[ 1 ] = match[ 1 ].replace( runescape, funescape ); + + match[ 3 ] = ( match[ 3 ] || match[ 4 ] || match[ 5 ] || "" ) + .replace( runescape, funescape ); + + if ( match[ 2 ] === "~=" ) { + match[ 3 ] = " " + match[ 3 ] + " "; + } + + return match.slice( 0, 4 ); + }, + + CHILD: function( match ) { + + match[ 1 ] = match[ 1 ].toLowerCase(); + + if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { + + if ( !match[ 3 ] ) { + find.error( match[ 0 ] ); + } + + match[ 4 ] = +( match[ 4 ] ? + match[ 5 ] + ( match[ 6 ] || 1 ) : + 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) + ); + match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); + + } else if ( match[ 3 ] ) { + find.error( match[ 0 ] ); + } + + return match; + }, + + PSEUDO: function( match ) { + var excess, + unquoted = !match[ 6 ] && match[ 2 ]; + + if ( matchExpr.CHILD.test( match[ 0 ] ) ) { + return null; + } + + if ( match[ 3 ] ) { + match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; + + } else if ( unquoted && rpseudo.test( unquoted ) && + + ( excess = tokenize( unquoted, true ) ) && + + ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { + + match[ 0 ] = match[ 0 ].slice( 0, excess ); + match[ 2 ] = unquoted.slice( 0, excess ); + } + + return match.slice( 0, 3 ); + } + }, + + filter: { + + TAG: function( nodeNameSelector ) { + var expectedNodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { + return true; + } : + function( elem ) { + return nodeName( elem, expectedNodeName ); + }; + }, + + CLASS: function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + ( pattern = new RegExp( "(^|" + whitespace + ")" + className + + "(" + whitespace + "|$)" ) ) && + classCache( className, function( elem ) { + return pattern.test( + typeof elem.className === "string" && elem.className || + typeof elem.getAttribute !== "undefined" && + elem.getAttribute( "class" ) || + "" + ); + } ); + }, + + ATTR: function( name, operator, check ) { + return function( elem ) { + var result = find.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + if ( operator === "=" ) { + return result === check; + } + if ( operator === "!=" ) { + return result !== check; + } + if ( operator === "^=" ) { + return check && result.indexOf( check ) === 0; + } + if ( operator === "*=" ) { + return check && result.indexOf( check ) > -1; + } + if ( operator === "$=" ) { + return check && result.slice( -check.length ) === check; + } + if ( operator === "~=" ) { + return ( " " + result.replace( rwhitespace, " " ) + " " ) + .indexOf( check ) > -1; + } + if ( operator === "|=" ) { + return result === check || result.slice( 0, check.length + 1 ) === check + "-"; + } + + return false; + }; + }, + + CHILD: function( type, what, _argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, _context, xml ) { + var cache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + if ( simple ) { + while ( dir ) { + node = elem; + while ( ( node = node[ dir ] ) ) { + if ( ofType ? + nodeName( node, name ) : + node.nodeType === 1 ) { + + return false; + } + } + + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + if ( forward && useCache ) { + + outerCache = parent[ expando ] || ( parent[ expando ] = {} ); + cache = outerCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( ( node = ++nodeIndex && node && node[ dir ] || + + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + if ( node.nodeType === 1 && ++diff && node === elem ) { + outerCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + + if ( useCache ) { + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); + cache = outerCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + if ( diff === false ) { + + while ( ( node = ++nodeIndex && node && node[ dir ] || + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + if ( ( ofType ? + nodeName( node, name ) : + node.nodeType === 1 ) && + ++diff ) { + + if ( useCache ) { + outerCache = node[ expando ] || + ( node[ expando ] = {} ); + outerCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + PSEUDO: function( pseudo, argument ) { + + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + find.error( "unsupported pseudo: " + pseudo ); + + if ( fn[ expando ] ) { + return fn( argument ); + } + + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction( function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf.call( seed, matched[ i ] ); + seed[ idx ] = !( matches[ idx ] = matched[ i ] ); + } + } ) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + + not: markFunction( function( selector ) { + + var input = [], + results = [], + matcher = compile( selector.replace( rtrimCSS, "$1" ) ); + + return matcher[ expando ] ? + markFunction( function( seed, matches, _context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + while ( i-- ) { + if ( ( elem = unmatched[ i ] ) ) { + seed[ i ] = !( matches[ i ] = elem ); + } + } + } ) : + function( elem, _context, xml ) { + input[ 0 ] = elem; + matcher( input, null, xml, results ); + + input[ 0 ] = null; + return !results.pop(); + }; + } ), + + has: markFunction( function( selector ) { + return function( elem ) { + return find( selector, elem ).length > 0; + }; + } ), + + contains: markFunction( function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || jQuery.text( elem ) ).indexOf( text ) > -1; + }; + } ), + + lang: markFunction( function( lang ) { + + if ( !ridentifier.test( lang || "" ) ) { + find.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( ( elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); + return false; + }; + } ), + + target: function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + root: function( elem ) { + return elem === documentElement; + }, + + focus: function( elem ) { + return elem === safeActiveElement() && + document.hasFocus() && + !!( elem.type || elem.href || ~elem.tabIndex ); + }, + + enabled: createDisabledPseudo( false ), + disabled: createDisabledPseudo( true ), + + checked: function( elem ) { + + return ( nodeName( elem, "input" ) && !!elem.checked ) || + ( nodeName( elem, "option" ) && !!elem.selected ); + }, + + selected: function( elem ) { + + if ( elem.parentNode ) { + + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + empty: function( elem ) { + + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + parent: function( elem ) { + return !Expr.pseudos.empty( elem ); + }, + + header: function( elem ) { + return rheader.test( elem.nodeName ); + }, + + input: function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + button: function( elem ) { + return nodeName( elem, "input" ) && elem.type === "button" || + nodeName( elem, "button" ); + }, + + text: function( elem ) { + var attr; + return nodeName( elem, "input" ) && elem.type === "text" && + + ( ( attr = elem.getAttribute( "type" ) ) == null || + attr.toLowerCase() === "text" ); + }, + + first: createPositionalPseudo( function() { + return [ 0 ]; + } ), + + last: createPositionalPseudo( function( _matchIndexes, length ) { + return [ length - 1 ]; + } ), + + eq: createPositionalPseudo( function( _matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + } ), + + even: createPositionalPseudo( function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + odd: createPositionalPseudo( function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + lt: createPositionalPseudo( function( matchIndexes, length, argument ) { + var i; + + if ( argument < 0 ) { + i = argument + length; + } else if ( argument > length ) { + i = length; + } else { + i = argument; + } + + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + gt: createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ) + } +}; + +Expr.pseudos.nth = Expr.pseudos.eq; + +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +function tokenize( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + if ( !matched || ( match = rcomma.exec( soFar ) ) ) { + if ( match ) { + + soFar = soFar.slice( match[ 0 ].length ) || soFar; + } + groups.push( ( tokens = [] ) ); + } + + matched = false; + + if ( ( match = rleadingCombinator.exec( soFar ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + + type: match[ 0 ].replace( rtrimCSS, " " ) + } ); + soFar = soFar.slice( matched.length ); + } + + for ( type in Expr.filter ) { + if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || + ( match = preFilters[ type ]( match ) ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type: type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + if ( parseOnly ) { + return soFar.length; + } + + return soFar ? + find.error( selector ) : + + tokenCache( selector, groups ).slice( 0 ); +} + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[ i ].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + + function( elem, context, xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + function( elem, context, xml ) { + var oldCache, outerCache, + newCache = [ dirruns, doneName ]; + + if ( xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); + + if ( skip && nodeName( elem, skip ) ) { + elem = elem[ dir ] || elem; + } else if ( ( oldCache = outerCache[ key ] ) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + return ( newCache[ 2 ] = oldCache[ 2 ] ); + } else { + + outerCache[ key ] = newCache; + + if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[ i ]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[ 0 ]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + find( selector, contexts[ i ], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( ( elem = unmatched[ i ] ) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction( function( seed, results, context, xml ) { + var temp, i, elem, matcherOut, + preMap = [], + postMap = [], + preexisting = results.length, + + elems = seed || + multipleContexts( selector || "*", + context.nodeType ? [ context ] : context, [] ), + + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems; + + if ( matcher ) { + + matcherOut = postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + [] : + + results; + + matcher( matcherIn, matcherOut, context, xml ); + } else { + matcherOut = matcherIn; + } + + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + i = temp.length; + while ( i-- ) { + if ( ( elem = temp[ i ] ) ) { + matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) ) { + + temp.push( ( matcherIn[ i ] = elem ) ); + } + } + postFinder( null, ( matcherOut = [] ), temp, xml ); + } + + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) && + ( temp = postFinder ? indexOf.call( seed, elem ) : preMap[ i ] ) > -1 ) { + + seed[ temp ] = !( results[ temp ] = elem ); + } + } + } + + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + } ); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[ 0 ].type ], + implicitRelative = leadingRelative || Expr.relative[ " " ], + i = leadingRelative ? 1 : 0, + + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf.call( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + + var ret = ( !leadingRelative && ( xml || context != outermostContext ) ) || ( + ( checkContext = context ).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { + matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; + } else { + matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); + + if ( matcher[ expando ] ) { + + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[ j ].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + + tokens.slice( 0, i - 1 ) + .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) + ).replace( rtrimCSS, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + + elems = seed || byElement && Expr.find.TAG( "*", outermost ), + + dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), + len = elems.length; + + if ( outermost ) { + + outermostContext = context == document || context || outermost; + } + + for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + + if ( !context && elem.ownerDocument != document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( ( matcher = elementMatchers[ j++ ] ) ) { + if ( matcher( elem, context || document, xml ) ) { + push.call( results, elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + if ( bySet ) { + + if ( ( elem = !matcher && elem ) ) { + matchedCount--; + } + + if ( seed ) { + unmatched.push( elem ); + } + } + } + + matchedCount += i; + + if ( bySet && i !== matchedCount ) { + j = 0; + while ( ( matcher = setMatchers[ j++ ] ) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !( unmatched[ i ] || setMatched[ i ] ) ) { + setMatched[ i ] = pop.call( results ); + } + } + } + + setMatched = condense( setMatched ); + } + + push.apply( results, setMatched ); + + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + jQuery.uniqueSort( results ); + } + } + + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +function compile( selector, match ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[ i ] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + cached = compilerCache( selector, + matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + + cached.selector = selector; + } + return cached; +} + +function select( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( ( selector = compiled.selector || selector ) ); + + results = results || []; + + if ( match.length === 1 ) { + + tokens = match[ 0 ] = match[ 0 ].slice( 0 ); + if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { + + context = ( Expr.find.ID( + token.matches[ 0 ].replace( runescape, funescape ), + context + ) || [] )[ 0 ]; + if ( !context ) { + return results; + + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + i = matchExpr.needsContext.test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[ i ]; + + if ( Expr.relative[ ( type = token.type ) ] ) { + break; + } + if ( ( find = Expr.find[ type ] ) ) { + + if ( ( seed = find( + token.matches[ 0 ].replace( runescape, funescape ), + rsibling.test( tokens[ 0 ].type ) && + testContext( context.parentNode ) || context + ) ) ) { + + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +} + +support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; + +setDocument(); + +support.sortDetached = assert( function( el ) { + + return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; +} ); + +jQuery.find = find; + +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.unique = jQuery.uniqueSort; + +find.compile = compile; +find.select = select; +find.setDocument = setDocument; +find.tokenize = tokenize; + +find.escape = jQuery.escapeSelector; +find.getText = jQuery.text; +find.isXML = jQuery.isXMLDoc; +find.selectors = jQuery.expr; +find.support = jQuery.support; +find.uniqueSort = jQuery.uniqueSort; + +} )(); + +var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + +var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + +var rneedsContext = jQuery.expr.match.needsContext; + +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + +function winnow( elements, qualifier, not ) { + if ( isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + return jQuery.filter( qualifier, elements, not ); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + +var rootjQuery, + + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + if ( !selector ) { + return this; + } + + root = root || rootjQuery; + + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + if ( match && ( match[ 1 ] || !context ) ) { + + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + if ( isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + } else { + return this.constructor( context ).find( selector ); + } + + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + } else if ( isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +init.prototype = jQuery.fn; + +rootjQuery = jQuery( document ); + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + index: function( elem ) { + + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + return indexOf.call( this, + + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, _i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, _i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, _i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + if ( elem.contentDocument != null && + + getProto( elem.contentDocument ) ) { + + return elem.contentDocument; + } + + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + +function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +jQuery.Callbacks = function( options ) { + + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var + firing, + + memory, + + fired, + + locked, + + list = [], + + queue = [], + + firingIndex = -1, + + fire = function() { + + locked = locked || options.once; + + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + firingIndex = list.length; + memory = false; + } + } + } + + if ( !options.memory ) { + memory = false; + } + + firing = false; + + if ( locked ) { + + if ( memory ) { + list = []; + + } else { + list = ""; + } + } + }, + + self = { + + add: function() { + if ( list ) { + + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && toType( arg ) !== "string" ) { + + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + fired: function() { + return !!fired; + } + }; + + return self; +}; + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject, noValue ) { + var method; + + try { + + if ( value && isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + } else if ( value && isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + } else { + + resolve.apply( undefined, [ value ].slice( noValue ) ); + } + + } catch ( value ) { + + reject.apply( undefined, [ value ] ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + pipe: function( ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( _i, tuple ) { + + var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + then = returned && + + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + if ( isFunction( then ) ) { + + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + } else { + + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + } else { + + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + ( special || deferred.resolveWith )( that, args ); + } + }, + + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.error ); + } + + if ( depth + 1 >= maxDepth ) { + + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + if ( depth ) { + process(); + } else { + + if ( jQuery.Deferred.getErrorHook ) { + process.error = jQuery.Deferred.getErrorHook(); + + } else if ( jQuery.Deferred.getStackHook ) { + process.error = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + promise[ tuple[ 1 ] ] = list.add; + + if ( stateString ) { + list.add( + function() { + + state = stateString; + }, + + tuples[ 3 - i ][ 2 ].disable, + + tuples[ 3 - i ][ 3 ].disable, + + tuples[ 0 ][ 2 ].lock, + + tuples[ 0 ][ 3 ].lock + ); + } + + list.add( tuple[ 3 ].fire ); + + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + promise.promise( deferred ); + + if ( func ) { + func.call( deferred, deferred ); + } + + return deferred; + }, + + when: function( singleValue ) { + var + + remaining = arguments.length, + + i = remaining, + + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + primary = jQuery.Deferred(), + + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + primary.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + if ( remaining <= 1 ) { + adoptValue( singleValue, primary.done( updateFunc( i ) ).resolve, primary.reject, + !remaining ); + + if ( primary.state() === "pending" || + isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return primary.then(); + } + } + + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), primary.reject ); + } + + return primary.promise(); + } +} ); + +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +jQuery.Deferred.exceptionHook = function( error, asyncError ) { + + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, + error.stack, asyncError ); + } +}; + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + +var readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + isReady: false, + + readyWait: 1, + + ready: function( wait ) { + + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + jQuery.isReady = true; + + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + window.setTimeout( jQuery.ready ); + +} else { + + document.addEventListener( "DOMContentLoaded", completed ); + + window.addEventListener( "load", completed ); +} + +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + if ( toType( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + } else if ( value !== undefined ) { + chainable = true; + + if ( !isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + if ( raw ) { + fn.call( elems, value ); + fn = null; + + } else { + bulk = fn; + fn = function( elem, _key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; + +var rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g; + +function fcamelCase( _all, letter ) { + return letter.toUpperCase(); +} + +function camelCase( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); +} +var acceptData = function( owner ) { + + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + var value = owner[ this.expando ]; + + if ( !value ) { + value = {}; + + if ( acceptData( owner ) ) { + + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + if ( typeof data === "string" ) { + cache[ camelCase( data ) ] = value; + + } else { + + for ( prop in data ) { + cache[ camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + this.set( owner, key, value ); + + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + if ( Array.isArray( key ) ) { + + key = key.map( camelCase ); + } else { + key = camelCase( key ); + + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +var dataPriv = new Data(); + +var dataUser = new Data(); + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + var name; + + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + if ( elem && value === undefined ) { + + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + return; + } + + this.each( function() { + + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + if ( data ) { + if ( !queue || Array.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var documentElement = document.documentElement; + + var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } +var isHiddenWithinTree = function( elem, el ) { + + elem = el || elem; + + return elem.style.display === "none" || + elem.style.display === "" && + + isAttached( elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, scale, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + initial = initial / 2; + + unit = unit || initialInUnit[ 3 ]; + + initialInUnit = +initial || 1; + + while ( maxIterations-- ) { + + jQuery.style( elem, prop, initialInUnit + unit ); + if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; + + } + + initialInUnit = initialInUnit * 2; + jQuery.style( elem, prop, initialInUnit + unit ); + + valueParts = valueParts || []; + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + dataPriv.set( elem, "display", display ); + } + } + } + + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +var rcheckableType = ( /^(?:checkbox|radio)$/i ); + +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); + +var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + + div.innerHTML = ""; + support.option = !!div.lastChild; +} )(); + +var wrapMap = { + + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] +}; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +if ( !support.option ) { + wrapMap.optgroup = wrapMap.option = [ 1, "" ]; +} + +function getAll( context, tag ) { + + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, attached, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + if ( toType( elem ) === "object" ) { + + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + jQuery.merge( nodes, tmp.childNodes ); + + tmp = fragment.firstChild; + + tmp.textContent = ""; + } + } + } + + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + attached = isAttached( elem ); + + tmp = getAll( fragment.appendChild( elem ), "script" ); + + if ( attached ) { + setGlobalEval( tmp ); + } + + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + +var rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + if ( typeof types === "object" ) { + + if ( typeof selector !== "string" ) { + + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + fn = data; + data = undefined; + } else { + + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + if ( !acceptData( elem ) ) { + return; + } + + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + if ( !( events = elemData.events ) ) { + events = elemData.events = Object.create( null ); + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + if ( !type ) { + continue; + } + + special = jQuery.event.special[ type ] || {}; + + type = ( selector ? special.delegateType : special.bindType ) || type; + + special = jQuery.event.special[ type ] || {}; + + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + jQuery.event.global[ type ] = true; + } + + }, + + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + + event = jQuery.event.fix( nativeEvent ), + + handlers = ( + dataPriv.get( this, "events" ) || Object.create( null ) + )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + if ( delegateCount && + + cur.nodeType && + + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + noBubble: true + }, + click: { + + setup: function( data ) { + + var el = this || data; + + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click", true ); + } + + return false; + }, + trigger: function( data ) { + + var el = this || data; + + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); + } + + return true; + }, + + _default: function( event ) { + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +function leverageNative( el, type, isSetup ) { + + if ( !isSetup ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + if ( !saved ) { + + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + this[ type ](); + result = dataPriv.get( this, type ); + dataPriv.set( this, type, false ); + + if ( saved !== result ) { + + event.stopImmediatePropagation(); + event.preventDefault(); + + return result; + } + + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + } else if ( saved ) { + + dataPriv.set( this, type, jQuery.event.trigger( + saved[ 0 ], + saved.slice( 1 ), + this + ) ); + + event.stopPropagation(); + event.isImmediatePropagationStopped = returnTrue; + } + } + } ); +} + +jQuery.removeEvent = function( elem, type, handle ) { + + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + src.returnValue === false ? + returnTrue : + returnFalse; + + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + } else { + this.type = src; + } + + if ( props ) { + jQuery.extend( this, props ); + } + + this.timeStamp = src && src.timeStamp || Date.now(); + + this[ jQuery.expando ] = true; +}; + +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + code: true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + which: true +}, jQuery.event.addProp ); + +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + + function focusMappedHandler( nativeEvent ) { + if ( document.documentMode ) { + + var handle = dataPriv.get( this, "handle" ), + event = jQuery.event.fix( nativeEvent ); + event.type = nativeEvent.type === "focusin" ? "focus" : "blur"; + event.isSimulated = true; + + handle( nativeEvent ); + + if ( event.target === event.currentTarget ) { + + handle( event ); + } + } else { + + jQuery.event.simulate( delegateType, nativeEvent.target, + jQuery.event.fix( nativeEvent ) ); + } + } + + jQuery.event.special[ type ] = { + + setup: function() { + + var attaches; + + leverageNative( this, type, true ); + + if ( document.documentMode ) { + + attaches = dataPriv.get( this, delegateType ); + if ( !attaches ) { + this.addEventListener( delegateType, focusMappedHandler ); + } + dataPriv.set( this, delegateType, ( attaches || 0 ) + 1 ); + } else { + + return false; + } + }, + trigger: function() { + + leverageNative( this, type ); + + return true; + }, + + teardown: function() { + var attaches; + + if ( document.documentMode ) { + attaches = dataPriv.get( this, delegateType ) - 1; + if ( !attaches ) { + this.removeEventListener( delegateType, focusMappedHandler ); + dataPriv.remove( this, delegateType ); + } else { + dataPriv.set( this, delegateType, attaches ); + } + } else { + + return false; + } + }, + + _default: function( event ) { + return dataPriv.get( event.target, type ); + }, + + delegateType: delegateType + }; + + jQuery.event.special[ delegateType ] = { + setup: function() { + + var doc = this.ownerDocument || this.document || this, + dataHolder = document.documentMode ? this : doc, + attaches = dataPriv.get( dataHolder, delegateType ); + + if ( !attaches ) { + if ( document.documentMode ) { + this.addEventListener( delegateType, focusMappedHandler ); + } else { + doc.addEventListener( type, focusMappedHandler, true ); + } + } + dataPriv.set( dataHolder, delegateType, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this.document || this, + dataHolder = document.documentMode ? this : doc, + attaches = dataPriv.get( dataHolder, delegateType ) - 1; + + if ( !attaches ) { + if ( document.documentMode ) { + this.removeEventListener( delegateType, focusMappedHandler ); + } else { + doc.removeEventListener( type, focusMappedHandler, true ); + } + dataPriv.remove( dataHolder, delegateType ); + } else { + dataPriv.set( dataHolder, delegateType, attaches ); + } + } + }; +} ); + +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + +var + + rnoInnerhtml = /\s*$/g; + +function manipulationTarget( elem, content ) { + if ( nodeName( elem, "table" ) && + nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return jQuery( elem ).children( "tbody" )[ 0 ] || elem; + } + + return elem; +} + +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.get( src ); + events = pdataOld.events; + + if ( events ) { + dataPriv.remove( dest, "handle events" ); + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + args = flat( args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + valueIsFunction = isFunction( value ); + + if ( valueIsFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( valueIsFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + if ( hasScripts ) { + + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + jQuery.map( scripts, restoreScript ); + + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { + + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + }, doc ); + } + } else { + + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && isAttached( node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html; + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = isAttached( elem ); + + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + jQuery.cleanData( getAll( elem, false ) ); + + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +var rcustomProp = /^--/; + +var getStyles = function( elem ) { + + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + +var swap = function( elem, options, callback ) { + var ret, name, + old = {}; + + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.call( elem ); + + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + +var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); + +( function() { + + function computeStyleTests() { + + if ( !div ) { + return; + } + + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + + "margin-top:1px;padding:0;border:0"; + div.style.cssText = + "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + + "margin:auto;border:1px;padding:1px;" + + "width:60%;top:1%"; + documentElement.appendChild( container ).appendChild( div ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; + + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; + + boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; + + div.style.position = "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; + + documentElement.removeChild( container ); + + div = null; + } + + function roundPixelMeasures( measure ) { + return Math.round( parseFloat( measure ) ); + } + + var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, + reliableTrDimensionsVal, reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + if ( !div.style ) { + return; + } + + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + jQuery.extend( support, { + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelBoxStyles: function() { + computeStyleTests(); + return pixelBoxStylesVal; + }, + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + }, + scrollboxSize: function() { + computeStyleTests(); + return scrollboxSizeVal; + }, + + reliableTrDimensions: function() { + var table, tr, trChild, trStyle; + if ( reliableTrDimensionsVal == null ) { + table = document.createElement( "table" ); + tr = document.createElement( "tr" ); + trChild = document.createElement( "div" ); + + table.style.cssText = "position:absolute;left:-11111px;border-collapse:separate"; + tr.style.cssText = "box-sizing:content-box;border:1px solid"; + + tr.style.height = "1px"; + trChild.style.height = "9px"; + + trChild.style.display = "block"; + + documentElement + .appendChild( table ) + .appendChild( tr ) + .appendChild( trChild ); + + trStyle = window.getComputedStyle( tr ); + reliableTrDimensionsVal = ( parseInt( trStyle.height, 10 ) + + parseInt( trStyle.borderTopWidth, 10 ) + + parseInt( trStyle.borderBottomWidth, 10 ) ) === tr.offsetHeight; + + documentElement.removeChild( table ); + } + return reliableTrDimensionsVal; + } + } ); +} )(); + +function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + isCustomProp = rcustomProp.test( name ), + + style = elem.style; + + computed = computed || getStyles( elem ); + + if ( computed ) { + + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( isCustomProp && ret ) { + + ret = ret.replace( rtrimCSS, "$1" ) || undefined; + } + + if ( ret === "" && !isAttached( elem ) ) { + ret = jQuery.style( elem, name ); + } + + if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { + + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + ret + "" : + ret; +} + +function addGetHookIf( conditionFn, hookFn ) { + + return { + get: function() { + if ( conditionFn() ) { + + delete this.get; + return; + } + + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + +var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; + +function vendorPropName( name ) { + + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +function finalPropName( name ) { + var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; + } + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; +} + +var + + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + +function setPositiveNumber( _elem, value, subtract ) { + + var matches = rcssNum.exec( value ); + return matches ? + + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0, + marginDelta = 0; + + if ( box === ( isBorderBox ? "border" : "content" ) ) { + return 0; + } + + for ( ; i < 4; i += 2 ) { + + if ( box === "margin" ) { + marginDelta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); + } + + if ( !isBorderBox ) { + + delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + if ( box !== "padding" ) { + delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + + } else { + extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + + } else { + + if ( box === "content" ) { + delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + if ( box !== "margin" ) { + delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + if ( !isBorderBox && computedVal >= 0 ) { + + delta += Math.max( 0, Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + computedVal - + delta - + extra - + 0.5 + + ) ) || 0; + } + + return delta + marginDelta; +} + +function getWidthOrHeight( elem, dimension, extra ) { + + var styles = getStyles( elem ), + + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, + + val = curCSS( elem, dimension, styles ), + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); + + if ( rnumnonpx.test( val ) ) { + if ( !extra ) { + return val; + } + val = "auto"; + } + + if ( ( !support.boxSizingReliable() && isBorderBox || + + !support.reliableTrDimensions() && nodeName( elem, "tr" ) || + + val === "auto" || + + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + + elem.getClientRects().length ) { + + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; + } + } + + val = parseFloat( val ) || 0; + + return ( val + + boxModelAdjustment( + elem, + dimension, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles, + + val + ) + ) + "px"; +} + +jQuery.extend( { + + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + cssNumber: { + animationIterationCount: true, + aspectRatio: true, + borderImageSlice: true, + columnCount: true, + flexGrow: true, + flexShrink: true, + fontWeight: true, + gridArea: true, + gridColumn: true, + gridColumnEnd: true, + gridColumnStart: true, + gridRow: true, + gridRowEnd: true, + gridRowStart: true, + lineHeight: true, + opacity: true, + order: true, + orphans: true, + scale: true, + widows: true, + zIndex: true, + zoom: true, + + fillOpacity: true, + floodOpacity: true, + stopOpacity: true, + strokeMiterlimit: true, + strokeOpacity: true + }, + + cssProps: {}, + + style: function( elem, name, value, extra ) { + + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + var ret, type, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ), + style = elem.style; + + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + if ( value !== undefined ) { + type = typeof value; + + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + type = "number"; + } + + if ( value == null || value !== value ) { + return; + } + + if ( type === "number" && !isCustomProp ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } + } + + } else { + + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ); + + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( _i, dimension ) { + jQuery.cssHooks[ dimension ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, dimension, extra ); + } ) : + getWidthOrHeight( elem, dimension, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = getStyles( elem ), + + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; + + if ( isBorderBox && scrollboxSizeBuggy ) { + subtract -= Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + parseFloat( styles[ dimension ] ) - + boxModelAdjustment( elem, dimension, "border", false, styles ) - + 0.5 + ); + } + + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ dimension ] = value; + value = jQuery.css( elem, dimension ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( prefix !== "margin" ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( Array.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + result = jQuery.css( tween.elem, tween.prop, "" ); + + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +jQuery.fx.step = {}; + +var + fxNow, inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function schedule() { + if ( inProgress ) { + if ( document.hidden === false && window.requestAnimationFrame ) { + window.requestAnimationFrame( schedule ); + } else { + window.setTimeout( schedule, jQuery.fx.interval ); + } + + jQuery.fx.tick(); + } +} + +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = Date.now() ); +} + +function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + if ( isBox && elem.nodeType === 1 ) { + + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + propTween = false; + for ( prop in orig ) { + + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + if ( toggle ) { + dataShow.hidden = !hidden; + } + + if ( hidden ) { + showHide( [ elem ], true ); + } + + anim.done( function() { + + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + for ( index in props ) { + name = camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( Array.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + if ( percent < 1 && length ) { + return remaining; + } + + if ( !length ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + } + + deferred.resolveWith( elem, [ animation ] ); + return false; + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + result.stop.bind( result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + animation + .progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + return animation; +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !isFunction( easing ) && easing + }; + + if ( jQuery.fx.off ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + opt.old = opt.complete; + + opt.complete = function() { + if ( isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + data.finish = true; + + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = Date.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + jQuery.fx.start(); +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( inProgress ) { + return; + } + + inProgress = true; + schedule(); +}; + +jQuery.fx.stop = function() { + inProgress = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + _default: 400 +}; + +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + +( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + support.checkOn = input.value !== ""; + + support.optSelected = opt.selected; + + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + +var boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +function classesToArray( value ) { + if ( Array.isArray( value ) ) { + return value; + } + if ( typeof value === "string" ) { + return value.match( rnothtmlwhite ) || []; + } + return []; +} + +jQuery.fn.extend( { + addClass: function( value ) { + var classNames, cur, curValue, className, i, finalValue; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + classNames = classesToArray( value ); + + if ( classNames.length ) { + return this.each( function() { + curValue = getClass( this ); + cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + for ( i = 0; i < classNames.length; i++ ) { + className = classNames[ i ]; + if ( cur.indexOf( " " + className + " " ) < 0 ) { + cur += className + " "; + } + } + + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + this.setAttribute( "class", finalValue ); + } + } + } ); + } + + return this; + }, + + removeClass: function( value ) { + var classNames, cur, curValue, className, i, finalValue; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + classNames = classesToArray( value ); + + if ( classNames.length ) { + return this.each( function() { + curValue = getClass( this ); + + cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + for ( i = 0; i < classNames.length; i++ ) { + className = classNames[ i ]; + + while ( cur.indexOf( " " + className + " " ) > -1 ) { + cur = cur.replace( " " + className + " ", " " ); + } + } + + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + this.setAttribute( "class", finalValue ); + } + } + } ); + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var classNames, className, i, self, + type = typeof value, + isValidValue = type === "string" || Array.isArray( value ); + + if ( isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + if ( typeof stateVal === "boolean" && isValidValue ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + classNames = classesToArray( value ); + + return this.each( function() { + if ( isValidValue ) { + + self = jQuery( this ); + + for ( i = 0; i < classNames.length; i++ ) { + className = classNames[ i ]; + + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + dataPriv.set( this, "__className__", className ); + } + + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + +var rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, valueIsFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + return ret == null ? "" : ret; + } + + return; + } + + valueIsFunction = isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( valueIsFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( Array.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + for ( ; i < max; i++ ) { + option = options[ i ]; + + if ( ( option.selected || i === index ) && + + !option.disabled && + ( !option.parentNode.disabled || + !nodeName( option.parentNode, "optgroup" ) ) ) { + + value = jQuery( option ).val(); + + if ( one ) { + return value; + } + + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + } + + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( Array.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + +var location = window.location; + +var nonce = { guid: Date.now() }; + +var rquery = ( /\?/ ); + +jQuery.parseXML = function( data ) { + var xml, parserErrorElem; + if ( !data || typeof data !== "string" ) { + return null; + } + + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) {} + + parserErrorElem = xml && xml.getElementsByTagName( "parsererror" )[ 0 ]; + if ( !xml || parserErrorElem ) { + jQuery.error( "Invalid XML: " + ( + parserErrorElem ? + jQuery.map( parserErrorElem.childNodes, function( el ) { + return el.textContent; + } ).join( "\n" ) : + data + ) ); + } + return xml; +}; + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function( e ) { + e.stopPropagation(); + }; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = lastElement = tmp = elem = elem || document; + + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + lastElement = cur; + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + handle = ( dataPriv.get( cur, "events" ) || Object.create( null ) )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { + + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + jQuery.event.triggered = type; + + if ( event.isPropagationStopped() ) { + lastElement.addEventListener( type, stopPropagationCallback ); + } + + elem[ type ](); + + if ( event.isPropagationStopped() ) { + lastElement.removeEventListener( type, stopPropagationCallback ); + } + + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( Array.isArray( obj ) ) { + + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + add( prefix, v ); + + } else { + + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && toType( obj ) === "object" ) { + + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + add( prefix, obj ); + } +} + +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + var value = isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + if ( a == null ) { + return ""; + } + + if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ).filter( function() { + var type = this.type; + + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ).map( function( _i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( Array.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\ + + prefilters = {}, + + transports = {}, + + allTypes = "*/".concat( "*" ), + + originAnchor = document.createElement( "a" ); + +originAnchor.href = location.href; + +function addToPrefiltersOrTransports( structure ) { + + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( isFunction( func ) ) { + + while ( ( dataType = dataTypes[ i++ ] ) ) { + + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + finalDataType = finalDataType || firstDataType; + } + + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + dataTypes = s.dataTypes.slice(); + + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + if ( current === "*" ) { + + current = prev; + + } else if ( prev !== "*" && prev !== current ) { + + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + if ( !conv ) { + for ( conv2 in converters ) { + + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + if ( conv === true ) { + conv = converters[ conv2 ]; + + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + if ( conv !== true ) { + + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + active: 0, + + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + converters: { + + "* text": String, + + "text html": true, + + "text json": JSON.parse, + + "text xml": jQuery.parseXML + }, + + flatOptions: { + url: true, + context: true + } + }, + + ajaxSetup: function( target, settings ) { + return settings ? + + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + ajax: function( url, options ) { + + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + options = options || {}; + + var transport, + + cacheURL, + + responseHeadersString, + responseHeaders, + + timeoutTimer, + + urlAnchor, + + completed, + + fireGlobals, + + i, + + uncached, + + s = jQuery.ajaxSetup( {}, options ), + + callbackContext = s.context || s, + + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + statusCode = s.statusCode || {}, + + requestHeaders = {}, + requestHeadersNames = {}, + + strAbort = "canceled", + + jqXHR = { + readyState: 0, + + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); + } + } + match = responseHeaders[ key.toLowerCase() + " " ]; + } + return match == null ? null : match.join( ", " ); + }, + + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + jqXHR.always( map[ jqXHR.status ] ); + } else { + + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + deferred.promise( jqXHR ); + + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + s.type = options.method || options.type || s.method || s.type; + + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + try { + urlAnchor.href = s.url; + + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + s.crossDomain = true; + } + } + + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + if ( completed ) { + return jqXHR; + } + + fireGlobals = jQuery.event && s.global; + + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + s.type = s.type.toUpperCase(); + + s.hasContent = !rnoContent.test( s.type ); + + cacheURL = s.url.replace( rhash, "" ); + + if ( !s.hasContent ) { + + uncached = s.url.slice( cacheURL.length ); + + if ( s.data && ( s.processData || typeof s.data === "string" ) ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + delete s.data; + } + + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + + uncached; + } + + s.url = cacheURL + uncached; + + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + return jqXHR.abort(); + } + + strAbort = "abort"; + + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + if ( completed ) { + return jqXHR; + } + + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + if ( completed ) { + throw e; + } + + done( -1, e ); + } + } + + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + if ( completed ) { + return; + } + + completed = true; + + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + transport = undefined; + + responseHeadersString = headers || ""; + + jqXHR.readyState = status > 0 ? 4 : 0; + + isSuccess = status >= 200 && status < 300 || status === 304; + + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + if ( !isSuccess && + jQuery.inArray( "script", s.dataTypes ) > -1 && + jQuery.inArray( "json", s.dataTypes ) < 0 ) { + s.converters[ "text script" ] = function() {}; + } + + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + if ( isSuccess ) { + + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + } else if ( status === 304 ) { + statusText = "notmodified"; + + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( _i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + if ( isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + +jQuery.ajaxPrefilter( function( s ) { + var i; + for ( i in s.headers ) { + if ( i.toLowerCase() === "content-type" ) { + s.contentType = s.headers[ i ] || ""; + } + } +} ); + +jQuery._evalUrl = function( url, options, doc ) { + return jQuery.ajax( { + url: url, + + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options, doc ); + } + } ); +}; + +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var htmlIsFunction = isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +var xhrSuccessStatus = { + + 0: 200, + + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + +callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.ontimeout = + xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; +}; + +xhr.onload = callback(); +errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); + +if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; +} else { + xhr.onreadystatechange = function() { + + if ( xhr.readyState === 4 ) { + + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; +} + +callback = callback( "abort" ); + +try { + + xhr.send( options.hasContent && options.data || null ); +} catch ( e ) { + + if ( callback ) { + throw e; + } +} +}, + +abort: function() { +if ( callback ) { + callback(); +} +} +}; +} +} ); + +jQuery.ajaxPrefilter( function( s ) { +if ( s.crossDomain ) { +s.contents.script = false; +} +} ); + +jQuery.ajaxSetup( { +accepts: { +script: "text/javascript, application/javascript, " + +"application/ecmascript, application/x-ecmascript" +}, +contents: { +script: /\b(?:java|ecma)script\b/ +}, +converters: { +"text script": function( text ) { +jQuery.globalEval( text ); +return text; +} +} +} ); + +jQuery.ajaxPrefilter( "script", function( s ) { +if ( s.cache === undefined ) { +s.cache = false; +} +if ( s.crossDomain ) { +s.type = "GET"; +} +} ); + +jQuery.ajaxTransport( "script", function( s ) { + +if ( s.crossDomain || s.scriptAttrs ) { +var script, callback; +return { +send: function( _, complete ) { +script = jQuery( " + + + diff --git a/data/www/edit.html b/data/www/edit.html new file mode 100644 index 0000000..9f29e48 --- /dev/null +++ b/data/www/edit.html @@ -0,0 +1,191 @@ + + + + Edit file + + + + + + + +

Edit file

+
+
+
+
+ +
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/data/www/edit_old.html b/data/www/edit_old.html new file mode 100644 index 0000000..e76f413 --- /dev/null +++ b/data/www/edit_old.html @@ -0,0 +1,151 @@ + + + + Edit file + + + + + + +

Edit file

+
+ Editing file: {{SAVE_PATH_INPUT}} +
+
+
+ +
+
+ {{SAVE_PATH_INPUT}} + + + + +
+
+
+ + + + + + \ No newline at end of file diff --git a/data/www/failed.html b/data/www/failed.html new file mode 100644 index 0000000..cfcf05e --- /dev/null +++ b/data/www/failed.html @@ -0,0 +1,33 @@ + + + + Update Failed + + + + + + + +
+

The update has failed.

+
+ +
+ + + + \ No newline at end of file diff --git a/data/www/files.html b/data/www/files.html new file mode 100644 index 0000000..54f37df --- /dev/null +++ b/data/www/files.html @@ -0,0 +1,276 @@ + + + + File Manager + + + + + + + + + +
+

File Manager

+ +
+ File list +
+

  Total: {{FS_TOTAL_BYTES}}, Used: {{FS_USED_BYTES}}, Available: {{FS_FREE_BYTES}}

+
+ + {{LISTED_FILES}} +
Listing: Size:
+
+
+ +
+ +
+ File upload +
+
+ + + + + + + + + + + +
+
+    + +
+
+
+ +
+
+
+ +
+ +
+ Edit file +
+
+ + + +
+
+
+
+ +
+ +
+ Delete file +
+
+ + + +
+
+
+
+ +
+ + + + +
+ + + \ No newline at end of file diff --git a/data/www/home.html b/data/www/home.html new file mode 100644 index 0000000..84f6450 --- /dev/null +++ b/data/www/home.html @@ -0,0 +1,303 @@ + + + + System Summary + + + + + + + +

System Summary

+
+ +
+ Booth Overview +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+
+ +
+ System Information +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ +
+
+ + +
+
+
+ + +
+ Network / WiFi +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+
+ +
+ Bluetoth LE +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+
+ +
+ Luma Stiks +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+
+ +
+ + + + \ No newline at end of file diff --git a/data/www/index.html b/data/www/index.html new file mode 100644 index 0000000..6c6f81a --- /dev/null +++ b/data/www/index.html @@ -0,0 +1,29 @@ + + + + + ESP32 AP + + + +

Welcome to ESP32 AP

+

This is a simple web server running on ESP32 in Access Point mode.

+
+ + + \ No newline at end of file diff --git a/data/www/lights.html b/data/www/lights.html new file mode 100644 index 0000000..01a004b --- /dev/null +++ b/data/www/lights.html @@ -0,0 +1,527 @@ + + + + + + + + + + + + + +
+

Lights Configuration

+
+ Countdown ( Constant Light ) +
+
+ + +
+
+ + +
+
+
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+ + +
+
+ +      + +
+
+
+
+ +
+
+ Saved Animation Profiles +
+
+ + +
+
+ + + +
+
+
+
+ +
+ + + + diff --git a/data/www/navbar.html b/data/www/navbar.html new file mode 100644 index 0000000..406a174 --- /dev/null +++ b/data/www/navbar.html @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/data/www/ok.html b/data/www/ok.html new file mode 100644 index 0000000..6df4dda --- /dev/null +++ b/data/www/ok.html @@ -0,0 +1,34 @@ + + + + Update Success + + + + + + + +
+

The update was successful.

+
+ +
+ + + + \ No newline at end of file diff --git a/data/www/setup.html b/data/www/setup.html new file mode 100644 index 0000000..18b6fb9 --- /dev/null +++ b/data/www/setup.html @@ -0,0 +1,385 @@ + + + + Booth Configuration Tools + + + + + + + + +

System Setup

+
+ +
+ LED Strip #1 Settings +
+
+
+ +
+
+ +
+
+ +
+
+
+ +
+ LED Strip #1 Settings +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ + +
+ Test Tool (Single LED) + +
+
+ +
+
+ +
+ +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ + +
+ Luma Stiks Found + +
+
+
+ + +
+
+
+ + +
+
+ +
+
+
+ + +
+
+
+ + +
+
+ +
+
+
+ + +
+
+
+ + +
+
+ +
+
+
+ + +
+
+
+ + +
+
+ +
+
+ +
+
+ +
+ +
+
+ +
+ + + + \ No newline at end of file diff --git a/data/www/upgrade.html b/data/www/upgrade.html new file mode 100644 index 0000000..f7da5e9 --- /dev/null +++ b/data/www/upgrade.html @@ -0,0 +1,234 @@ + + + + + + Firmware Upgrade + + + + +
+

Firmware Upgrade

+ +
+ + Disconnected +
+ +
+

Current Version: -

+

Latest Version: -

+
+ +
+ + +
+ +
+
+ + + + \ No newline at end of file diff --git a/data/www/wifi.html b/data/www/wifi.html new file mode 100644 index 0000000..06e3df9 --- /dev/null +++ b/data/www/wifi.html @@ -0,0 +1,159 @@ + + + + + + Wifi Access + + + + + +

Wifi Access

+
+
+ + +
+ + + + + +
+ + + + diff --git a/diagram.json b/diagram.json new file mode 100644 index 0000000..3cf5955 --- /dev/null +++ b/diagram.json @@ -0,0 +1,8 @@ +{ + "version": 1, + "author": "David P", + "editor": "wokwi", + "parts": ["board-esp32-s3-devkitc-1"], + "connections": [ [ "esp:TX", "$serialMonitor:RX", "", [] ], [ "esp:RX", "$serialMonitor:TX", "", [] ] ], + "dependencies": {} +} \ No newline at end of file diff --git a/docs/!instructions.txt b/docs/!instructions.txt new file mode 100644 index 0000000..95720bd --- /dev/null +++ b/docs/!instructions.txt @@ -0,0 +1,18 @@ + +Choose Control App: + 1) Open app-events.json + 2) Change index to corresponding app + a. 0=DSLRBooth, 1=...., 2=..... + +Front Constant Light: ( to enable the light) + 1) Open led-devices.json + 2) Change "front-light", "en" to true + +Rear Constant Light: + 1) Open led-devices.json + 2) Change "rear-light", "en" to true + + + + +**anim-profiles \ No newline at end of file diff --git a/docs/DSLRBooth Mode Wifi no bluetooth.md b/docs/DSLRBooth Mode Wifi no bluetooth.md new file mode 100644 index 0000000..b99510b --- /dev/null +++ b/docs/DSLRBooth Mode Wifi no bluetooth.md @@ -0,0 +1,2 @@ +# DSLRBooth Mode Wifi no bluetooth + diff --git a/docs/Manual Mode.md b/docs/Manual Mode.md new file mode 100644 index 0000000..c1f0c63 --- /dev/null +++ b/docs/Manual Mode.md @@ -0,0 +1,3 @@ +# Manual Mode + +asdad diff --git a/docs/Sys Report.txt b/docs/Sys Report.txt new file mode 100644 index 0000000..74c6f70 --- /dev/null +++ b/docs/Sys Report.txt @@ -0,0 +1,51 @@ +********** APP CONTROL ********** +App: DSLRBooth +Mode: + +********** PERIPHERALS ********** +RGB Strip1 Active: true, Size: 164, Core: 0, I2S_Ch: 0 + Shift: 0, Offset: 0, RGB Order: grb, Pin: 30 +RGB Strip2 Active: true, Size: 164, Core: 0, I2S_Ch: 0 + Shift: 0, Offset: 0, RGB Order: grb, Pin: 30 +Front Light Active: true, Relay: 0, Freq: 500 +Rear Light Active: false, Relay: 1, Freq: 500 +Buzzer: On +OLED: Off + +********** NETWORK ********** +Wifi Client Active: true, Status: Connected + SSID: ATA, RSSi:-107, Ch:12, Encryp: asdasda, + IP: 192.168.0.15, MAC: XX:XX:XX:XX:XX, +Wifi AP Active: false, Status: Client connected, + IP: 192.168.4.1 + +********** Chip ********** +Clock: 240Mhz, RAM: asdfas, Used: 23423, Free: 23423 +ID: 23:23:43:32:23:43 + +********** Bluetooth LE ********** +Active: true, Server SSID: ATA_COMMXX, Connected: +SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" +CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" +CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" + +********** FAN / T SENSOR ********** +Fan Active: true, Relay: 3 +TSensor Active: true, Setpoint1: 80, Setpoint2: 85 +T Now: 78.2F + +********** 12V Monitor ********** +Volt: 12.2V +********** Buttons ********** + + +********** Touch Sensors ********** + +********** 433 RX/TX ********** +RX Active: true +TX Active: true + +********** Tasks & CPU Usage ********** +Task1... CPU Usage: 23 Priority:3 +Task2... +Task3... diff --git a/docs/Todo (add).doc b/docs/Todo (add).doc new file mode 100644 index 0000000..86d8e0d --- /dev/null +++ b/docs/Todo (add).doc @@ -0,0 +1,22 @@ + + +2) Make I2S buffer setting automatic based on pixel size ---------------- DONE + +3) Add Address Input box for firmware web location + +4) cpu / thread water mark diagnostics + +5) ble multi client + +6) Strip 2 simulataneous function Test + +7) Add button to control Rear Lights + + +**** Add LED Color Indication on BLE Connection established and disconnect + +*** + + + +**** Saving from web to LittleFS.... Write to temporary file then rename. \ No newline at end of file diff --git a/docs/Todo.(fixes)doc b/docs/Todo.(fixes)doc new file mode 100644 index 0000000..0f94156 --- /dev/null +++ b/docs/Todo.(fixes)doc @@ -0,0 +1,10 @@ +1) Helio and Lumia Lighting Logic + +2) Rear Light Button Ramp control + +3) Better Tunes + +4) Save Wifi Creds in Flash...not LittleFS + +5) Config page, + Booth Type, Front active, Rear Active, \ No newline at end of file diff --git a/docs/cli commands.txt b/docs/cli commands.txt new file mode 100644 index 0000000..2714777 --- /dev/null +++ b/docs/cli commands.txt @@ -0,0 +1,23 @@ +1. set-relay -value -freq +2. set-event -index +3. set-front-light -value -freq +4. playtune -index x + playtune "dasdasafsdfasdf" +5. readtemp -c +6. restart +7. set-bright +8. readfile -f filename +9. list directory +10. trigger button +11. echo +12. ble-msg +13. ping +14. post ble command +15. post tcp command +16. set wifi creds +17. start AP +18. start ble +19. test event anim colmain colbase density speed +20. send rf +21. get 12V value +22. \ No newline at end of file diff --git a/esp32s3_atabooth_8mb.code-workspace b/esp32s3_atabooth_8mb.code-workspace new file mode 100644 index 0000000..2ce8fdd --- /dev/null +++ b/esp32s3_atabooth_8mb.code-workspace @@ -0,0 +1,74 @@ +{ + "folders": [ + { + "path": "." + }, + { + "path": "../esp32s3_module_8mb" + } + ], + "settings": { + "files.associations": { + "*.service": "ini", + "new": "cpp", + "array": "cpp", + "atomic": "cpp", + "*.tcc": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "condition_variable": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "list": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "map": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "ratio": "cpp", + "regex": "cpp", + "set": "cpp", + "string": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "fstream": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "mutex": "cpp", + "ostream": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "thread": "cpp", + "cinttypes": "cpp", + "typeinfo": "cpp" + }, + "liveServer.settings.multiRootWorkspaceName": "esp32s3_atabooth_8mb" + } +} \ No newline at end of file diff --git a/firmware_update/GenUpdate.py b/firmware_update/GenUpdate.py new file mode 100644 index 0000000..1356185 --- /dev/null +++ b/firmware_update/GenUpdate.py @@ -0,0 +1,165 @@ +import os +import shutil +import hashlib +import json + +def copy_folder_to_destination(src_path, dest_path, skip_dirs=None, skip_files=None): + # Ensure the destination directory exists + os.makedirs(os.path.dirname(dest_path), exist_ok=True) + + # Remove existing folder if present + if os.path.exists(dest_path): + shutil.rmtree(dest_path) + + # Create destination directory + os.makedirs(dest_path) + + # Walk through source directory + for root, dirs, files in os.walk(src_path): + # Remove directories to skip from dirs list + if skip_dirs: + dirs[:] = [d for d in dirs if d not in skip_dirs] + + # Calculate relative path + rel_path = os.path.relpath(root, src_path) + dest_dir = os.path.join(dest_path, rel_path) + + # Create corresponding destination directory + os.makedirs(dest_dir, exist_ok=True) + + # Copy files that aren't in skip_files + for file in files: + if skip_files and file in skip_files: + continue + src_file = os.path.join(root, file) + dest_file = os.path.join(dest_dir, file) + shutil.copy2(src_file, dest_file) + +def calculate_md5(file_path): + hash_md5 = hashlib.md5() + with open(file_path, "rb") as f: + for chunk in iter(lambda: f.read(4096), b""): + hash_md5.update(chunk) + return hash_md5.hexdigest() + +def get_file_size(file_path): + return os.path.getsize(file_path) + +def update_json_file(json_array, folder_path): + # Create new data array for files + file_array = [] + + # Walk through the copied folder and collect file details + for root, _, files in os.walk(folder_path): + for file in files: + file_path = os.path.join(root, file) + relative_path = os.path.relpath(file_path, folder_path) + + # Replace backslashes with forward slashes + relative_path = relative_path.replace('\\', '/') + + file_entry = { + "remote": os.path.join("data/", relative_path), + "local": os.path.join("/", relative_path), + "md5": calculate_md5(file_path), + "size": get_file_size(file_path) + } + file_array.append(file_entry) + + # Replace the contents of the input json_array with new data + json_array.clear() + json_array.extend(file_array) + + +def update_files(src_path, dest_path, skip_dirs=None, skip_files=None): + # Check if the source folder exists + if not os.path.isdir(src_path) or not os.path.isdir(dest_path): + print("Invalid folder path!") + return + + # Copy all data contents + copy_folder_to_destination(src_path, dest_path, skip_dirs, skip_files) + print("Folder copied successfully.") + + + +def main(): + + here_path = os.path.dirname(os.path.abspath(__file__)) + project_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + # Path of the folder to copy (you can modify this) + src_folder_name = "data" + src_path = os.path.join(project_path, src_folder_name) + #print(f"source path: {src_path}") + + # Path of the destination folder + dest_folder_name = "firmware_update\\latest\\data" + dest_path = os.path.join(project_path, dest_folder_name) + #print(f"destination path: {dest_path}") + + # Path of the firmware binary file + bin_name = ".pio\\build\\esp32s3dev\\firmware.bin" + + # Skip these directories + skip_dirs = ["boards", "booths"] + + # Skip these files + skip_files = ["wifi.json", "system.json", "luma-stiks.json", ] + + + update_files = input("Do you want to update the files? (y/n): ") + + # *********************** Copy Data Files *********************** + if update_files.lower() == "y": + update_files(src_path, dest_path, skip_dirs, skip_files) + + + # *********************** Copy Binary file *********************** + update_firmware = input("Do you want to update the firmware? (y/n): ") + if update_firmware.lower() == "y": + # Copy firmware.bin to the destination + bin_path = os.path.join(project_path, bin_name) + shutil.copy(bin_path, here_path) + #print(f"firmware path: {bin_path}") + print("firmware.bin copied successfully.") + + + + # *********************** Process update.json *********************** + # Update the JSON file + json_path = os.path.join(here_path, "update.json") + print(f"json path: {json_path}") + + # Read existing JSON + with open(json_path, "r") as f: + try: + json_doc = json.load(f) + except json.JSONDecodeError: + print("Invalid JSON file!") + return + + # process the files array + #if update_files.lower() == "y": + json_files_array = json_doc["files"] + update_json_file(json_files_array, dest_path) + #print(f"Folder {os.path.basename(src_path)} processed successfully.") + + + # *********************** Process firmwware.bin in update.json *********************** + # process the firmware + if update_firmware.lower() == "y": + json_firmware = json_doc["firmware"] + firmware_path = os.path.join(here_path, "firmware.bin") + json_firmware["md5"] = calculate_md5(firmware_path) + json_firmware["size"] = get_file_size(firmware_path) + + + # Write updated JSON + with open(json_path, "w") as f: + json.dump(json_doc, f, indent=4) + + print("Update JSON files created successfully.") + +if __name__ == "__main__": + main() diff --git a/firmware_update/UploadToGoogle.py b/firmware_update/UploadToGoogle.py new file mode 100644 index 0000000..d5747a1 --- /dev/null +++ b/firmware_update/UploadToGoogle.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +import os +import datetime +from pathlib import Path +from google.cloud import storage + +# ============================================================================= +# CONFIGURATION CONSTANTS +# ============================================================================= + +CREATE_BACKUP = False +UPLOAD_FIRMWARE = True +UPLOAD_MANIFEST = True +UPLOAD_DATA = True + + +# The name of your Google Cloud Storage bucket. +GCS_BUCKET_NAME = 'boothifier' # <-- Change this + +# The destination directory (prefix) inside your bucket. +# For example, 'release' or 'prod'. Use an empty string ('') to use the bucket root. +DESTINATION_DIR = 'latest' # <-- Change this (or leave '' for bucket root) +BACKUPS_DIR = 'backups' + +LOCAL_ROOT_PATH = Path(__file__).parent.resolve() + +# Path to your Google Cloud credentials JSON file. +GOOGLE_APPLICATION_CREDENTIALS_PATH = str(LOCAL_ROOT_PATH / 'loyal-column-439819-e3-8cddff2ee2c2.json') + + +# Local path to the firmware file. +LOCAL_FIRMWARE_PATH = str(LOCAL_ROOT_PATH / 'firmware.bin') # <-- Change this if needed +LOCAL_MANIFEST_PATH = str(LOCAL_ROOT_PATH / 'update.json') # <-- Change this if needed + +# Local path to the data directory. +LOCAL_DATA_DIRECTORY = 'data' # <-- Change this if needed + +# ============================================================================= +# SET UP GOOGLE CLOUD CREDENTIALS +# ============================================================================= +os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = GOOGLE_APPLICATION_CREDENTIALS_PATH + +# ============================================================================= +# HELPER FUNCTIONS +# ============================================================================= + +def backup_existing_files(bucket, destination_prefix, backups_prefix, backup_folder): + """ + Copy every blob in the bucket with a name starting with destination_prefix + (except any files already in a backup folder) into a backup folder under backups_prefix. + + Files will be copied into: + // + """ + blobs = bucket.list_blobs(prefix=destination_prefix) + for blob in blobs: + # Compute the file's relative path by removing the destination_prefix. + if destination_prefix: + # Remove the destination_prefix and any leading slashes. + relative_path = blob.name[len(destination_prefix):].lstrip('/') + else: + relative_path = blob.name + + # Construct the new blob name in the backup folder under backups_prefix. + if backups_prefix: + new_blob_name = f"{backups_prefix}/{backup_folder}/{relative_path}" + else: + new_blob_name = f"{backup_folder}/{relative_path}" + + print(f"Backing up: copying '{blob.name}' to '{new_blob_name}'") + bucket.copy_blob(blob, bucket, new_blob_name) + +def upload_file(bucket, local_path, destination_blob_name): + """ + Upload a single file to the bucket with the specified blob name. + Overwrites the blob if it already exists. + """ + print(f"Uploading file '{local_path}' to '{destination_blob_name}'") + blob = bucket.blob(destination_blob_name) + # Set Cache-Control header to force clients to always fetch fresh content. + blob.cache_control = 'private, max-age=0, no-transform' + blob.upload_from_filename(local_path) + +def upload_directory(bucket, local_directory, destination_prefix): + """ + Recursively upload the contents of the local directory to the bucket under + destination_prefix. The directory structure is preserved. + """ + for root, _, files in os.walk(local_directory): + for file in files: + file_local_path = os.path.join(root, file) + # Compute the relative path of the file from the base directory. + rel_path = os.path.relpath(file_local_path, local_directory) + if destination_prefix: + dest_blob_name = f"{destination_prefix}/{rel_path}" + else: + dest_blob_name = rel_path + upload_file(bucket, file_local_path, dest_blob_name) + +# ============================================================================= +# MAIN FUNCTION +# ============================================================================= + +def main(): + # Initialize the Google Cloud Storage client. + client = storage.Client() + bucket = client.bucket(GCS_BUCKET_NAME) + + # Normalize the destination prefix by stripping any trailing slashes. + destination_prefix = DESTINATION_DIR.strip('/') + backups_prefix = BACKUPS_DIR.strip('/') + + if(CREATE_BACKUP): + # Create a backup folder name with a timestamp. + timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + backup_folder = f"backup_{timestamp}" + print(f"Creating backup folder '{backup_folder}' inside '{destination_prefix}' and copying existing files...") + backup_existing_files(bucket, destination_prefix, backups_prefix, backup_folder) + + + if(UPLOAD_FIRMWARE): + print("Uploading firmware...") + # Upload the firmware file. + if destination_prefix: + firmware_destination = f"{destination_prefix}/firmware.bin" + else: + firmware_destination = "firmware.bin" + upload_file(bucket, LOCAL_FIRMWARE_PATH, firmware_destination) + + if(UPLOAD_MANIFEST): + print("Uploading manifest...") + # Upload the manifest. + if destination_prefix: + manifest_destination = f"{destination_prefix}/update.json" + else: + manifest_destination = "update.json" + upload_file(bucket, LOCAL_MANIFEST_PATH, manifest_destination) + + if(UPLOAD_DATA): + print("Uploading data directory...") + # Upload the data directory. + if destination_prefix: + data_destination = f"{destination_prefix}/data" + else: + data_destination = "data" + upload_directory(bucket, LOCAL_DATA_DIRECTORY, data_destination) + + + print("Upload complete.") + +if __name__ == '__main__': + main() diff --git a/firmware_update/latest/data/ata-boothifier-upgrade.html b/firmware_update/latest/data/ata-boothifier-upgrade.html new file mode 100644 index 0000000..3821ad8 --- /dev/null +++ b/firmware_update/latest/data/ata-boothifier-upgrade.html @@ -0,0 +1,501 @@ + + + + + + ATA Firmware Update + + + + +

ATA Firmware Update

+ + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+ +
+ + +
+ + +
+ + + + +
+ + +
+ + +
+ + +
+ + +
+
+ + +
+ +
+ + + + diff --git a/firmware_update/latest/data/css/global-style.css b/firmware_update/latest/data/css/global-style.css new file mode 100644 index 0000000..a4edce2 --- /dev/null +++ b/firmware_update/latest/data/css/global-style.css @@ -0,0 +1,79 @@ +body { + /*background-color: #f7f7f7;*/ + font-family: Tahoma, Arial, sans-serif; + font-size: small; + margin: 0; + display: flex; + min-height: 100vh; +} +.main-container { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + padding: 20px; +} +h1 { + margin-top: 0; +} +input{ + cursor:pointer; +} +input[type="number"]{ + width: 100px; +} +#submit { + width:120px; +} +select{ + width:160px; +} +#select-path { + width:250px; +} +#select-dir { + width:90px; +} +#spacer-50 { + height: 50px; +} +#spacer-20 { + height: 20px; +} +#spacer-10 { + height: 10px; +} +table { + /*background-color: #dddddd;*/ + border-collapse: collapse; + width:600px; + margin: 0 auto; + overflow: visible; +} +td, th { + /*border: 1px solid #dddddd;*/ + text-align: left; + padding: 2px; +} +#first_td_th { + width:400px; +} +fieldset { + width:620px; + /*background-color: #f7f7f7;*/ + border-radius: 10px; + border-color: blue; +} +#format-notice { + color: #ff0000; +} +legend { + display: flex; + justify-content: center; + background-color:white; + background-blend-mode: darken; + border-radius: 10px; + padding: 1px 8px 2px 8px; + border-style: solid; + border-width: 1.0; +} \ No newline at end of file diff --git a/firmware_update/latest/data/css/nav.css b/firmware_update/latest/data/css/nav.css new file mode 100644 index 0000000..f74f175 --- /dev/null +++ b/firmware_update/latest/data/css/nav.css @@ -0,0 +1,72 @@ +.navbar { + width: 100%; + background-color: black; + border-bottom: 2px solid white; + display: flex; + justify-content: flex-start; + align-items: center; + position: sticky; + top: 0; + z-index: 1000; + } + .navbar-left { + padding: 0 10px; + } + .navbar ul { + list-style-type: none; + margin: 0; + padding: 0; + display: flex; + align-items: center; + position: relative; /* Ensure relative positioning for the submenu */ + } + .navbar ul li { + float: left; + position: relative; /* Ensure relative positioning for the submenu */ + } + .navbar ul li a { + display: block; + color: white; + text-align: center; + padding: 14px 16px; + text-decoration: none; + border-right: 1px solid white; + } + .navbar ul li a:hover { + background-color: grey; + } + .navbar ul li a.disabled { + color: grey; + pointer-events: none; + } + .navbar ul .submenu { + display: none; + position: absolute; + top: 100%; + left: 0; + background-color: black; + list-style-type: none; + margin: 0; + padding: 0; + border-top: 2px solid white; + z-index: 1000; + } + .navbar ul .submenu li { + float: none; + border-right: none; + } + .navbar ul .submenu li a { + padding: 10px 16px; + border-bottom: 1px solid white; + } + .navbar ul .submenu li a:hover { + background-color: grey; + } + .navbar ul li:hover > .submenu { + display: block; + } + .nav-image { + height: 40px; + width: auto; + } + \ No newline at end of file diff --git a/firmware_update/latest/data/favicon.ico b/firmware_update/latest/data/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..3030261a3e26621938f916036d0f10aff5f0e208 GIT binary patch literal 1150 zcmaKrZA?>V6vvMdT9jlnny85yF!%-%Gt|w@eK5E!Vl=uhDgy`D;1orXv6#7_qEUfX zDMY0-OkO7#2aRCV7FvtYa*>x(u%%ror8uilYztPQQ1tF!G)80My*c;hKF|F<=k0%p zX5gMPhwu!hgMLKwiD(rJOIUfkju#R3K6e@ad`y~DJN2epxdUf=7Yt_ULtH{l$XI^M z!l^=409)Ef?6sy(j%ZDGQRlH?>Nx7)bf*o58c#e<=+7`;d6uQGcI9d$<03{hS)xd^ z)f9)fHl=fW>IEckKQw(#W8xSw2Vao-fIG1Duq#TNYQJgtvMf^eV zzZm|Pdqm9A@p5TUx8lOOiryVnc|+@p_|C8}fnznvV&8D<_P*Yv+&A%?YX8fPU1@_{ z_zedi3w{^)=kz-uB+aXU?zdbwZx5{Q*+W;K zZJ6deQXHgtJ3oGypAgK?ZseZ@z6|v%!~6&^KQT{lm8@uMJn65KXHyfCHqCzcFr{0` zcHW5j?f1w0&V_y!gJ1G0|KU7up^C#^V2KsCeu&V>mqb=BQNv84GiQi0W_+~$(MRS0 O_VJJJUmxQMBKjZfDN?=w literal 0 HcmV?d00001 diff --git a/firmware_update/latest/data/images/atalogo.png b/firmware_update/latest/data/images/atalogo.png new file mode 100644 index 0000000000000000000000000000000000000000..40f3ee677364375e76d185ba6271c3c85d3b2c1a GIT binary patch literal 16690 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 C#3g+I literal 0 HcmV?d00001 diff --git a/firmware_update/latest/data/js/event-box.js b/firmware_update/latest/data/js/event-box.js new file mode 100644 index 0000000..cb01b1b --- /dev/null +++ b/firmware_update/latest/data/js/event-box.js @@ -0,0 +1,442 @@ +class EventBox extends HTMLElement { + constructor() { + super(); + this.attachShadow({ mode: 'open' }); + this.shadowRoot.innerHTML = ` + +
+
+ Event0
+
+
+ + +
+
+ +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+ +
+
+
+ +
+
+ `; + this.Title = this.shadowRoot.querySelector('#center-text'); + this.hueSelect = this.shadowRoot.querySelector('#hue-selector'); + this.AnimationList = this.shadowRoot.getElementById('animation-list'); + this.Speed = this.shadowRoot.getElementById('speed'); + this.HueRange = this.shadowRoot.querySelector('#huerange'); + this.Param1 = this.shadowRoot.querySelector('#param1'); + this.Param2 = this.shadowRoot.querySelector('#param2'); + this.Check1 = this.shadowRoot.querySelector('#check1'); + this.Check2 = this.shadowRoot.querySelector('#check2'); + this.Check3 = this.shadowRoot.querySelector('#check3'); + this.Check4 = this.shadowRoot.querySelector('#check4'); + + this.AnimationListLabel = this.shadowRoot.querySelector('#list-label'); + this.SpeedLabel = this.shadowRoot.querySelector('#speed-label'); + this.HueRangeLabel = this.shadowRoot.querySelector('#huerange-label'); + this.Param1Label = this.shadowRoot.getElementById('param1-label'); + this.Param2Label = this.shadowRoot.getElementById('param2-label'); + this.Check1Label = this.shadowRoot.getElementById('check1-label'); + this.Check2Label = this.shadowRoot.getElementById('check2-label'); + this.Check3Label = this.shadowRoot.getElementById('check3-label'); + this.Check4Label = this.shadowRoot.getElementById('check4-label'); + + this.AnimationPropsJson; + + this.SpeedCaption = ""; + this.HueRangeCaption = ""; + this.Param1Caption = ""; + this.Param2Caption = ""; + this.Check1Caption = ""; + this.Check2Caption = ""; + this.Check3Caption = ""; + + this.setSpeedCaption(`Speed: `); + this.Speed.min = 0; + this.Speed.max = 100; + + this.setHueRangeCaption(`Hue Range: `); + this.HueRange.min = 0; + this.HueRange.max = 360; + + this.setParam1Caption(`Param1: `); + this.Param1.min = 0; + this.Param1.max = 100; + + this.setParam2Caption(`Param2: `); + this.Param2.min = 0; + this.Param2.max = 100; + + this.setCheck1Caption(`Check1`); + this.setCheck2Caption(`Check2`); + this.setCheck3Caption(`Check3`); + this.setCheck4Caption(`50% Lum`); + + this.Index = 0; + + this.Speed.addEventListener('input', () => { this.updateSpeedLabel(); }); + this.HueRange.addEventListener('input', () => { this.updateHueRangeLabel(); }); + this.Param1.addEventListener('input', () => { this.updateParam1Label(); }); + this.Param2.addEventListener('input', () => { this.updateParam2Label(); }); + + this.TryButton = this.shadowRoot.querySelector('#try-button'); + this.TryButton.addEventListener('click', () => { + this.handleTryButtonClick(); + }); + + this.AnimationList.addEventListener('change', this.handleAnimationListChange.bind(this)); + } + + setIndex(i){ this.Index = i; } + getIndex(){ return this.Index; } + updateSpeedLabel(){ + if(!this.Speed.hidden){ + this.SpeedLabel.innerHTML = this.SpeedCaption + this.getSpeedValue(); + } + } + updateHueRangeLabel(){ + if(!this.HueRange.hidden){ + this.HueRangeLabel.innerHTML = this.HueRangeCaption + this.getHueRangeValue(); + } + } + updateParam1Label(){ + if(!this.Param1.hidden){ + this.Param1Label.innerHTML = this.Param1Caption + this.getParam1Value(); + } + } + updateParam2Label(){ + if(!this.Param2.hidden){ + this.Param2Label.innerHTML = this.Param2Caption + this.getParam2Value(); + } + } + setTitle(text){ + this.Title.textContent = text; + } + addOptionToList(value, text){ + const optionElement = document.createElement('option'); + optionElement.value = value; + optionElement.textContent = text; + this.AnimationList.appendChild(optionElement); + } + setHidden(hidden){ this.hidden = hidden; } + getHidden(){ return this.hidden; } + setHueValue(value){ this.hueSelect.setHue(value); } + getHueValue(){ return this.hueSelect.getSelectedHue(); } + setSpeedValue(value){ + this.Speed.value = value; + this.updateSpeedLabel(); + } + setSpeedCaption(caption){ + let hid = false; + if(caption.trim() === ""){ + hid = true; + } + this.SpeedCaption = caption; + this.SpeedLabel.innerHTML = caption; + this.Speed.hidden = hid; + this.SpeedLabel.hidden = hid; + } + getSpeedValue(){ return this.Speed.value; } + setHueRangeValue(value){ + this.HueRange.value = value; + this.updateHueRangeLabel() + } + setHueRangeCaption(caption){ + let hid = false; + if(caption.trim() === ""){ + hid = true; + } + this.HueRangeCaption = caption; + this.HueRangeLabel.innerHTML = caption; + this.HueRange.hidden = hid; + this.HueRangeLabel.hidden = hid; + } + getHueRangeValue(){ return this.HueRange.value; } + setParam1Value(value){ + this.Param1.value = value; + this.updateParam1Label() + } + setParam1Caption(caption){ + let hid = false; + if(caption.trim() === ""){ + hid = true; + } + this.Param1Caption = caption; + this.Param1Label.innerHTML = caption; + this.Param1.hidden = hid; + this.Param1Label.hidden = hid; + } + getParam1Value(){ return this.Param1.value; } + setParam2Value(value){ + this.Param2.value = value; + this.updateParam2Label() + } + setParam2Caption(caption){ + let hid = false; + if(caption.trim() === ""){ + hid = true; + } + this.Param2Caption = caption; + this.Param2Label.innerHTML = caption; + this.Param2.hidden = hid; + this.Param2Label.hidden = hid; + } + getParam2Value(){ return this.Param2.value; } + + setCheck1Value(value){ this.Check1.checked = value; } + setCheck1Caption(caption){ + let hid = false; + if(caption.trim() === ""){ + hid = true; + } + this.Check1Label.innerHTML = caption; + this.Check1.hidden = hid; + this.Check1Label.hidden = hid; + } + getCheck1Value(){ return this.Check1.checked; } + + setCheck2Value(value){ this.Check2.checked = value; } + setCheck2Caption(caption){ + let hid = false; + if(caption.trim() === ""){ + hid = true; + } + this.Check2Label.innerHTML = caption; + this.Check2.hidden = hid; + this.Check2Label.hidden = hid; + } + getCheck2Value(){ return this.Check2.checked; } + + setCheck3Value(value){ this.Check3.checked = value; } + setCheck3Caption(caption){ + let hid = false; + if(caption.trim() === ""){ + hid = true; + } + this.Check3Label.innerHTML = caption; + this.Check3.hidden = hid; + this.Check3Label.hidden = hid; + } + getCheck3Value(){ return this.Check3.checked; } + + setCheck4Value(value){ this.Check4.checked = value; } + setCheck4Caption(caption){ + let hid = false; + if(caption.trim() === ""){ + hid = true; + } + this.Check4Label.innerHTML = caption; + this.Check4.hidden = hid; + this.Check4Label.hidden = hid; + } + getCheck4Value(){ return this.Check4.checked; } + + setAnimationIndex(index){ + this.AnimationList.selectedIndex = index; + const changeEvent = new Event('change'); + this.AnimationList.dispatchEvent(changeEvent); + } + getAnimationIndex(){ + return this.AnimationList.selectedIndex; + } + + handleTryButtonClick() { + const eventIndexValue = this.getIndex(); + const animIndexValue = this.getAnimationIndex(); + const hueValue = this.getHueValue(); + const speedValue = this.getSpeedValue(); + const colorRangeValue = this.getHueRangeValue(); + const param1Value = this.getParam1Value(); + const param2Value = this.getParam2Value(); + const check1Value = this.getCheck1Value(); + const check2Value = this.getCheck2Value(); + const check3Value = this.getCheck3Value(); + const check4Value = this.getCheck4Value(); + + this.dispatchEvent(new CustomEvent('tryClick', { + detail: { + eventIndex: eventIndexValue, + animIndex: animIndexValue, + hue: hueValue, + speed: speedValue, + colorRange: colorRangeValue, + param1: param1Value, + param2: param2Value, + check1: check1Value, + check2: check2Value, + check3: check3Value, + check4: check4Value + } + })); + } + + setAnimationCaptions(propsJson){ + this.AnimationPropsJson = propsJson; + + //add options to list + let x = 0; + this.AnimationPropsJson.forEach(props => { + this.addOptionToList(x, props.name); + x++; + }); + + } + + handleAnimationListChange(){ + const selectedIndex = this.getAnimationIndex(); + const selectedProps = this.AnimationPropsJson[selectedIndex]; + + this.updateControlProps(selectedProps); + } + + updateControlProps(props){ + this.setSpeedCaption(props.speed); + let s = props['hue-range']; + this.setHueRangeCaption(s || ""); + this.setParam1Caption(props.param1 || ""); + this.setParam2Caption(props.param2 || ""); + this.setCheck1Caption(props.check1 || ""); + this.setCheck2Caption(props.check2 || ""); + this.setCheck3Caption(props.check3 || ""); + this.setCheck4Caption(props.check4 || ""); + + this.updateSpeedLabel(); + this.updateHueRangeLabel(); + this.updateParam1Label(); + this.updateParam2Label(); + } + +} + +customElements.define('event-box', EventBox); + \ No newline at end of file diff --git a/firmware_update/latest/data/js/fwUoload.js b/firmware_update/latest/data/js/fwUoload.js new file mode 100644 index 0000000..446f3b4 --- /dev/null +++ b/firmware_update/latest/data/js/fwUoload.js @@ -0,0 +1,64 @@ +async function uploadFile(event) { + event.preventDefault(); + + const file = fileInput.files[0]; + if (!file) { + alert("Please select a file."); + return; + } + + // Existing file validation code... + + const formData = new FormData(); + formData.append('file', file); + + // Create a custom reader function + const reader = file.stream().getReader(); + const totalLength = file.size; + + let uploaded = 0; + + // Create a readable stream and use the custom reader function + const uploadStream = new ReadableStream({ + async start(controller) { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + uploaded += value.length; + let percentUploaded = (uploaded / totalLength * 100).toFixed(2); + + // Update progress bar and label + progressBar.value = percentUploaded; + progressLabel.innerHTML = `${percentUploaded}%`; + + controller.enqueue(value); + } + controller.close(); + }, + }); + + // Create a new Request with the readable stream as body + const req = new Request('/update', { + method: 'POST', + body: uploadStream, + headers: { + // Add any relevant headers here + }, + }); + + try { + const response = await fetch(req); + + if (response.ok) { + alert('Upload completed!'); + progressLabel.innerHTML = "Completed!"; + } else { + alert('An error occurred during the upload.'); + progressLabel.innerHTML = "Error!"; + } + } catch (error) { + console.error('Upload failed:', error); + } + } + \ No newline at end of file diff --git a/firmware_update/latest/data/js/hue-select.js b/firmware_update/latest/data/js/hue-select.js new file mode 100644 index 0000000..ca067d1 --- /dev/null +++ b/firmware_update/latest/data/js/hue-select.js @@ -0,0 +1,244 @@ + +class HueSelect extends HTMLElement { + constructor() { + super(); + this.attachShadow({ mode: 'open' }); + this.shadowRoot.innerHTML = ` + + + `; + + this.currentHue = 0; + this.colorList; + this.hueLabel = this.shadowRoot.getElementById('hue-label'); + + const selectedColorDiv = this.shadowRoot.getElementById('selectedColor'); + const colorPatch = selectedColorDiv.querySelector('.color-patch'); + + selectedColorDiv.addEventListener('click', () => { + const dropdownContent = this.shadowRoot.querySelector('.dropdown-content'); + dropdownContent.style.display = dropdownContent.style.display === 'block' ? 'none' : 'block'; + }); + + this.createColorOptions(); + this.setHue(0); + } + + generateColors() { + const colors = []; + for (let hue = 0; hue <= 360; hue += 10) { + if (hue == 360) { hue = 359; } + colors.push(hue); + } + + colors.push(-1); + colors.push(-2); + return colors; + } + + createColorOption(hue) { + const colorOption = document.createElement('div'); + colorOption.classList.add('color-option'); + + const colorPatch = document.createElement('span'); + colorPatch.classList.add('color-patch'); + + const colorText = document.createElement('span'); + colorText.classList.add('color-text'); + colorText.textContent = hue; + + const rgbHex = document.createElement('span'); + rgbHex.classList.add('rgb-hex'); + + if (hue === -2) { + colorPatch.style.backgroundColor = 'rgb(0,0,0)'; + rgbHex.innerHTML = '  #000000'; + } else if (hue === -1) { + colorPatch.style.backgroundColor = 'rgb(255,255,255)'; + rgbHex.innerHTML = '  #FFFFFF'; + } else { + colorPatch.style.backgroundColor = `hsl(${hue}, 100%, 50%)`; + const hexColor = this.hslToRgb(hue, 100, 50).toUpperCase(); + rgbHex.innerHTML = `  ${hexColor}`; + } + + colorOption.appendChild(colorPatch); + colorOption.appendChild(colorText); + colorOption.appendChild(rgbHex); + + colorOption.addEventListener('click', () => this.handleColorSelection(hue)); + + return colorOption; + } + + createColorOptions() { + const dropdownContent = this.shadowRoot.querySelector('.dropdown-content'); + this.colorList = this.generateColors(); + + this.colorList.forEach(hue => { + const colorOption = this.createColorOption(hue); + dropdownContent.appendChild(colorOption); + }); + } + + // Function to convert HSL to RGB + hslToRgb(h, s, l) { + h /= 360; + s /= 100; + l /= 100; + let r, g, b; + if (s === 0) { + r = g = b = l; // achromatic + } else { + const hue2rgb = (p, q, t) => { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; + return p; + }; + const q = l < 0.5 ? l * (1 + s) : l + s - l * s; + const p = 2 * l - q; + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + const toHex = (x) => { + const hex = Math.round(x * 255).toString(16); + return hex.length === 1 ? '0' + hex : hex; + }; + return `#${toHex(r)}${toHex(g)}${toHex(b)}`; + } + + handleColorSelection(hue) { + this.currentHue = hue; + const selectedColorDiv = this.shadowRoot.getElementById('selectedColor'); + const colorPatch = selectedColorDiv.querySelector('.color-patch'); + + // Update the color patch and hue label + const rgb = this.getRGBfromHue(hue); + colorPatch.style.backgroundColor = rgb; + this.setHueLabel(hue); + this.hideDropdown(); + + this.dispatchEvent(new CustomEvent('change', { detail: { hue, rgb } })); + } + + // Method to get the hue value of the selected item + getSelectedHue() { + return this.currentHue; + } + + getSelectedRGB() { + const selectedColorText = this.shadowRoot.getElementById('selectedColor').textContent.trim(); + return parseFloat(selectedColorText); + } + + getRGBfromHue(hue){ + if(hue == -1){ + return '#FFFFFF'; + }else if(hue == -2){ + return '#000000'; + }else{ + return this.hslToRgb(hue, 100, 50);; + } + } + + // Method to get the RGB value of the selected item + getSelectedRgb() { + const selectedHue = this.getSelectedHue(); + return getRGBfromHue(selectedHue); + } + + setHue(hue) { + // Round the input hue to the nearest 10th + let roundedHue = hue; + if(hue === -1 || hue === -2){ + roundedHue = hue; + }else{ + roundedHue = Math.round(hue / 10) * 10; + if (roundedHue >= 360) { + roundedHue = 359; + }else if (roundedHue < 0) { + roundedHue = 0; + } + } + + this.currentHue = roundedHue; + + this.colorList.forEach(colorVal => { + if (colorVal === roundedHue) { + this.handleColorSelection(roundedHue); + } + }); + + this.hideDropdown(); + } + + hideDropdown() { + const dropdownContent = this.shadowRoot.querySelector('.dropdown-content'); + dropdownContent.style.display = 'none'; + } + + setHueLabel(value){ + this.hueLabel.textContent = "Hue: " + value; + } + + + } + + customElements.define('hue-select', HueSelect); + \ No newline at end of file diff --git a/firmware_update/latest/data/js/jquery-3.7.1.js b/firmware_update/latest/data/js/jquery-3.7.1.js new file mode 100644 index 0000000..f7d84ab --- /dev/null +++ b/firmware_update/latest/data/js/jquery-3.7.1.js @@ -0,0 +1,8673 @@ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +"use strict"; + +var arr = []; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var flat = arr.flat ? function( array ) { + return arr.flat.call( array ); +} : function( array ) { + return arr.concat.apply( [], array ); +}; + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + +var isFunction = function isFunction( obj ) { + + return typeof obj === "function" && typeof obj.nodeType !== "number" && + typeof obj.item !== "function"; + }; + +var isWindow = function isWindow( obj ) { + return obj != null && obj === obj.window; + }; + +var document = window.document; + + var preservedScriptAttributes = { + type: true, + src: true, + nonce: true, + noModule: true + }; + + function DOMEval( code, node, doc ) { + doc = doc || document; + + var i, val, + script = doc.createElement( "script" ); + + script.text = code; + if ( node ) { + for ( i in preservedScriptAttributes ) { + + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); + } + } + } + doc.head.appendChild( script ).parentNode.removeChild( script ); + } + +function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} + +var version = "3.7.1", + + rhtmlSuffix = /HTML$/i, + + jQuery = function( selector, context ) { + + return new jQuery.fn.init( selector, context ); + }; + +jQuery.fn = jQuery.prototype = { + + jquery: version, + + constructor: jQuery, + + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + get: function( num ) { + + if ( num == null ) { + return slice.call( this ); + } + + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + pushStack: function( elems ) { + + var ret = jQuery.merge( this.constructor(), elems ); + + ret.prevObject = this; + + return ret; + }, + + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + even: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return ( i + 1 ) % 2; + } ) ); + }, + + odd: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return i % 2; + } ) ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + if ( typeof target === "boolean" ) { + deep = target; + + target = arguments[ i ] || {}; + i++; + } + + if ( typeof target !== "object" && !isFunction( target ) ) { + target = {}; + } + + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + if ( ( options = arguments[ i ] ) != null ) { + + for ( name in options ) { + copy = options[ name ]; + + if ( name === "__proto__" || target === copy ) { + continue; + } + + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; + + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; + } else { + clone = src; + } + copyIsArray = false; + + target[ name ] = jQuery.extend( deep, clone, copy ); + + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + return target; +}; + +jQuery.extend( { + + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isPlainObject: function( obj ) { + var proto, Ctor; + + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + if ( !proto ) { + return true; + } + + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + globalEval: function( code, options, doc ) { + DOMEval( code, { nonce: options && options.nonce }, doc ); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + text: function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + + while ( ( node = elem[ i++ ] ) ) { + + ret += jQuery.text( node ); + } + } + if ( nodeType === 1 || nodeType === 11 ) { + return elem.textContent; + } + if ( nodeType === 9 ) { + return elem.documentElement.textContent; + } + if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + + return ret; + }, + + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + isXMLDoc: function( elem ) { + var namespace = elem && elem.namespaceURI, + docElem = elem && ( elem.ownerDocument || elem ).documentElement; + + return !rhtmlSuffix.test( namespace || docElem && docElem.nodeName || "HTML" ); + }, + + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + return flat( ret ); + }, + + guid: 1, + + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), + function( _i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); + } ); + +function isArrayLike( obj ) { + + var length = !!obj && "length" in obj && obj.length, + type = toType( obj ); + + if ( isFunction( obj ) || isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +} +var pop = arr.pop; + +var sort = arr.sort; + +var splice = arr.splice; + +var whitespace = "[\\x20\\t\\r\\n\\f]"; + +var rtrimCSS = new RegExp( + "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", + "g" +); + +jQuery.contains = function( a, b ) { + var bup = b && b.parentNode; + + return a === bup || !!( bup && bup.nodeType === 1 && ( + + a.contains ? + a.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + ) ); +}; + +var rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g; + +function fcssescape( ch, asCodePoint ) { + if ( asCodePoint ) { + + if ( ch === "\0" ) { + return "\uFFFD"; + } + + return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + return "\\" + ch; +} + +jQuery.escapeSelector = function( sel ) { + return ( sel + "" ).replace( rcssescape, fcssescape ); +}; + +var preferredDoc = document, + pushNative = push; + +( function() { + +var i, + Expr, + outermostContext, + sortInput, + hasDuplicate, + push = pushNative, + + document, + documentElement, + documentIsHTML, + rbuggyQSA, + matches, + + expando = jQuery.expando, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + nonnativeSelectorCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|" + + "loop|multiple|open|readonly|required|scoped", + + identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", + + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + + "*([*^$|!~]?=)" + whitespace + + + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + + whitespace + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + + ".*" + + ")\\)|)", + + rwhitespace = new RegExp( whitespace + "+", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rleadingCombinator = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + + whitespace + "*" ), + rdescend = new RegExp( whitespace + "|>" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + ID: new RegExp( "^#(" + identifier + ")" ), + CLASS: new RegExp( "^\\.(" + identifier + ")" ), + TAG: new RegExp( "^(" + identifier + "|[*])" ), + ATTR: new RegExp( "^" + attributes ), + PSEUDO: new RegExp( "^" + pseudos ), + CHILD: new RegExp( + "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + bool: new RegExp( "^(?:" + booleans + ")$", "i" ), + + needsContext: new RegExp( "^" + whitespace + + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\([^\\r\\n\\f])", "g" ), + funescape = function( escape, nonHex ) { + var high = "0x" + escape.slice( 1 ) - 0x10000; + + if ( nonHex ) { + + return nonHex; + } + + return high < 0 ? + String.fromCharCode( high + 0x10000 ) : + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + unloadHandler = function() { + setDocument(); + }, + + inDisabledFieldset = addCombinator( + function( elem ) { + return elem.disabled === true && nodeName( elem, "fieldset" ); + }, + { dir: "parentNode", next: "legend" } + ); + +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +try { + push.apply( + ( arr = slice.call( preferredDoc.childNodes ) ), + preferredDoc.childNodes + ); + + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { + apply: function( target, els ) { + pushNative.apply( target, slice.call( els ) ); + }, + call: function( target ) { + pushNative.apply( target, slice.call( arguments, 1 ) ); + } + }; +} + +function find( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + nodeType = context ? context.nodeType : 9; + + results = results || []; + + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + if ( !seed ) { + setDocument( context ); + context = context || document; + + if ( documentIsHTML ) { + + if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { + + if ( ( m = match[ 1 ] ) ) { + + if ( nodeType === 9 ) { + if ( ( elem = context.getElementById( m ) ) ) { + + if ( elem.id === m ) { + push.call( results, elem ); + return results; + } + } else { + return results; + } + + } else { + + if ( newContext && ( elem = newContext.getElementById( m ) ) && + find.contains( context, elem ) && + elem.id === m ) { + + push.call( results, elem ); + return results; + } + } + + } else if ( match[ 2 ] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + } else if ( ( m = match[ 3 ] ) && context.getElementsByClassName ) { + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + if ( !nonnativeSelectorCache[ selector + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) ) { + + newSelector = selector; + newContext = context; + + if ( nodeType === 1 && + ( rdescend.test( selector ) || rleadingCombinator.test( selector ) ) ) { + + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + + if ( newContext != context || !support.scope ) { + + if ( ( nid = context.getAttribute( "id" ) ) ) { + nid = jQuery.escapeSelector( nid ); + } else { + context.setAttribute( "id", ( nid = expando ) ); + } + } + + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + + toSelector( groups[ i ] ); + } + newSelector = groups.join( "," ); + } + + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + + return select( selector.replace( rtrimCSS, "$1" ), context, results, seed ); +} + +function createCache() { + var keys = []; + + function cache( key, value ) { + + if ( keys.push( key + " " ) > Expr.cacheLength ) { + + delete cache[ keys.shift() ]; + } + return ( cache[ key + " " ] = value ); + } + return cache; +} + +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +function assert( fn ) { + var el = document.createElement( "fieldset" ); + + try { + return !!fn( el ); + } catch ( e ) { + return false; + } finally { + + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + + el = null; + } +} + +function createInputPseudo( type ) { + return function( elem ) { + return nodeName( elem, "input" ) && elem.type === type; + }; +} + +function createButtonPseudo( type ) { + return function( elem ) { + return ( nodeName( elem, "input" ) || nodeName( elem, "button" ) ) && + elem.type === type; + }; +} + +function createDisabledPseudo( disabled ) { + + return function( elem ) { + + if ( "form" in elem ) { + + if ( elem.parentNode && elem.disabled === false ) { + + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + return elem.isDisabled === disabled || + + elem.isDisabled !== !disabled && + inDisabledFieldset( elem ) === disabled; + } + + return elem.disabled === disabled; + + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + return false; + }; +} + +function createPositionalPseudo( fn ) { + return markFunction( function( argument ) { + argument = +argument; + return markFunction( function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + while ( i-- ) { + if ( seed[ ( j = matchIndexes[ i ] ) ] ) { + seed[ j ] = !( matches[ j ] = seed[ j ] ); + } + } + } ); + } ); +} + +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +function setDocument( node ) { + var subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + document = doc; + documentElement = document.documentElement; + documentIsHTML = !jQuery.isXMLDoc( document ); + + matches = documentElement.matches || + documentElement.webkitMatchesSelector || + documentElement.msMatchesSelector; + + if ( documentElement.msMatchesSelector && + + preferredDoc != document && + ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { + + subWindow.addEventListener( "unload", unloadHandler ); + } + + support.getById = assert( function( el ) { + documentElement.appendChild( el ).id = jQuery.expando; + return !document.getElementsByName || + !document.getElementsByName( jQuery.expando ).length; + } ); + + support.disconnectedMatch = assert( function( el ) { + return matches.call( el, "*" ); + } ); + + support.scope = assert( function() { + return document.querySelectorAll( ":scope" ); + } ); + + support.cssHas = assert( function() { + try { + document.querySelector( ":has(*,:jqfake)" ); + return false; + } catch ( e ) { + return true; + } + } ); + + if ( support.getById ) { + Expr.filter.ID = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute( "id" ) === attrId; + }; + }; + Expr.find.ID = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter.ID = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode( "id" ); + return node && node.value === attrId; + }; + }; + + Expr.find.ID = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + + elems = context.getElementsByName( id ); + i = 0; + while ( ( elem = elems[ i++ ] ) ) { + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + Expr.find.TAG = function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + } else { + return context.querySelectorAll( tag ); + } + }; + + Expr.find.CLASS = function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + rbuggyQSA = []; + + assert( function( el ) { + + var input; + + documentElement.appendChild( el ).innerHTML = + "" + + ""; + + if ( !el.querySelectorAll( "[selected]" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push( "~=" ); + } + + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push( ".#.+[+~]" ); + } + + if ( !el.querySelectorAll( ":checked" ).length ) { + rbuggyQSA.push( ":checked" ); + } + + input = document.createElement( "input" ); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + documentElement.appendChild( el ).disabled = true; + if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + input = document.createElement( "input" ); + input.setAttribute( "name", "" ); + el.appendChild( input ); + if ( !el.querySelectorAll( "[name='']" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + + whitespace + "*(?:''|\"\")" ); + } + } ); + + if ( !support.cssHas ) { + + rbuggyQSA.push( ":has" ); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); + + sortOrder = function( a, b ) { + + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + 1; + + if ( compare & 1 || + ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { + + if ( a === document || a.ownerDocument == preferredDoc && + find.contains( preferredDoc, a ) ) { + return -1; + } + + if ( b === document || b.ownerDocument == preferredDoc && + find.contains( preferredDoc, b ) ) { + return 1; + } + + return sortInput ? + ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + }; + + return document; +} + +find.matches = function( expr, elements ) { + return find( expr, null, null, elements ); +}; + +find.matchesSelector = function( elem, expr ) { + setDocument( elem ); + + if ( documentIsHTML && + !nonnativeSelectorCache[ expr + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + if ( ret || support.disconnectedMatch || + + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch ( e ) { + nonnativeSelectorCache( expr, true ); + } + } + + return find( expr, document, null, [ elem ] ).length > 0; +}; + +find.contains = function( context, elem ) { + + if ( ( context.ownerDocument || context ) != document ) { + setDocument( context ); + } + return jQuery.contains( context, elem ); +}; + +find.attr = function( elem, name ) { + + if ( ( elem.ownerDocument || elem ) != document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + if ( val !== undefined ) { + return val; + } + + return elem.getAttribute( name ); +}; + +find.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +jQuery.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + hasDuplicate = !support.sortStable; + sortInput = !support.sortStable && slice.call( results, 0 ); + sort.call( results, sortOrder ); + + if ( hasDuplicate ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + splice.call( results, duplicates[ j ], 1 ); + } + } + + sortInput = null; + + return results; +}; + +jQuery.fn.uniqueSort = function() { + return this.pushStack( jQuery.uniqueSort( slice.apply( this ) ) ); +}; + +Expr = jQuery.expr = { + + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + ATTR: function( match ) { + match[ 1 ] = match[ 1 ].replace( runescape, funescape ); + + match[ 3 ] = ( match[ 3 ] || match[ 4 ] || match[ 5 ] || "" ) + .replace( runescape, funescape ); + + if ( match[ 2 ] === "~=" ) { + match[ 3 ] = " " + match[ 3 ] + " "; + } + + return match.slice( 0, 4 ); + }, + + CHILD: function( match ) { + + match[ 1 ] = match[ 1 ].toLowerCase(); + + if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { + + if ( !match[ 3 ] ) { + find.error( match[ 0 ] ); + } + + match[ 4 ] = +( match[ 4 ] ? + match[ 5 ] + ( match[ 6 ] || 1 ) : + 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) + ); + match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); + + } else if ( match[ 3 ] ) { + find.error( match[ 0 ] ); + } + + return match; + }, + + PSEUDO: function( match ) { + var excess, + unquoted = !match[ 6 ] && match[ 2 ]; + + if ( matchExpr.CHILD.test( match[ 0 ] ) ) { + return null; + } + + if ( match[ 3 ] ) { + match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; + + } else if ( unquoted && rpseudo.test( unquoted ) && + + ( excess = tokenize( unquoted, true ) ) && + + ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { + + match[ 0 ] = match[ 0 ].slice( 0, excess ); + match[ 2 ] = unquoted.slice( 0, excess ); + } + + return match.slice( 0, 3 ); + } + }, + + filter: { + + TAG: function( nodeNameSelector ) { + var expectedNodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { + return true; + } : + function( elem ) { + return nodeName( elem, expectedNodeName ); + }; + }, + + CLASS: function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + ( pattern = new RegExp( "(^|" + whitespace + ")" + className + + "(" + whitespace + "|$)" ) ) && + classCache( className, function( elem ) { + return pattern.test( + typeof elem.className === "string" && elem.className || + typeof elem.getAttribute !== "undefined" && + elem.getAttribute( "class" ) || + "" + ); + } ); + }, + + ATTR: function( name, operator, check ) { + return function( elem ) { + var result = find.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + if ( operator === "=" ) { + return result === check; + } + if ( operator === "!=" ) { + return result !== check; + } + if ( operator === "^=" ) { + return check && result.indexOf( check ) === 0; + } + if ( operator === "*=" ) { + return check && result.indexOf( check ) > -1; + } + if ( operator === "$=" ) { + return check && result.slice( -check.length ) === check; + } + if ( operator === "~=" ) { + return ( " " + result.replace( rwhitespace, " " ) + " " ) + .indexOf( check ) > -1; + } + if ( operator === "|=" ) { + return result === check || result.slice( 0, check.length + 1 ) === check + "-"; + } + + return false; + }; + }, + + CHILD: function( type, what, _argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, _context, xml ) { + var cache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + if ( simple ) { + while ( dir ) { + node = elem; + while ( ( node = node[ dir ] ) ) { + if ( ofType ? + nodeName( node, name ) : + node.nodeType === 1 ) { + + return false; + } + } + + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + if ( forward && useCache ) { + + outerCache = parent[ expando ] || ( parent[ expando ] = {} ); + cache = outerCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( ( node = ++nodeIndex && node && node[ dir ] || + + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + if ( node.nodeType === 1 && ++diff && node === elem ) { + outerCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + + if ( useCache ) { + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); + cache = outerCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + if ( diff === false ) { + + while ( ( node = ++nodeIndex && node && node[ dir ] || + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + if ( ( ofType ? + nodeName( node, name ) : + node.nodeType === 1 ) && + ++diff ) { + + if ( useCache ) { + outerCache = node[ expando ] || + ( node[ expando ] = {} ); + outerCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + PSEUDO: function( pseudo, argument ) { + + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + find.error( "unsupported pseudo: " + pseudo ); + + if ( fn[ expando ] ) { + return fn( argument ); + } + + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction( function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf.call( seed, matched[ i ] ); + seed[ idx ] = !( matches[ idx ] = matched[ i ] ); + } + } ) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + + not: markFunction( function( selector ) { + + var input = [], + results = [], + matcher = compile( selector.replace( rtrimCSS, "$1" ) ); + + return matcher[ expando ] ? + markFunction( function( seed, matches, _context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + while ( i-- ) { + if ( ( elem = unmatched[ i ] ) ) { + seed[ i ] = !( matches[ i ] = elem ); + } + } + } ) : + function( elem, _context, xml ) { + input[ 0 ] = elem; + matcher( input, null, xml, results ); + + input[ 0 ] = null; + return !results.pop(); + }; + } ), + + has: markFunction( function( selector ) { + return function( elem ) { + return find( selector, elem ).length > 0; + }; + } ), + + contains: markFunction( function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || jQuery.text( elem ) ).indexOf( text ) > -1; + }; + } ), + + lang: markFunction( function( lang ) { + + if ( !ridentifier.test( lang || "" ) ) { + find.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( ( elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); + return false; + }; + } ), + + target: function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + root: function( elem ) { + return elem === documentElement; + }, + + focus: function( elem ) { + return elem === safeActiveElement() && + document.hasFocus() && + !!( elem.type || elem.href || ~elem.tabIndex ); + }, + + enabled: createDisabledPseudo( false ), + disabled: createDisabledPseudo( true ), + + checked: function( elem ) { + + return ( nodeName( elem, "input" ) && !!elem.checked ) || + ( nodeName( elem, "option" ) && !!elem.selected ); + }, + + selected: function( elem ) { + + if ( elem.parentNode ) { + + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + empty: function( elem ) { + + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + parent: function( elem ) { + return !Expr.pseudos.empty( elem ); + }, + + header: function( elem ) { + return rheader.test( elem.nodeName ); + }, + + input: function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + button: function( elem ) { + return nodeName( elem, "input" ) && elem.type === "button" || + nodeName( elem, "button" ); + }, + + text: function( elem ) { + var attr; + return nodeName( elem, "input" ) && elem.type === "text" && + + ( ( attr = elem.getAttribute( "type" ) ) == null || + attr.toLowerCase() === "text" ); + }, + + first: createPositionalPseudo( function() { + return [ 0 ]; + } ), + + last: createPositionalPseudo( function( _matchIndexes, length ) { + return [ length - 1 ]; + } ), + + eq: createPositionalPseudo( function( _matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + } ), + + even: createPositionalPseudo( function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + odd: createPositionalPseudo( function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + lt: createPositionalPseudo( function( matchIndexes, length, argument ) { + var i; + + if ( argument < 0 ) { + i = argument + length; + } else if ( argument > length ) { + i = length; + } else { + i = argument; + } + + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + gt: createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ) + } +}; + +Expr.pseudos.nth = Expr.pseudos.eq; + +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +function tokenize( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + if ( !matched || ( match = rcomma.exec( soFar ) ) ) { + if ( match ) { + + soFar = soFar.slice( match[ 0 ].length ) || soFar; + } + groups.push( ( tokens = [] ) ); + } + + matched = false; + + if ( ( match = rleadingCombinator.exec( soFar ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + + type: match[ 0 ].replace( rtrimCSS, " " ) + } ); + soFar = soFar.slice( matched.length ); + } + + for ( type in Expr.filter ) { + if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || + ( match = preFilters[ type ]( match ) ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type: type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + if ( parseOnly ) { + return soFar.length; + } + + return soFar ? + find.error( selector ) : + + tokenCache( selector, groups ).slice( 0 ); +} + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[ i ].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + + function( elem, context, xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + function( elem, context, xml ) { + var oldCache, outerCache, + newCache = [ dirruns, doneName ]; + + if ( xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); + + if ( skip && nodeName( elem, skip ) ) { + elem = elem[ dir ] || elem; + } else if ( ( oldCache = outerCache[ key ] ) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + return ( newCache[ 2 ] = oldCache[ 2 ] ); + } else { + + outerCache[ key ] = newCache; + + if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[ i ]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[ 0 ]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + find( selector, contexts[ i ], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( ( elem = unmatched[ i ] ) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction( function( seed, results, context, xml ) { + var temp, i, elem, matcherOut, + preMap = [], + postMap = [], + preexisting = results.length, + + elems = seed || + multipleContexts( selector || "*", + context.nodeType ? [ context ] : context, [] ), + + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems; + + if ( matcher ) { + + matcherOut = postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + [] : + + results; + + matcher( matcherIn, matcherOut, context, xml ); + } else { + matcherOut = matcherIn; + } + + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + i = temp.length; + while ( i-- ) { + if ( ( elem = temp[ i ] ) ) { + matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) ) { + + temp.push( ( matcherIn[ i ] = elem ) ); + } + } + postFinder( null, ( matcherOut = [] ), temp, xml ); + } + + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) && + ( temp = postFinder ? indexOf.call( seed, elem ) : preMap[ i ] ) > -1 ) { + + seed[ temp ] = !( results[ temp ] = elem ); + } + } + } + + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + } ); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[ 0 ].type ], + implicitRelative = leadingRelative || Expr.relative[ " " ], + i = leadingRelative ? 1 : 0, + + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf.call( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + + var ret = ( !leadingRelative && ( xml || context != outermostContext ) ) || ( + ( checkContext = context ).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { + matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; + } else { + matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); + + if ( matcher[ expando ] ) { + + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[ j ].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + + tokens.slice( 0, i - 1 ) + .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) + ).replace( rtrimCSS, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + + elems = seed || byElement && Expr.find.TAG( "*", outermost ), + + dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), + len = elems.length; + + if ( outermost ) { + + outermostContext = context == document || context || outermost; + } + + for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + + if ( !context && elem.ownerDocument != document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( ( matcher = elementMatchers[ j++ ] ) ) { + if ( matcher( elem, context || document, xml ) ) { + push.call( results, elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + if ( bySet ) { + + if ( ( elem = !matcher && elem ) ) { + matchedCount--; + } + + if ( seed ) { + unmatched.push( elem ); + } + } + } + + matchedCount += i; + + if ( bySet && i !== matchedCount ) { + j = 0; + while ( ( matcher = setMatchers[ j++ ] ) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !( unmatched[ i ] || setMatched[ i ] ) ) { + setMatched[ i ] = pop.call( results ); + } + } + } + + setMatched = condense( setMatched ); + } + + push.apply( results, setMatched ); + + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + jQuery.uniqueSort( results ); + } + } + + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +function compile( selector, match ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[ i ] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + cached = compilerCache( selector, + matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + + cached.selector = selector; + } + return cached; +} + +function select( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( ( selector = compiled.selector || selector ) ); + + results = results || []; + + if ( match.length === 1 ) { + + tokens = match[ 0 ] = match[ 0 ].slice( 0 ); + if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { + + context = ( Expr.find.ID( + token.matches[ 0 ].replace( runescape, funescape ), + context + ) || [] )[ 0 ]; + if ( !context ) { + return results; + + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + i = matchExpr.needsContext.test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[ i ]; + + if ( Expr.relative[ ( type = token.type ) ] ) { + break; + } + if ( ( find = Expr.find[ type ] ) ) { + + if ( ( seed = find( + token.matches[ 0 ].replace( runescape, funescape ), + rsibling.test( tokens[ 0 ].type ) && + testContext( context.parentNode ) || context + ) ) ) { + + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +} + +support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; + +setDocument(); + +support.sortDetached = assert( function( el ) { + + return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; +} ); + +jQuery.find = find; + +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.unique = jQuery.uniqueSort; + +find.compile = compile; +find.select = select; +find.setDocument = setDocument; +find.tokenize = tokenize; + +find.escape = jQuery.escapeSelector; +find.getText = jQuery.text; +find.isXML = jQuery.isXMLDoc; +find.selectors = jQuery.expr; +find.support = jQuery.support; +find.uniqueSort = jQuery.uniqueSort; + +} )(); + +var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + +var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + +var rneedsContext = jQuery.expr.match.needsContext; + +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + +function winnow( elements, qualifier, not ) { + if ( isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + return jQuery.filter( qualifier, elements, not ); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + +var rootjQuery, + + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + if ( !selector ) { + return this; + } + + root = root || rootjQuery; + + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + if ( match && ( match[ 1 ] || !context ) ) { + + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + if ( isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + } else { + return this.constructor( context ).find( selector ); + } + + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + } else if ( isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +init.prototype = jQuery.fn; + +rootjQuery = jQuery( document ); + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + index: function( elem ) { + + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + return indexOf.call( this, + + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, _i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, _i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, _i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + if ( elem.contentDocument != null && + + getProto( elem.contentDocument ) ) { + + return elem.contentDocument; + } + + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + +function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +jQuery.Callbacks = function( options ) { + + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var + firing, + + memory, + + fired, + + locked, + + list = [], + + queue = [], + + firingIndex = -1, + + fire = function() { + + locked = locked || options.once; + + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + firingIndex = list.length; + memory = false; + } + } + } + + if ( !options.memory ) { + memory = false; + } + + firing = false; + + if ( locked ) { + + if ( memory ) { + list = []; + + } else { + list = ""; + } + } + }, + + self = { + + add: function() { + if ( list ) { + + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && toType( arg ) !== "string" ) { + + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + fired: function() { + return !!fired; + } + }; + + return self; +}; + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject, noValue ) { + var method; + + try { + + if ( value && isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + } else if ( value && isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + } else { + + resolve.apply( undefined, [ value ].slice( noValue ) ); + } + + } catch ( value ) { + + reject.apply( undefined, [ value ] ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + pipe: function( ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( _i, tuple ) { + + var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + then = returned && + + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + if ( isFunction( then ) ) { + + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + } else { + + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + } else { + + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + ( special || deferred.resolveWith )( that, args ); + } + }, + + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.error ); + } + + if ( depth + 1 >= maxDepth ) { + + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + if ( depth ) { + process(); + } else { + + if ( jQuery.Deferred.getErrorHook ) { + process.error = jQuery.Deferred.getErrorHook(); + + } else if ( jQuery.Deferred.getStackHook ) { + process.error = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + promise[ tuple[ 1 ] ] = list.add; + + if ( stateString ) { + list.add( + function() { + + state = stateString; + }, + + tuples[ 3 - i ][ 2 ].disable, + + tuples[ 3 - i ][ 3 ].disable, + + tuples[ 0 ][ 2 ].lock, + + tuples[ 0 ][ 3 ].lock + ); + } + + list.add( tuple[ 3 ].fire ); + + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + promise.promise( deferred ); + + if ( func ) { + func.call( deferred, deferred ); + } + + return deferred; + }, + + when: function( singleValue ) { + var + + remaining = arguments.length, + + i = remaining, + + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + primary = jQuery.Deferred(), + + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + primary.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + if ( remaining <= 1 ) { + adoptValue( singleValue, primary.done( updateFunc( i ) ).resolve, primary.reject, + !remaining ); + + if ( primary.state() === "pending" || + isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return primary.then(); + } + } + + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), primary.reject ); + } + + return primary.promise(); + } +} ); + +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +jQuery.Deferred.exceptionHook = function( error, asyncError ) { + + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, + error.stack, asyncError ); + } +}; + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + +var readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + isReady: false, + + readyWait: 1, + + ready: function( wait ) { + + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + jQuery.isReady = true; + + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + window.setTimeout( jQuery.ready ); + +} else { + + document.addEventListener( "DOMContentLoaded", completed ); + + window.addEventListener( "load", completed ); +} + +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + if ( toType( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + } else if ( value !== undefined ) { + chainable = true; + + if ( !isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + if ( raw ) { + fn.call( elems, value ); + fn = null; + + } else { + bulk = fn; + fn = function( elem, _key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; + +var rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g; + +function fcamelCase( _all, letter ) { + return letter.toUpperCase(); +} + +function camelCase( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); +} +var acceptData = function( owner ) { + + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + var value = owner[ this.expando ]; + + if ( !value ) { + value = {}; + + if ( acceptData( owner ) ) { + + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + if ( typeof data === "string" ) { + cache[ camelCase( data ) ] = value; + + } else { + + for ( prop in data ) { + cache[ camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + this.set( owner, key, value ); + + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + if ( Array.isArray( key ) ) { + + key = key.map( camelCase ); + } else { + key = camelCase( key ); + + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +var dataPriv = new Data(); + +var dataUser = new Data(); + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + var name; + + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + if ( elem && value === undefined ) { + + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + return; + } + + this.each( function() { + + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + if ( data ) { + if ( !queue || Array.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var documentElement = document.documentElement; + + var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } +var isHiddenWithinTree = function( elem, el ) { + + elem = el || elem; + + return elem.style.display === "none" || + elem.style.display === "" && + + isAttached( elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, scale, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + initial = initial / 2; + + unit = unit || initialInUnit[ 3 ]; + + initialInUnit = +initial || 1; + + while ( maxIterations-- ) { + + jQuery.style( elem, prop, initialInUnit + unit ); + if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; + + } + + initialInUnit = initialInUnit * 2; + jQuery.style( elem, prop, initialInUnit + unit ); + + valueParts = valueParts || []; + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + dataPriv.set( elem, "display", display ); + } + } + } + + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +var rcheckableType = ( /^(?:checkbox|radio)$/i ); + +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); + +var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + + div.innerHTML = ""; + support.option = !!div.lastChild; +} )(); + +var wrapMap = { + + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] +}; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +if ( !support.option ) { + wrapMap.optgroup = wrapMap.option = [ 1, "" ]; +} + +function getAll( context, tag ) { + + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, attached, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + if ( toType( elem ) === "object" ) { + + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + jQuery.merge( nodes, tmp.childNodes ); + + tmp = fragment.firstChild; + + tmp.textContent = ""; + } + } + } + + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + attached = isAttached( elem ); + + tmp = getAll( fragment.appendChild( elem ), "script" ); + + if ( attached ) { + setGlobalEval( tmp ); + } + + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + +var rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + if ( typeof types === "object" ) { + + if ( typeof selector !== "string" ) { + + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + fn = data; + data = undefined; + } else { + + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + if ( !acceptData( elem ) ) { + return; + } + + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + if ( !( events = elemData.events ) ) { + events = elemData.events = Object.create( null ); + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + if ( !type ) { + continue; + } + + special = jQuery.event.special[ type ] || {}; + + type = ( selector ? special.delegateType : special.bindType ) || type; + + special = jQuery.event.special[ type ] || {}; + + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + jQuery.event.global[ type ] = true; + } + + }, + + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + + event = jQuery.event.fix( nativeEvent ), + + handlers = ( + dataPriv.get( this, "events" ) || Object.create( null ) + )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + if ( delegateCount && + + cur.nodeType && + + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + noBubble: true + }, + click: { + + setup: function( data ) { + + var el = this || data; + + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click", true ); + } + + return false; + }, + trigger: function( data ) { + + var el = this || data; + + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); + } + + return true; + }, + + _default: function( event ) { + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +function leverageNative( el, type, isSetup ) { + + if ( !isSetup ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + if ( !saved ) { + + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + this[ type ](); + result = dataPriv.get( this, type ); + dataPriv.set( this, type, false ); + + if ( saved !== result ) { + + event.stopImmediatePropagation(); + event.preventDefault(); + + return result; + } + + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + } else if ( saved ) { + + dataPriv.set( this, type, jQuery.event.trigger( + saved[ 0 ], + saved.slice( 1 ), + this + ) ); + + event.stopPropagation(); + event.isImmediatePropagationStopped = returnTrue; + } + } + } ); +} + +jQuery.removeEvent = function( elem, type, handle ) { + + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + src.returnValue === false ? + returnTrue : + returnFalse; + + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + } else { + this.type = src; + } + + if ( props ) { + jQuery.extend( this, props ); + } + + this.timeStamp = src && src.timeStamp || Date.now(); + + this[ jQuery.expando ] = true; +}; + +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + code: true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + which: true +}, jQuery.event.addProp ); + +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + + function focusMappedHandler( nativeEvent ) { + if ( document.documentMode ) { + + var handle = dataPriv.get( this, "handle" ), + event = jQuery.event.fix( nativeEvent ); + event.type = nativeEvent.type === "focusin" ? "focus" : "blur"; + event.isSimulated = true; + + handle( nativeEvent ); + + if ( event.target === event.currentTarget ) { + + handle( event ); + } + } else { + + jQuery.event.simulate( delegateType, nativeEvent.target, + jQuery.event.fix( nativeEvent ) ); + } + } + + jQuery.event.special[ type ] = { + + setup: function() { + + var attaches; + + leverageNative( this, type, true ); + + if ( document.documentMode ) { + + attaches = dataPriv.get( this, delegateType ); + if ( !attaches ) { + this.addEventListener( delegateType, focusMappedHandler ); + } + dataPriv.set( this, delegateType, ( attaches || 0 ) + 1 ); + } else { + + return false; + } + }, + trigger: function() { + + leverageNative( this, type ); + + return true; + }, + + teardown: function() { + var attaches; + + if ( document.documentMode ) { + attaches = dataPriv.get( this, delegateType ) - 1; + if ( !attaches ) { + this.removeEventListener( delegateType, focusMappedHandler ); + dataPriv.remove( this, delegateType ); + } else { + dataPriv.set( this, delegateType, attaches ); + } + } else { + + return false; + } + }, + + _default: function( event ) { + return dataPriv.get( event.target, type ); + }, + + delegateType: delegateType + }; + + jQuery.event.special[ delegateType ] = { + setup: function() { + + var doc = this.ownerDocument || this.document || this, + dataHolder = document.documentMode ? this : doc, + attaches = dataPriv.get( dataHolder, delegateType ); + + if ( !attaches ) { + if ( document.documentMode ) { + this.addEventListener( delegateType, focusMappedHandler ); + } else { + doc.addEventListener( type, focusMappedHandler, true ); + } + } + dataPriv.set( dataHolder, delegateType, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this.document || this, + dataHolder = document.documentMode ? this : doc, + attaches = dataPriv.get( dataHolder, delegateType ) - 1; + + if ( !attaches ) { + if ( document.documentMode ) { + this.removeEventListener( delegateType, focusMappedHandler ); + } else { + doc.removeEventListener( type, focusMappedHandler, true ); + } + dataPriv.remove( dataHolder, delegateType ); + } else { + dataPriv.set( dataHolder, delegateType, attaches ); + } + } + }; +} ); + +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + +var + + rnoInnerhtml = /\s*$/g; + +function manipulationTarget( elem, content ) { + if ( nodeName( elem, "table" ) && + nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return jQuery( elem ).children( "tbody" )[ 0 ] || elem; + } + + return elem; +} + +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.get( src ); + events = pdataOld.events; + + if ( events ) { + dataPriv.remove( dest, "handle events" ); + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + args = flat( args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + valueIsFunction = isFunction( value ); + + if ( valueIsFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( valueIsFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + if ( hasScripts ) { + + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + jQuery.map( scripts, restoreScript ); + + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { + + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + }, doc ); + } + } else { + + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && isAttached( node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html; + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = isAttached( elem ); + + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + jQuery.cleanData( getAll( elem, false ) ); + + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +var rcustomProp = /^--/; + +var getStyles = function( elem ) { + + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + +var swap = function( elem, options, callback ) { + var ret, name, + old = {}; + + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.call( elem ); + + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + +var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); + +( function() { + + function computeStyleTests() { + + if ( !div ) { + return; + } + + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + + "margin-top:1px;padding:0;border:0"; + div.style.cssText = + "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + + "margin:auto;border:1px;padding:1px;" + + "width:60%;top:1%"; + documentElement.appendChild( container ).appendChild( div ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; + + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; + + boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; + + div.style.position = "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; + + documentElement.removeChild( container ); + + div = null; + } + + function roundPixelMeasures( measure ) { + return Math.round( parseFloat( measure ) ); + } + + var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, + reliableTrDimensionsVal, reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + if ( !div.style ) { + return; + } + + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + jQuery.extend( support, { + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelBoxStyles: function() { + computeStyleTests(); + return pixelBoxStylesVal; + }, + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + }, + scrollboxSize: function() { + computeStyleTests(); + return scrollboxSizeVal; + }, + + reliableTrDimensions: function() { + var table, tr, trChild, trStyle; + if ( reliableTrDimensionsVal == null ) { + table = document.createElement( "table" ); + tr = document.createElement( "tr" ); + trChild = document.createElement( "div" ); + + table.style.cssText = "position:absolute;left:-11111px;border-collapse:separate"; + tr.style.cssText = "box-sizing:content-box;border:1px solid"; + + tr.style.height = "1px"; + trChild.style.height = "9px"; + + trChild.style.display = "block"; + + documentElement + .appendChild( table ) + .appendChild( tr ) + .appendChild( trChild ); + + trStyle = window.getComputedStyle( tr ); + reliableTrDimensionsVal = ( parseInt( trStyle.height, 10 ) + + parseInt( trStyle.borderTopWidth, 10 ) + + parseInt( trStyle.borderBottomWidth, 10 ) ) === tr.offsetHeight; + + documentElement.removeChild( table ); + } + return reliableTrDimensionsVal; + } + } ); +} )(); + +function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + isCustomProp = rcustomProp.test( name ), + + style = elem.style; + + computed = computed || getStyles( elem ); + + if ( computed ) { + + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( isCustomProp && ret ) { + + ret = ret.replace( rtrimCSS, "$1" ) || undefined; + } + + if ( ret === "" && !isAttached( elem ) ) { + ret = jQuery.style( elem, name ); + } + + if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { + + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + ret + "" : + ret; +} + +function addGetHookIf( conditionFn, hookFn ) { + + return { + get: function() { + if ( conditionFn() ) { + + delete this.get; + return; + } + + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + +var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; + +function vendorPropName( name ) { + + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +function finalPropName( name ) { + var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; + } + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; +} + +var + + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + +function setPositiveNumber( _elem, value, subtract ) { + + var matches = rcssNum.exec( value ); + return matches ? + + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0, + marginDelta = 0; + + if ( box === ( isBorderBox ? "border" : "content" ) ) { + return 0; + } + + for ( ; i < 4; i += 2 ) { + + if ( box === "margin" ) { + marginDelta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); + } + + if ( !isBorderBox ) { + + delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + if ( box !== "padding" ) { + delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + + } else { + extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + + } else { + + if ( box === "content" ) { + delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + if ( box !== "margin" ) { + delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + if ( !isBorderBox && computedVal >= 0 ) { + + delta += Math.max( 0, Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + computedVal - + delta - + extra - + 0.5 + + ) ) || 0; + } + + return delta + marginDelta; +} + +function getWidthOrHeight( elem, dimension, extra ) { + + var styles = getStyles( elem ), + + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, + + val = curCSS( elem, dimension, styles ), + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); + + if ( rnumnonpx.test( val ) ) { + if ( !extra ) { + return val; + } + val = "auto"; + } + + if ( ( !support.boxSizingReliable() && isBorderBox || + + !support.reliableTrDimensions() && nodeName( elem, "tr" ) || + + val === "auto" || + + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + + elem.getClientRects().length ) { + + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; + } + } + + val = parseFloat( val ) || 0; + + return ( val + + boxModelAdjustment( + elem, + dimension, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles, + + val + ) + ) + "px"; +} + +jQuery.extend( { + + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + cssNumber: { + animationIterationCount: true, + aspectRatio: true, + borderImageSlice: true, + columnCount: true, + flexGrow: true, + flexShrink: true, + fontWeight: true, + gridArea: true, + gridColumn: true, + gridColumnEnd: true, + gridColumnStart: true, + gridRow: true, + gridRowEnd: true, + gridRowStart: true, + lineHeight: true, + opacity: true, + order: true, + orphans: true, + scale: true, + widows: true, + zIndex: true, + zoom: true, + + fillOpacity: true, + floodOpacity: true, + stopOpacity: true, + strokeMiterlimit: true, + strokeOpacity: true + }, + + cssProps: {}, + + style: function( elem, name, value, extra ) { + + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + var ret, type, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ), + style = elem.style; + + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + if ( value !== undefined ) { + type = typeof value; + + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + type = "number"; + } + + if ( value == null || value !== value ) { + return; + } + + if ( type === "number" && !isCustomProp ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } + } + + } else { + + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ); + + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( _i, dimension ) { + jQuery.cssHooks[ dimension ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, dimension, extra ); + } ) : + getWidthOrHeight( elem, dimension, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = getStyles( elem ), + + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; + + if ( isBorderBox && scrollboxSizeBuggy ) { + subtract -= Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + parseFloat( styles[ dimension ] ) - + boxModelAdjustment( elem, dimension, "border", false, styles ) - + 0.5 + ); + } + + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ dimension ] = value; + value = jQuery.css( elem, dimension ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( prefix !== "margin" ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( Array.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + result = jQuery.css( tween.elem, tween.prop, "" ); + + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +jQuery.fx.step = {}; + +var + fxNow, inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function schedule() { + if ( inProgress ) { + if ( document.hidden === false && window.requestAnimationFrame ) { + window.requestAnimationFrame( schedule ); + } else { + window.setTimeout( schedule, jQuery.fx.interval ); + } + + jQuery.fx.tick(); + } +} + +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = Date.now() ); +} + +function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + if ( isBox && elem.nodeType === 1 ) { + + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + propTween = false; + for ( prop in orig ) { + + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + if ( toggle ) { + dataShow.hidden = !hidden; + } + + if ( hidden ) { + showHide( [ elem ], true ); + } + + anim.done( function() { + + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + for ( index in props ) { + name = camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( Array.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + if ( percent < 1 && length ) { + return remaining; + } + + if ( !length ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + } + + deferred.resolveWith( elem, [ animation ] ); + return false; + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + result.stop.bind( result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + animation + .progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + return animation; +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !isFunction( easing ) && easing + }; + + if ( jQuery.fx.off ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + opt.old = opt.complete; + + opt.complete = function() { + if ( isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + data.finish = true; + + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = Date.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + jQuery.fx.start(); +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( inProgress ) { + return; + } + + inProgress = true; + schedule(); +}; + +jQuery.fx.stop = function() { + inProgress = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + _default: 400 +}; + +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + +( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + support.checkOn = input.value !== ""; + + support.optSelected = opt.selected; + + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + +var boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +function classesToArray( value ) { + if ( Array.isArray( value ) ) { + return value; + } + if ( typeof value === "string" ) { + return value.match( rnothtmlwhite ) || []; + } + return []; +} + +jQuery.fn.extend( { + addClass: function( value ) { + var classNames, cur, curValue, className, i, finalValue; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + classNames = classesToArray( value ); + + if ( classNames.length ) { + return this.each( function() { + curValue = getClass( this ); + cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + for ( i = 0; i < classNames.length; i++ ) { + className = classNames[ i ]; + if ( cur.indexOf( " " + className + " " ) < 0 ) { + cur += className + " "; + } + } + + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + this.setAttribute( "class", finalValue ); + } + } + } ); + } + + return this; + }, + + removeClass: function( value ) { + var classNames, cur, curValue, className, i, finalValue; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + classNames = classesToArray( value ); + + if ( classNames.length ) { + return this.each( function() { + curValue = getClass( this ); + + cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + for ( i = 0; i < classNames.length; i++ ) { + className = classNames[ i ]; + + while ( cur.indexOf( " " + className + " " ) > -1 ) { + cur = cur.replace( " " + className + " ", " " ); + } + } + + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + this.setAttribute( "class", finalValue ); + } + } + } ); + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var classNames, className, i, self, + type = typeof value, + isValidValue = type === "string" || Array.isArray( value ); + + if ( isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + if ( typeof stateVal === "boolean" && isValidValue ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + classNames = classesToArray( value ); + + return this.each( function() { + if ( isValidValue ) { + + self = jQuery( this ); + + for ( i = 0; i < classNames.length; i++ ) { + className = classNames[ i ]; + + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + dataPriv.set( this, "__className__", className ); + } + + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + +var rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, valueIsFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + return ret == null ? "" : ret; + } + + return; + } + + valueIsFunction = isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( valueIsFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( Array.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + for ( ; i < max; i++ ) { + option = options[ i ]; + + if ( ( option.selected || i === index ) && + + !option.disabled && + ( !option.parentNode.disabled || + !nodeName( option.parentNode, "optgroup" ) ) ) { + + value = jQuery( option ).val(); + + if ( one ) { + return value; + } + + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + } + + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( Array.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + +var location = window.location; + +var nonce = { guid: Date.now() }; + +var rquery = ( /\?/ ); + +jQuery.parseXML = function( data ) { + var xml, parserErrorElem; + if ( !data || typeof data !== "string" ) { + return null; + } + + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) {} + + parserErrorElem = xml && xml.getElementsByTagName( "parsererror" )[ 0 ]; + if ( !xml || parserErrorElem ) { + jQuery.error( "Invalid XML: " + ( + parserErrorElem ? + jQuery.map( parserErrorElem.childNodes, function( el ) { + return el.textContent; + } ).join( "\n" ) : + data + ) ); + } + return xml; +}; + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function( e ) { + e.stopPropagation(); + }; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = lastElement = tmp = elem = elem || document; + + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + lastElement = cur; + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + handle = ( dataPriv.get( cur, "events" ) || Object.create( null ) )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { + + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + jQuery.event.triggered = type; + + if ( event.isPropagationStopped() ) { + lastElement.addEventListener( type, stopPropagationCallback ); + } + + elem[ type ](); + + if ( event.isPropagationStopped() ) { + lastElement.removeEventListener( type, stopPropagationCallback ); + } + + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( Array.isArray( obj ) ) { + + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + add( prefix, v ); + + } else { + + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && toType( obj ) === "object" ) { + + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + add( prefix, obj ); + } +} + +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + var value = isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + if ( a == null ) { + return ""; + } + + if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ).filter( function() { + var type = this.type; + + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ).map( function( _i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( Array.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\ + + prefilters = {}, + + transports = {}, + + allTypes = "*/".concat( "*" ), + + originAnchor = document.createElement( "a" ); + +originAnchor.href = location.href; + +function addToPrefiltersOrTransports( structure ) { + + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( isFunction( func ) ) { + + while ( ( dataType = dataTypes[ i++ ] ) ) { + + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + finalDataType = finalDataType || firstDataType; + } + + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + dataTypes = s.dataTypes.slice(); + + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + if ( current === "*" ) { + + current = prev; + + } else if ( prev !== "*" && prev !== current ) { + + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + if ( !conv ) { + for ( conv2 in converters ) { + + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + if ( conv === true ) { + conv = converters[ conv2 ]; + + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + if ( conv !== true ) { + + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + active: 0, + + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + converters: { + + "* text": String, + + "text html": true, + + "text json": JSON.parse, + + "text xml": jQuery.parseXML + }, + + flatOptions: { + url: true, + context: true + } + }, + + ajaxSetup: function( target, settings ) { + return settings ? + + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + ajax: function( url, options ) { + + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + options = options || {}; + + var transport, + + cacheURL, + + responseHeadersString, + responseHeaders, + + timeoutTimer, + + urlAnchor, + + completed, + + fireGlobals, + + i, + + uncached, + + s = jQuery.ajaxSetup( {}, options ), + + callbackContext = s.context || s, + + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + statusCode = s.statusCode || {}, + + requestHeaders = {}, + requestHeadersNames = {}, + + strAbort = "canceled", + + jqXHR = { + readyState: 0, + + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); + } + } + match = responseHeaders[ key.toLowerCase() + " " ]; + } + return match == null ? null : match.join( ", " ); + }, + + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + jqXHR.always( map[ jqXHR.status ] ); + } else { + + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + deferred.promise( jqXHR ); + + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + s.type = options.method || options.type || s.method || s.type; + + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + try { + urlAnchor.href = s.url; + + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + s.crossDomain = true; + } + } + + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + if ( completed ) { + return jqXHR; + } + + fireGlobals = jQuery.event && s.global; + + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + s.type = s.type.toUpperCase(); + + s.hasContent = !rnoContent.test( s.type ); + + cacheURL = s.url.replace( rhash, "" ); + + if ( !s.hasContent ) { + + uncached = s.url.slice( cacheURL.length ); + + if ( s.data && ( s.processData || typeof s.data === "string" ) ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + delete s.data; + } + + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + + uncached; + } + + s.url = cacheURL + uncached; + + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + return jqXHR.abort(); + } + + strAbort = "abort"; + + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + if ( completed ) { + return jqXHR; + } + + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + if ( completed ) { + throw e; + } + + done( -1, e ); + } + } + + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + if ( completed ) { + return; + } + + completed = true; + + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + transport = undefined; + + responseHeadersString = headers || ""; + + jqXHR.readyState = status > 0 ? 4 : 0; + + isSuccess = status >= 200 && status < 300 || status === 304; + + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + if ( !isSuccess && + jQuery.inArray( "script", s.dataTypes ) > -1 && + jQuery.inArray( "json", s.dataTypes ) < 0 ) { + s.converters[ "text script" ] = function() {}; + } + + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + if ( isSuccess ) { + + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + } else if ( status === 304 ) { + statusText = "notmodified"; + + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( _i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + if ( isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + +jQuery.ajaxPrefilter( function( s ) { + var i; + for ( i in s.headers ) { + if ( i.toLowerCase() === "content-type" ) { + s.contentType = s.headers[ i ] || ""; + } + } +} ); + +jQuery._evalUrl = function( url, options, doc ) { + return jQuery.ajax( { + url: url, + + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options, doc ); + } + } ); +}; + +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var htmlIsFunction = isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +var xhrSuccessStatus = { + + 0: 200, + + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + +callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.ontimeout = + xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; +}; + +xhr.onload = callback(); +errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); + +if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; +} else { + xhr.onreadystatechange = function() { + + if ( xhr.readyState === 4 ) { + + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; +} + +callback = callback( "abort" ); + +try { + + xhr.send( options.hasContent && options.data || null ); +} catch ( e ) { + + if ( callback ) { + throw e; + } +} +}, + +abort: function() { +if ( callback ) { + callback(); +} +} +}; +} +} ); + +jQuery.ajaxPrefilter( function( s ) { +if ( s.crossDomain ) { +s.contents.script = false; +} +} ); + +jQuery.ajaxSetup( { +accepts: { +script: "text/javascript, application/javascript, " + +"application/ecmascript, application/x-ecmascript" +}, +contents: { +script: /\b(?:java|ecma)script\b/ +}, +converters: { +"text script": function( text ) { +jQuery.globalEval( text ); +return text; +} +} +} ); + +jQuery.ajaxPrefilter( "script", function( s ) { +if ( s.cache === undefined ) { +s.cache = false; +} +if ( s.crossDomain ) { +s.type = "GET"; +} +} ); + +jQuery.ajaxTransport( "script", function( s ) { + +if ( s.crossDomain || s.scriptAttrs ) { +var script, callback; +return { +send: function( _, complete ) { +script = jQuery( " + + + diff --git a/firmware_update/latest/data/www/edit.html b/firmware_update/latest/data/www/edit.html new file mode 100644 index 0000000..9f29e48 --- /dev/null +++ b/firmware_update/latest/data/www/edit.html @@ -0,0 +1,191 @@ + + + + Edit file + + + + + + + +

Edit file

+
+
+
+
+ +
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/firmware_update/latest/data/www/edit_old.html b/firmware_update/latest/data/www/edit_old.html new file mode 100644 index 0000000..e76f413 --- /dev/null +++ b/firmware_update/latest/data/www/edit_old.html @@ -0,0 +1,151 @@ + + + + Edit file + + + + + + +

Edit file

+
+ Editing file: {{SAVE_PATH_INPUT}} +
+
+
+ +
+
+ {{SAVE_PATH_INPUT}} + + + + +
+
+
+ + + + + + \ No newline at end of file diff --git a/firmware_update/latest/data/www/failed.html b/firmware_update/latest/data/www/failed.html new file mode 100644 index 0000000..cfcf05e --- /dev/null +++ b/firmware_update/latest/data/www/failed.html @@ -0,0 +1,33 @@ + + + + Update Failed + + + + + + + +
+

The update has failed.

+
+ +
+ + + + \ No newline at end of file diff --git a/firmware_update/latest/data/www/files.html b/firmware_update/latest/data/www/files.html new file mode 100644 index 0000000..54f37df --- /dev/null +++ b/firmware_update/latest/data/www/files.html @@ -0,0 +1,276 @@ + + + + File Manager + + + + + + + + + +
+

File Manager

+ +
+ File list +
+

  Total: {{FS_TOTAL_BYTES}}, Used: {{FS_USED_BYTES}}, Available: {{FS_FREE_BYTES}}

+
+ + {{LISTED_FILES}} +
Listing: Size:
+
+
+ +
+ +
+ File upload +
+
+ + + + + + + + + + + +
+
+    + +
+
+
+ +
+
+
+ +
+ +
+ Edit file +
+
+ + + +
+
+
+
+ +
+ +
+ Delete file +
+
+ + + +
+
+
+
+ +
+ + + + +
+ + + \ No newline at end of file diff --git a/firmware_update/latest/data/www/home.html b/firmware_update/latest/data/www/home.html new file mode 100644 index 0000000..84f6450 --- /dev/null +++ b/firmware_update/latest/data/www/home.html @@ -0,0 +1,303 @@ + + + + System Summary + + + + + + + +

System Summary

+
+ +
+ Booth Overview +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+
+ +
+ System Information +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ +
+
+ + +
+
+
+ + +
+ Network / WiFi +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+
+ +
+ Bluetoth LE +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+
+ +
+ Luma Stiks +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+
+ +
+ + + + \ No newline at end of file diff --git a/firmware_update/latest/data/www/index.html b/firmware_update/latest/data/www/index.html new file mode 100644 index 0000000..6c6f81a --- /dev/null +++ b/firmware_update/latest/data/www/index.html @@ -0,0 +1,29 @@ + + + + + ESP32 AP + + + +

Welcome to ESP32 AP

+

This is a simple web server running on ESP32 in Access Point mode.

+
+ + + \ No newline at end of file diff --git a/firmware_update/latest/data/www/lights.html b/firmware_update/latest/data/www/lights.html new file mode 100644 index 0000000..01a004b --- /dev/null +++ b/firmware_update/latest/data/www/lights.html @@ -0,0 +1,527 @@ + + + + + + + + + + + + + +
+

Lights Configuration

+
+ Countdown ( Constant Light ) +
+
+ + +
+
+ + +
+
+
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+ + +
+
+ +      + +
+
+
+
+ +
+
+ Saved Animation Profiles +
+
+ + +
+
+ + + +
+
+
+
+ +
+ + + + diff --git a/firmware_update/latest/data/www/navbar.html b/firmware_update/latest/data/www/navbar.html new file mode 100644 index 0000000..406a174 --- /dev/null +++ b/firmware_update/latest/data/www/navbar.html @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/firmware_update/latest/data/www/ok.html b/firmware_update/latest/data/www/ok.html new file mode 100644 index 0000000..6df4dda --- /dev/null +++ b/firmware_update/latest/data/www/ok.html @@ -0,0 +1,34 @@ + + + + Update Success + + + + + + + +
+

The update was successful.

+
+ +
+ + + + \ No newline at end of file diff --git a/firmware_update/latest/data/www/setup.html b/firmware_update/latest/data/www/setup.html new file mode 100644 index 0000000..18b6fb9 --- /dev/null +++ b/firmware_update/latest/data/www/setup.html @@ -0,0 +1,385 @@ + + + + Booth Configuration Tools + + + + + + + + +

System Setup

+
+ +
+ LED Strip #1 Settings +
+
+
+ +
+
+ +
+
+ +
+
+
+ +
+ LED Strip #1 Settings +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ + +
+ Test Tool (Single LED) + +
+
+ +
+
+ +
+ +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ + +
+ Luma Stiks Found + +
+
+
+ + +
+
+
+ + +
+
+ +
+
+
+ + +
+
+
+ + +
+
+ +
+
+
+ + +
+
+
+ + +
+
+ +
+
+
+ + +
+
+
+ + +
+
+ +
+
+ +
+
+ +
+ +
+
+ +
+ + + + \ No newline at end of file diff --git a/firmware_update/latest/data/www/upgrade.html b/firmware_update/latest/data/www/upgrade.html new file mode 100644 index 0000000..f7da5e9 --- /dev/null +++ b/firmware_update/latest/data/www/upgrade.html @@ -0,0 +1,234 @@ + + + + + + Firmware Upgrade + + + + +
+

Firmware Upgrade

+ +
+ + Disconnected +
+ +
+

Current Version: -

+

Latest Version: -

+
+ +
+ + +
+ +
+
+ + + + \ No newline at end of file diff --git a/firmware_update/latest/data/www/wifi.html b/firmware_update/latest/data/www/wifi.html new file mode 100644 index 0000000..06e3df9 --- /dev/null +++ b/firmware_update/latest/data/www/wifi.html @@ -0,0 +1,159 @@ + + + + + + Wifi Access + + + + + +

Wifi Access

+
+
+ + +
+ + + + + +
+ + + + diff --git a/firmware_update/latest/firmware.bin b/firmware_update/latest/firmware.bin new file mode 100644 index 0000000000000000000000000000000000000000..1422fc833b7ff62dbd363b8a0a60fc8131b2c61d GIT binary patch literal 1409568 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

nGMv%}!6xl_va&O~bkLA}nVA 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 z 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

)7d0~Z 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%0DZVp8}khA0WDQP+pvesxWnT{Sd?ndiL8ZP^t1< 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

1^p^OiJLvtfrKJgS+;>)Y8-rGR5@- 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⁣ctEJj%YlB+dFD=WUFB`FVoiQ#V&{@U=HhTl^B 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%0AKy=%Q$E059! zT4ha4ySr5ntc}$u1&ylFBV#on_~VUPMvsQ7VewbiSQ{_h#N?RC?NTsK=fhM{yL72B z>}1O+>}*fD${75nrrv3^J}i_g*m9+(Jf<`ayN#@v&0g{uEBQ=-rW!lq1GItyY}sNI zXgatVf0d2pBwJ@NwiK`i9mWku9b2Vq-HnYH));e)*_zfK>+XOiF2KcEd&0Y| z+`7*%&utNjaM3kL;OBJ6W|z9vUSO34@&i{``4(IGiFZdjmW+) zCsn8qJU1TNRlTS%b*#eF=!+E^V^_g{Jml8(j~a6e96$X0Si4>yH5!%F4_|~^dv*(p zHDq19M#zQM%!4nFf;zEkj{jq{mo?%xn`ru4HG(C{n7cdJb#)($G!9BvS{eDy%`F-Zx142ub&7+UK@BgQpE%2^I!9ScqT7-JmZbL0haqq!cZ74ywl*@v)d~#pP0JlAZM$pq$La4j-88|G z-aG8^rqWR;i04J;cue2_Cz~WY1JlR|_~8aWiIxjSLoT!q2HqQhp7VO*_nP2|z!$c` zMiKZ5oK_!@gE_&thE^xHP0gAb&lIkn8p|Z_se?W^7+|GfU|CJquC9<<=?7~HjMgB_ z9)`M1YXnN-;go~-AH*!wLa$Yj#gFg-SK%v##ViiYT>8EXydfTemHJv{;aLN zM~xjRf}YH}v~o^s4knHUgS+wk2agVJT2tG5luY$lCdV7`?Yhww%Y(b(VEl{c9h!hD ziMgM+H!4jMUQzodkoh4zl+U*M63M+|bvzuV08OmYyQ`p%K>8{15t))9KDCX@vco>1 z%pS)i?(xuEkOGFeiWpeZrUO@fpO3ICP}BlrCh2K!zAV7%9Pl$+4xA5lHkJD$P|J0x z{1HSQB!85?jhhN1XqaE!w5TT%4h(!n`SrFi={zvmk}g}uLGIN=3`%)2Cw0F_Exodx z9Dxr!JV)y0Bi|;t5iTO#FH$=;#M}A_)4_#W;$XOs+j4bbqz4uT1-@w{KF7pMzlJW# zow&5XyA_}f&Vf#f!|y))zWk@S8M#x&a{=6sY{c5|QQlwCCf(m2&=Mh04k&lqXT$et zUGlcY&;ewY_7NQ}nfb*51kiyJKo&S3&X0#1K<(GbpaCcZRftcLW_|(ILt#aOITcNh2UW%g~e3=`|vSyhIoHk>WvJj?37FU(0fF!yFA8FqHsY0%5_rWJUibJ z;h(HSv2$=a-C)V|{*w07N40`A=ZF(6JO9S$VQ2S0#rA)S2(Tw-#-a8nB zmc+v!?H6*;k1gFvWx!teGv64NOmS0=^VdzfcFb?^Bma9ZrAzddN zHvVy~?ufP{wXY(M(FkwkF{y$pPjJ-;t`=sR;7Vqe3ZVD42(DD-4d%ZDR~oY>;>wM< z?%k~3oV?k(xp1>fa5ZnPo_Ogxa9mQiB=db~pD*@m0AyeG(a&2W+5bHA9e>0a3C8_# zZDOBV)1P~~`plmXtNTAck$t@X3;uB4A<)^x;)l^!;719fils#h=xp# z1zpZ)J7~hw4JAamu>L6T9hf-?wsnO0)t^J?G(xm}UYbG~8*7aIxW?;_#>N`q@NL0; zExr5W_wBW=t!fzI4;#lcqnfPARSm^cpN9Zg z-!ju*EF5=$*;B7RAT@$wCi~73%-;0!*R8ZwQ+4IKwW;d-H8wl3ipO7L6^p+|tNMoc z?0+&TjZt!)@>2@JujW{tvB3!Eil(lwYZgzbrfafVda)ABINhrGA`^TXJV^|7?1@TF z!9*tL;0ktvHY}^ye;1vWbySYC*2uOL#iNYbO?{#LFeckpDx-SuH9|LyHS5AOyN$MJ z=~6RgI{S5ZTL?$sivfC-i8B3L#C;Y|uL5_by7Gea#l)$8)8M`rtv|8SHLE1OvdHzdheOw&`*v;rZ!n~5 z_@<{oB82YQm6mCAT>Vg4_0kZ)BMb_yFBa0)30A%353;qa;Fb~N>i-7pfxYZ)SchPD z{c0EEYbTRvgog}m{~);CQMI>$hp@}wk#?nhfzGyY)bV&Qudu5q6%ege?)&dPhohiz zb6mO~o<#7dN#44TNiNFa7pTH1y^qIp&y9Cf#5I-}8C`h_(IWaQarWI4=?M5%laFEL zd_#^lK-boMN8?GS`*TNm+`9Kg6lp1mW7OsO+;flf#c_ObJXf5^7bi`;RC4Ob&%2^J zJnbVmCQWc%TlYRHX)hY>3kH!|B&A@dzqJ>s8j`Y2`P#kR;gY)-e>u>U7lPq{T-aLB z85Zi_61L7@G@ao<{n0@EKZW{Zf%@Zt`nQFxC4sH8JHsuljY9p2K>bPZ7tPf0?b_i5 z?%-i0zz~ME&K$nc;SY8oP)#j&5nL>qe;Xbpsv#Mm1H}{BMg0NSJ#6tLGJ@tFNz6bw zK;vS}3nSrTTin+$4oX{72;Y&?e6GX+RWFNb;qWZYv0A%FTV!pG_;evj39v~6ZbFak zc16|4#AkWq-D7a%3XVy^N*Mz!ggdaMk=JBbg=J~{SiNhYf4C3f&H*)uByC`~4I_V< z?4K$rK$|1Fml=7fmTK+9Q1stZ!GbAY{}v&G6vJN=&YR(b}dLNM(@K6?vVggF{Z*Z)U4ZS+U)&8a_54;X%&4ki9an*NXUmm@!_ zFQonPd++%FNYBe7`A;vr=^Ft&KOlzWJo!#MDupMbx^mq&#AAW@sf7uC-Ii9rX5BThaV5XS-^iaTWvH+&ukrCMTd?#R zuLipw&nt2UJFW)T^UyoVo6a|`dy4p1f#2tUx=* z1AzqjJCs3}8mT?Siam>_O}u2=EoLPqHh*mUTFlC1R(zdR5QBpjN@38bbI%z1OFV86 zgsV^)l;dgc%0qF`x!+Jap0aI6!Gz|MwgD8>k%B4}DR(wnF4cDKR=MP@XTpUOzv{VvwG5< zjC+QQgxlbQ4>RoQNwPh1z|0A-9Kmor?93X3E z!mV%Ry2!U?e_EKBZY5Q7>3+t&deU^Jq4w`t6EDs7e_#I$wWKG)2Wx&%|NW%?&jF6d z>W`J`|18Q=Mqd~<>lZmgzm8$-SwCS>o)7Az(gr?gVCdD8V&NADZih+RLRzUT<{7gC zlZgI;Lqx94(|lI)xVFNf`cyMw1(A`Z%vVJabodwgmT0NqA0YEUL0&M&NVW)-{*NO+ z_v2c2;Kx7yJ@p9rRkqbMx^0SO&wLehI$aaD9&ZJUnBv$8#rfM$W+f z$KXdkj%yC=ja~SihYLJg-@#@qqX6g@X&HgD)j+Q{bjS?LWYqeM^E40CJFt)%iGgPv z(lhhO#|L1ANsf{$qMPQSnzQF&`jCfO2TQEGgVoBQlB#?g_BtY%Cyp1^M?C4wU;5`U zdzn)Y-m}upDJv0_;G)|zk$KWV(kd!HgLz?UC_h~}HI$luH~AAHe@^{BjJ*$B)7Abz ze)irU#`Z=xAVtC42Ix?%F)gO8>o(BlpKeHLXx0T4M6x2IvK!^z({K>gn3iDnXwR|q|9PF{%3+B^ z9#bxB!qpw2m`p8lQE>8BokU^_8#xd=RSK)YV?L!9$uC71)qthSH)3j?#?vV ze21aHT@id;!!s|cW~|pQMnRR}>U{AMR6s3QVDM{tuoFVvM-vHy~WRXi$`juA9^PQ zy`XZX&x-;dlW~KDm8N4rcL&W$7oSwbQx@Wl^I{McxIiRW-%om#pYbXWOID14M))_e z59gbf8!?`K2_c3U5oo^ACS#>p)>5O4tV>xJcZ!i7dY-=_rSmX9#@4dzpIeRIWyq!H zX?{Qqwb6!CrVO^4k{0t#gN-^wGgPSW7=wwH((**lSzvty!M{oapm$Gh?yUbd%)w+FC#g6hF(G^fPl@u3m`fPcKQ4unFENcE|4^wUQSp3je z$KoWgaVj-`_FM=X&HX~T37natCqRBg9as#Ur1dllBRY4HbXKE`jnR}YpJkWf4k&g+ zN{=QllIj?_Oigr5Xwg7Y_uR2}b(P!cb*xJ~y^eL?F5DE!i~o14uv4TH%NoSe=#Ml_ z&%GySZOsbuV+BU^Yj!DWTy8Yg*ag8X!8Ll5u7RI*nGR#f5v1P;J`R9Z0`CU+ndN5= zUBB6;#P4s_eYo$^RsQ5*9<9aJJ87!)e#$=s={gzdXBm=m=JV@WRZDvFy29ISdIoJ$ z0v<6L*K%4aG%xsRD)%CuL-cms9v(8Ix3a-_;=dH)(KfyASKyK>DgA-n?yZ>5hOeno zvh?mO8@{1VGzQCR1(p9Q8b`w?_)=|wbSGCv_FRAVH=|)D`Ma2l36U0S*vBPdDn6mm_9K@U_E1k z(I{5uM9fV3whu>_jk+k0SfMl}Z%lPI_8E0CC>|IF8C+f?)>5V!ZDA;M-_bQidWm!Z z3`eNpemrZcN`6F%ORuA0xA`~NVWPX7?z|I{iT%JY(AI&MtX){FEI`wq5sp@ExA=jX zqj01et2Qfe!PI|XIQLh&LvU})*i!-_v8L~ zILzn5%+o(F+^JLJP$T0VP1gsTjE>RE*QMiHYoNQK)onv7=J+%@Gy;7m-{?EEysu%4 z!;>3zDmTWHJH(Sav^h7r!LHQVN9gR!b#~5^7j-Hx#*;V1lQ*Qa5#kZtC*d zsT}r01AKjkClYT2x+VHOKBR$8vuqpBJ+QUO@01_$C#W5(CFpqseYoRoNzVL1+UNRk zR#KN2<;;t5<_*#14K1SkkJ`WWak+hN=PGBvi!*O1?Sun)gp)khRV6xaL>0umxFi8_ z{>Ub=Qa5evEN9>hdn?9##{%O%jDcbjz|YF@v&-BN&fu*I9duTLqk%EH#*6nWHRk2- zDYc!vW8HH1a!onoC^Znd#f^=_1eHgj{5(7wB1komPlx&lNrY2FS)-;{0 zH(KcjawGeeW<2odXh)HEY19Lc4rCWYKSob5PzEz@Fzj_)O}ag$2Y;JXs~pbadtM!bY^%9WT=hB#hYu6y`Stc^*Rq};n(jQrNiHlxxcLN}~q9G$~)bdP>~QqB>k=w&wZsK-L6EO&&XblchX zmX^+u?_7XQE`u3<=HhLJocg@qV0%V8Ma>RccZAj3_V!wGS1nSNtlOKIo_d!t_3nN55?9hy zJ-jz~6S)=Cw_aNNWg_@)WOjUB-?Xiv=?Hc|{d*I+Lh3p>mXJ3T{x1`kO4%VBMHr^k z4c@gsu?g2oxdQ5eh72he)eS~i=Yd27U%PX&98qwKgU;n4Zz$aBwt4m+F;vt~4k0v1 z!$^8D6+yW9L0_JcNC)1Qj$G?vLr1PzBSb7jyIIc74*JrEtXrkmvI9>w9MnsZxmDrK zdks|Rng2jy_V>I=J!OzFhwPU5y*7QhfN}+NBmmK?gFX#0iVZmtkLsCs zm=H)Wu8T};LbWsWXNRONl4i!2iO7YLoyZY?Ln4nw&n(dra=R~ z+rCD9fn>7SL{yO?o@z+B=`BmRMX*P$vl0y zbw+V+xL(4Rz&kveJ{zOJK9liF1I{pK;APkJ_&HeXeO%Er6zdh=hfe7NWdYQQ4Us-Ac;RG!Xra^@djfn)?78C z{F)KgXdR93JmNTr^G$Q{I~@z|`Sjp&nrN0u%taRauHz&Yj{nR8=NrSfPTAS?Bb^o{ zV!k$BXh#X2Yw(`cU~h=_M<&4zJZR7IRpB^OBrc|BS`uki1o~IFp}$})W#80%?4e@nQ#slU?euA_r8<57<9ov(JpC8mfU!@n0GKRZBlmvhVdrUZW1EMgwTHFY4TaBujLyPaau^x~Dp&tL|WV_)PE4@rz#S&`MC z3-Rcf`n{FT5BO#s1Tkm-vrfu6w%0e-JN*vF4X5J`|EUvA?67Npfv6>=tEHl*osCWb zl_jbA+{Rep)q#sRu}AlW;?=E1QwXuXuqEr%g{2Eh7cQ0I?45JrZ%amyL%;H;M*bGK zP>tVrKx?iHk`p{5ek-<4t8nLc<~z1!#Ilm7M8z}qjlg(dHa1;qN1-c%4A-V^D$o?$ zQz+AIidNc2Lny9tV-|Ob?g^shtdsgq?{dw+X0>CJracQb8)-%<`xg)s8Ei^Dx zX_R7b(y~MCJ9jlY)myE?>4e)te8C6zyn`{Ua`&5Ok9bP@^-Py_<$6Z;J&&EF5gFP= z5wpdIJ_)>d4S}m|q;^hrG#}V;(u<${ zbQib(YSxE5q>BrEg`L6HA?-WUwt*`I@ZA-hd#9>4H>A$BnSpM*tFD}q^13Sb zyQ+$+wgZ7VC>L=K3}IfNUsBj*FyUmG3PZn<4^!C^-F&nTB2YaYw2t80u!dL`a?W`` zg0~lH^(awjQ$@GSPLK)?T)J3;Bf9te^L&FFz-GSJK(?ii)BdLW$)bWRVXet{n8 z;C`T#>EMo1dX(qBr}X3{f#TcGVcwjI?|KIHdDp!IZS`17NX0tUI?yUTYs&;R03EsIo!F6>D1$sP1HKb+GFiH_&z->-cFv zDX1uxL3pMqVdDLYY1y|YgyZhb4pw58Jzis zd!~7yE$W2QwhLih@Us`^!Pns)RxZpYfPlv%QLL@NHqe#<_iBuJrvoi;YrLkkX|5`5 z_A5%89(Gai+x4^3b`)3uzbwr4h%1uDGi{))`u>5oTzq#q%mu()=sOIotsJxf$iVtv z;$5t*8vK^RygrGwtv;o+r2#RB|2*8fV1FKH1a<)}z!6|QPy=}3PXaXJ+YW+f8o{lH8i~72&b&LgSx^TF>R^Zyz|>h#hj5QTKU7eoxryg7 z)8<@IhjDwL-pM@#*WuiWbIv>s_aO9IF1E{=r{ktUAIojL=FE%ZzK3f(#7H^wMhNOi zZZhfM|bdV`=QaUF=w$W4UJSne-S#|i2^ z-2Kqs%kA%U<|T9YL2u$72Z!Ui@z75Y)cd%H5Z25k!{&Z246c9SK7q|dt_G@w`w6O* zlOlB+cQ^c`aFAS(mnx`}xG~UA7Ssng+<(fm3+fbZH1tyibs9Gc`ssrDAeR9BUj_9c zZXog`jl27@Gj9eL5Bqd33aLEI#X|pxpw8rU(9aUoM>#F@8G<^S(?CB*P(SB}L;skd z&gJfe{&7Klf*S_?-vl+2i-A6i3;o@hH;>C}cjnFKhQMY47X>w2P;)o}*M;05*yM6| zKwTuLd0Zs)PYUW{F71pn?I^nv8y8^YGyM(Y6+$HGO3hKL9jdbR%6V&%0 zuFRSDKZ5!`_Z#$HLG^JLp!W;v2i)XK&b;;9IoMQkXQ6&5s2jL5&{qlSMy?(DO6Kt75Nj>maTvF;E{$GQ zdP@5fR>wXS)H*uuhe4Mo$lpNwq?5iwcgaP-&w7<_v`Db z^%+!8Fn=Ra!kEPONi`LRJ4)qnOR)Fqb}rX%@*HI}#mmRY1+SEsP1X9N{c&ul*0=ZT z*H`Pi`t?5Wt}^P?{pR;zj)Z}RUt6t{QzoLyBh7_IapqFM0Tci$02hF%RXy&+VMQ)8 zvlDX8U*Z-$J3GrMq2s;c8BU1=gT7*`QzG8_))&P|P6=HWFV;eG^$l^c+9@Fym|_t- z2gCQa^wS=Rs22Nk62?)X#uG6(|LZN+AgI9*>2QhgKunrPq^YRaBy^NPUW87))`Nj* zaVQe8Nu*?~+L4NXjb*IH`YoTIzhJ5j&f_IXhk3(`>dee-6O5S)>3DpR7-9pgtJ^0S zVaOF*W~zht^TgHzJS!7|Es|Ipd7N>as-poI?LJ;JlZKs5r2w(W59ANNklt6B{e|Wf z=BL4eenyiMK1HcY5?*=Um`1N)3WsO(h*+m6p zS#Vxg)9J>pxRZVWy3i{;#P>i;ugr965>$AV8>aE{q>FqZWyB@M!W*iK#iqflNU)e9 zhGJrzTS!_)fn&MK5y%Q*-YmIaoQb*Jz+M}r18KQIqS)QX*1p?SkAV34F5WU$gbe6E zx9Hrm&7pH*m%HqLu1ugA;KJ0Tz=C7{7J<_h0;iN06T`h-nwBRlp7FqL5))hNe-JT9aePft3J z!^usJo(4=1#Ou)S@?k|qQ$W*J)a%srgtn&OwtN-lL1pjq+@jK(k>L5i5?qz&K+!&07$ zL(9(b!TL~k_NK1=rta69y0)9T)|Q3-$=}#k^{3T?iFo*BOeVIr!LNO?D z!=Y~h<$HmNz%@9?!Lz{;IjEgrp#8Xi@8ES6vS)A)CJB|15RArIhO~zBjYd4e+z)Xf(_qL7LE63W9 z!x7hl9}+e{@xH&Eg?!u%L&DjFUu+_Fk?(uHrb6U(sjv-O_ATPUJ5$7Wgm=3m@!AyZ z1si6@AzrgllqM{I=Zu%n_!kfTWD%hP?pzv8y0>gKe{iOH?c2zT?V)8J-uD(S7>a2X zb}85)_+dzbFZd%X?o0JyTe!j6(u8M5H&t>s8Zod9wx=9~Qqe$ihZBz%1x^OOihLym zheU{!&^>@a-r;aeYB2AzMW^wLbI;LVyu?{9=bq)=jn)#UOYW}uy+ZAogw>(vJ(FY( zJk~Wy?kK;*Gl@9<2P%Vd;5*9~i=2}}kTVUFB(;;I4rly^3JIIBkLT}8-I_@`bxsn2 z*&Xy|S#ywCT1JVOK-5sC9AE)R!lC666&tmg7K_lE6iaNuB6v0E@_T^N_Dd`hQ>f~3 z5NIMEheaIVi#8Fz@DLYUyd3?*7R30d}MKRy59=lmc1;9APv!m}47&D`Ji zaRcbLqw&*;ZfpD~H>z>sOr;`}M1m5Llt@730MgEWNL+TEiY>zeGD3TD3G~o7^JsHP zZStrrKxpU#3@GJF{LyfBF|Jycoe}&lBL5Ff`>jB8!6PBKj_MdlGO6AoCkq6-`iTyM zIMW3n&aHS8MM$$;>DZ_$>*R5t2qg+>SI`Rt#;B`1i66KGGB%lSbiv*1V&>!NH zEuy8cQ&D>$3Rn$10uY=GbAh7m?5GR$L0B_JLr#;IPiMhDbV3=fl6!ol-;8yg+ASwM zUdao^dckY(k^`*}Rq~oml;@3tp=z27YpT>(?)#1}`<}+^QZ#QF*q;e_DG*k5a`(G8 zld4mct#8GBE7)%rG6FYV1CC|;A$S_rw+1121lj2)f3&I>5(z!%3TEAIzN~{TI9AEs zf#p!jS)JTh$9s3Gs2aE^B=>#BvnJ}KaJ){_!${>5e(Q?Qp>34=y&W}8DsK~Y*iR#9 zl)G;k+U0xRyj1_Mfa9JI1qRp}p-ra|v7Db;HOgytscxqBzTiu7@Th9K+;LpPt)u22 z=m&$hikn=)495F2U^UOZP5sdv2z(xTgg*_;$~IRCKDeH?eowUhZz54BqiA$!+t~TuB2Py221?`XVI8#kSg@Oirq3ig8$C@GyC=bw$ za^F$DunRT_`du(63ha?!P=O-c5k6305$9kZN!JWP-uteYO85 zgb??#@8|`YKY1xTRMGE6Xs5=ZUJfsMw9f(>i7_sIJHMAyK8_15B>rzaHnFn!vWPzm zNyXC!cmXUJJ1rm;xRxsrJSb6kJ)k?!vk5^j^j8n`WAczi*J#4>9HHIEwot7Yn>P%G zMYtJRQ>F11Ydm5x3J*pvjtUK0x)NQWQ>b8^Vu?{KbzlY*xY`z8NQeL9ok_|sDfK{c zd6F6q8%#98@g5`;hu@e05AmkBUAY_oM#_~Y7oA-Sr(73;b-14R)|B~ z^sV;!j((wOW6*7C^ABs{ga`r;^6*!AzU8GV5kw{M;9Xn5vvjQDVuJY{n8<|8zm}AT z-W@IpV(5h8?wn65y*$^|$3A>pdl1$?QG(xcIkNB81+Q!H0E5G6^ z-yqp5`S=_@dj(1Qnb*#s0ICEF#?9 zzm(ne%NY6n7>XcW{KR<0`@D_D1Q|t`25W_;YP?W5X|>feg=avjHktb=Rk_hh!hUN6UM^z}L#9ggNJasC^+?P0zT z1S*E1ToJM2P^vhH2?3TXY+1#c&xs^s8J7uFp(+Z%!&1a2oOyd z))x?_j*01Z?R3gnPh-c*9iezPnhl*y&uHVAC)>SdLS*6J@h*!0KnJeUv)ezLpkynG zaC~z6RhQ^Jc>^QyZ`tN;a{A6dI)ZvrHMlye*z+sT_Tl=dd!6t-ilaOI(MnIWVh<+3 zYyDxY+ua$6Z^J(zC(IwFa=W)V9H||fL>SLl%GK@?O?gj$Hfe<>SR^j^z z>Ea2f@a;Z*xh+~zzS9}4JQ_OHWq+4mj@YdFMpD{olQ>zHZthwNuEsG9UdOLbg&mz@ zUHgmWK~=NYVfkiNGb0%^L5&QsLUO}#XZe(=$B}+}nDvqiBEv0zrN8-k(Vd(!gC|C%>Z;YVQc>$Ik}(u33`C5<4x*R9A;xA-t|uV&b4cs zB`j=Qp}yRng9qa*$LXvc@>{5{Q2S2yx=q45_jceE>dNhm?0y{6m*}=^)3}`4Z8*dU z88r@zBhG??}Sa2_>_dj4f>XrILmJ$@5j{FMzLup3$aP8ac>94hRGA|GBOhF3f?^y7pT;l zu?`D*8sQVbzoS&6T}t`azQ_kx^MnqlFi<`azJ9sdV5-OP62(B+KDXHG4XIuvR4y@v zb5T9Mk9#ql^;W2Yb7%=JS9`)~g*HjnuC2$=0?T`W#upd$P!KUU`8SSk(7J78h9Qd^ zwruka@$%1a;K4r*@PuoVh`p1)oZ{)T`HCbMV3jLI@)MW2}LG&m|^uRoG2!n zs;EcWQd^Kvs>(7Ykcal2cFD@tm~=*Bwe_Z@*5h6mJh~(^p6p4>pv5wM0kQAIZ5_J= zyDFARD)>xu)#EZb+%n`jt)bR=Ymj7-2xmmT<>3MLQ{)*Rv5_)*S)-O2X4y+$4X(Vv zKMS76Pgz`z1Q6BL0uS%tA*4S;VS5k9DN-Uywhh|0*; zb_?w!qtUF#VBl)M;dz>2T4BP8`L$+TKv*yQuSXnc#rW#}xQ5ZV47j%g|2TM6MUZGY zUMX9To?u@Hje`@Sp>_pyVY^s`B?_YLph>xy;3!Ynn$Y04N8EiFcQ{e_F+9{cvD#3g z3;5JqAx*&H{@tN}-kCx?DU2>f>~K7Ut}p$;c#9#y^Yoki2}Y0CaXY`1$K!mNg)St}DFw}mAP?-2evg71oA5{D zls_7a|6ufNIq`?MJX^l_lX+Rc8O4mw+pvHLUQk|2jU&Qia6SFsbaYWJc|%gT4L4f{ zEtj*YsBkh3&OikUmmEEtz-{$m>Qf$UzvX33JutgMgA3%LWe0-hRH3?J@Kp9?PjGq6 zVl!C!C;Ii&Tb10K8mNBM5Q;Oz7Gc)!UqlPJPEW9_0F7`Mt`ol@S&AMm_*>$wZ()5M zI|gHJ462p5cc^?_x6j1ufem$}%UNg}7gD$4 z+uUd@)GaOz^K^g^x;)O`fIoa3N^o@pjYVG8cmvG&gvGMJ3{Zh}Jh~9|=_Rz#!3h8^ z*uyub02ldw2@j|wh>KfvqwKC5aKiv`A*I5gwJJ15ev3U4GXrB#U@rJT7+)_RLvseh z>%hcJ5m>^#^+{N%9T?3YA{3~JIa3M7t_f- zjkZ672q6aWr4jN-$Rsi4v4=04PUp!kg=K1RFKCaDNyW62s$-E!WTx7&Q-y&tb{rje z0%x>_J4efU0v#>XVM0h?Ma}eM_1FXJOiR~jh4r1;G?EO){kAGIkOjh8EB%h+Ja-pO z7wey+I>DvmwoM!A{w*Gw6nZ6kA@#PMA-Hj&H_9HRzd@B8KQVsQCR^FMqFdFI6ynV2 zQhII_esAzvh~bmpFW<;m?|l_#-<_id6*K*}E~=PWKlW{8HU{1raUjfQoC~f7dZBLz z)&pKZ`boGs9M}te;(W;R2Wb3PczwpKX1Vn;#10$nc<_%T#k^|0$g}b3sSr;(<2Nc* zRA*P~?BRGjL4y5H>sfx@i1B!TsznCVX6d{U6QIk0gw6E$2`y6Stf%n~+xX2A7$Cxa zv&0xnXL^`x;4DvfJ=lUd#y9)(>soIXGGy zKTlLSfK;kTHcR3Mlk9M^PbctYWF~7#l8cALni)u`^k(BUf&5MjxlJ zGc!2);|V{Ti?QNm7CmKCkY6GT2+y3SBN*lcSk{B(U}j-KLddK+0oI=7eb0imt=So( z%5YNoU2w~R7I z*MvXRptJoosP&KO&G~bBmO!1s-g;pdYOY3aaE21vqTm(9DcH0tK=@SnQ3d>I1N?CI zKg6TG7RZg1KiHxkvI#6?SL4KFMHKD`3uBua?AHSXPyvwu3&a2#zye4BIn{gN4{!tJ zfE}m)FtdS9K>m5S z`B)oktAhUM(qu~q^exbnmy#_Gge#r~-W3 z0rO?nHV(9Y&;E&ffk&WU3Y-M6ue9tuXw@~GQ|w`F>|~S0(u+IUK=BQnLG5F0njPV0 zlQ_)g0$NCzZ5`r&1a{a1Cyj{Dj_+#%T@QONFc)s01AnhmnXB$FSt3^@TTu#B?8aN1a0tXPbbvI;Rg5I|?-25C+ zh%hYDK4_BJ@`= zY3S=g-vRv!xCR`ASr0r3xCfgoX^B$X>M>H=x}he^wm}Cbl39$+g>tgAA>4c(e!BLA zo2UK*Wfs^D#9vpL3nFmFcCOUM!cYBdsjcHNsm%K1Aeyc3pbwzH9QW!u1alh z!JPQJ)b<)^{FmY8g}|~LiG}Tz+TMoUdSCQ7C&$;buG#Zhi)+2HF5O?Banq zxF58K*&Hi*{$rS5KN4;p))a1L)dwb4K>slGYk&j5?fm!i&U}M>2i5^+faHInETL`{ zejI9h75c9L9!UFlxOq9S7q|-84uS`KTM_6E;1b}-54G*eLRp`O@_GpTui^P+pkD$v zfT_*+eSvp?%fRTv;pT!)v8~{;)V9}Pvb+p_pN8GjpqaoX=&t~yq2C6a{ucH?>nw>a za;n7kE%d$6{}XfzpaymVO|Y|okIu;w8@92R;g`g^%9niU6p76No;u)X_;>gQU>Q&g zoC4y%4>!*M76WU5J-|62;%K<}FTm4)8~6nH0g(R?ZoV6s4QvN~1g-<)e+)N24!jTS z2L}BVZngpofg)fta0vJv7;+5$fyaO%pa!@G+s zP9Qw+KJYyd)q*w{$N@G2hk)plxH|yM1KtH%0p+Q1^CaL!pcXg^@Ic(@aP!}QHNdde zaB~Wf53B+90vCXYHnh>ei@*nf_?K|=G@u0d2H=5ve+@T31*`&&0(Z2Bo6~`}fWyF` zGvVe(fwjOjVD?#*L*Qp1>Kwik$OpCnX94Ybyg3T20Db`23n-Vs8^AuG8yNQ+$`^17 z2)&4U31k6p1I@sI4#WXe0LOqkFCm|R4}nI2y^Qt)cn+up{tfg3aaYg=0WSa_07n6N zXSjJ5Pzf9a+5q|Q;pPM&4OkC2ctRZ02{s^rSm+}`!vQt)6lg~XE(|$fhjx-UG{Bv( z8xBgLg&qS~2OY%fLE}L&(00(cD8(Hs$4KBV*e8HeKa^sf)nS0${h;@Njs=X+Q&3*U zfx-^gl!;>k%;P~#0HycAjGo#-VFM<@{1;Ff7bvlUS^!Wo2{Z*X6%-)TK&c(2i-CE- z3;@@D9S_2t(uaUF=rcjnfhVBP0;Oh37s8wkIv;caC_r*RsU0ZE1I-0p1PYKRL8%=m zc>#D9`lo@Xpr`a1(0ove=b&E#O8rn;0Q2*p0C^b{>m%evP{iY)^d*@84odL~^o5|* zAEmFtybKg*z;FE%&VGPIpTN(+FTfQ*E<+l? zI3NXh1Xu_>2b2O{U?*@GxB&bP#D_$fQ-S%w-+?!P4}smlVW1OW8*a33%O$Ocvd zn}L4-hXFd~JO;W6zsEs!gn1jV0(b<7QoJ!z6Ypu;au2p|z(d;j1+loprNy{chdX6J zFLjd6&7G15+~4ztB=3A5G1%@IaO4Dw)EC(rMIndZY> z)Fa1x$rcu6y9P)bePH4^)W0SmEgWSF_2DS$`oqvWpnnT^6#6`%6j%Xs$0eR$3jOOq zC+dtBv>xzAM40P<|3Cf@!i_H4PRDO!1}N`yK^FkoKyJ{E*6~Hq4@7@g1wVwS`mmZk zF)QVn`5RU?PyD1Y^L2g6zKK0Ay`L75dU)aw@QcpUg0BVYdkI8}xR4*z{wz=ko?Gxu zJNBu~OHfW|K2XqfS_k8IK%P*)JzK{_&ovVw+4_)|emOToEA2l!a#7=mvF z977|_p`^=J7xlPzpq*543#_O24OFXR@4te#uj6 zvt+T_oEm4c%*DLWlxVWFBkp|6nN_eqIL2g|0zb*nPXbDTJ7NAD^hbM26Ptj& zz%F3+C_=u%?=j9nGGhSzj3f~>j_O4)5D^YplT1CJp?2s%=6;Md0btu+wfQ>m8=yiQ zi(&Uqj8C>9UVS-axB+oM43KDLGpjP0Ou2>4%mvyFB(q&ZG7AtVq4AzTS{k~4+QSq4 zJT7X74rhfxsVjdmlCYU>!F-Wl9k9QGtyW$IdmMit7T8S(RBvIPTxznko=>*aUrn|Y zTuZjB23!}DE$x?+E$iM)wj8{aY%yF(ws`5>8pU8+Scr{9x=pF5M-xnzt`T_8XdL

aG`U=+c9{--0dK|;aik_lP&k|Sm2_|hc>`dY@isUFMcO>u#5AHh|vqZIf49#AN zDoOJVgr-f=&Q4lneQCyc-}bT}wzgdt1Qq{Rf7#dmZ~odk|8M@XFaO{CwOtJO!@kB( z-S|FN=&Yq!p~Tv5TOV$DcekXfaR}d%;xM@CA6OM_J<@ITir`PTfM}b$o9@saqM^pq z&66tegmUONhE5w>)QMjgaa7_R>jT|o`vgC94<;cLyX|b0c3(H12-AK=?S+H*@QbtY z+4|8Cnh5!=TkE^OVK$576GpT0k>ygZtPN}1E#>O9tI9*0#z(tk-2onPNaK1={6Xx@IOUvUDw?*mKGV0RXM?^Nv$8bitae+ zL`p0k)Ad~sgEJ^1kCk;=!#oV*WW*lp0H<|edD5sALW)qFBIPvQG#3u7P=i$|`;Di( zmKAlgQLw=o*A@y{p66oSN>S|6Ukj{nyQ;o^9M*9@6FfmeCsb9$@i=p~I1TQ$XZ zj&H!D)zK|f7Eoz7gn(U8KvE_RntkBJunsCuIU$zvpN6$iq?`2dM!mS&)|S<)wZFJ- z6a<#HmmYB8d9$fV+$Dxwo_5HN4@$&BSTQ8?=;%{qG%R-Ca734kERSHLHZx9anO))U z$2(n;>~4Mxw))_Qd>X6qcDCIwoavYwNE7=oIy=q81xNQ!dmp@-W#Qj5dNcd>AY(^-hVn0 zmT%I5F`T(T-*2cFW!BUSGK?TB7KbfSI&~=*{V5&TSf6p7$423xQ(^4%H&}L-&bz=I z>oqR-7Og^MsR~_e)ZiqTp1!4mqv5bhpa)LN<+?2=a3zzP%@a#;8xvm z1a7N@2CgsNau@0=ZeTh;qMv)~NJAjpxgJO0q)BL*@RZ3d_hB7-TS%t;B%$fj?M9s& zXPO`?4SH67Y71%bliJOJIPKz=ZWY8EdJDW;=uw7^G*hS0v$$2E(XPM&E5Q!YI|>3P ztY{aUWBLLmnP1NH3y=;0B!C>KI<|k}QKWwiXaQP*cHlhF0dxW^{HXvvkO&-vT@2_E zPz`7t&LgXe648QY|$bu_{#{*YT;`fshKWy=1f&uhUO*p(vz^C9m-1I zkWQA6ISc2>NY0=Ta)*WF`0`xQGt)8)KEL-@NFQcp@K`J@Yv$Q&gA44U6mEx%BH&V>^c*F7PK`<7MZmis;}g zcfctLN}|N^8+}hA|5eSc=*HZNF6$+}Xb3s^(ti(>cw3;Sc*qJrx%j^aN(ctZ%Ho5( z%c5P;snKH{nu5>q^4CYaOKqnQj}9;VoxSe+8-HjnUzA4Qpk5*uK7yrBKK|QR|CJ%X zKtlB|`c6K?*XO7*7t*X)G~nce{1QpvdvE&4Tzu16Gv9g&PgxgfG;t#arOD!P&eF!W zJV$?^MurTwkVU@CY(q=SII926m)WEW-ZW$OjOS09Xoc0Y4amq5s``4tQ=w1uB2D_G z0VusK!cVm{jOOir$|a2n`rmx@FRGB|)L+<1{Vf)8ZynAfeUwEVb;gew(H{-H27Z&F z{2UcEzUs@*q5c*rbPcL`!k0D*{$BFsKX1s)9EXIczY)G*2IDDin#BeogZEYQ+Auy> z+qj1f!-=`V?j$*HmGO@=`xX$F`p2b9LP);TIwI=c(y9^q$8$_fk%mkjO-}ap9eh!! zh69`|p;>dr*K*cCHA{V{UHCHY_r86@$;r;X{QSuO>i1k9YU?eJLdKu*A!l=SiA^haycp|vJ2vZQ-o2*Ya<<;^oW_uXC;P5sZg4d3!OJcc zThI6^9}+qDPPDA}#Q){QI?nLHCypX*$!p#ZwKzt%V^i%G2V~?q>SAl^w0NTjdUSc{ z(Hxu9=zg6dcB2SVYn`I7-69-^$I(W=XaJR7tKJ-flS$g*vh3IS@~!Q@=ojbCS(|!x ztgCFpV5aD2yhd%u0A!rTIM41(f0hpJEaRa2B0n7d@u_wXe`xOVL zWPvDR5QGkyo`g})Y_#fh^B`z2IP7(0=|vkkJeo7(4aTF5Y|!E%0(J~LlzpP;Vbb-i zZaQIiUfan`o=zZ;zvc_?_GjyqUM2C{@qE(!TAUV3C2UXaBvShT(M=}0Da1dW$Z$cC zy3d2VKb6ENCqsO7zeO`FqHhmSbk84R(@i3}2S}H;Xv8gR_P$z#da~#ee>2@vu$VUa z!YvDS8ujy7QTr_`@aCU{m)%KM5}K`>9I!j#vw0F}hJ$(^ivw@qbs=~ak^yUo>H$)y ztes2>r$Jyo+%J2dyexc6m-W;na_Rwc8Si^eA^T*3z)vDDzKeaPj=MaGTz-HcXzdh& zmTfG$kD1XgqPA2-H`R%1y6W+E*Y#(+W?YM6iZ-C)(6PQ&WZM&C^s>4=`pbJPr}m6% z-lJ;RGre|ChJVijTNX23ogj)o&ckhrcD!1bMgHU^J70)sU79$P5C^@hAD_T;CweiO z^JhF0|4$x^KsddOdjP1sPOM5o7KpnO$5*i?ar1iH%Qt+EGjOM(SjN#?8NoYMf|YO9 zDLf^2>#hb}O2M7U><&$=&@BYb51sm>xxG7hi$OTP?7{pb4db+2ARmzbfVRR)W;}kw z`7!OfJn;nQYlAn?vWeq^SW8Vowoz1eg-=SOhmV~Jbg*i`A#bB7IHV7yj7U2?YUdVu z@9NfZWhpz>`U{OUL-Nifo=d%v?jtbahV#(0*_7w>Tx!OPW9c9BX|w4x2R2;sPc3o+ zp@P(?C+}qQGrkq{@g!|V_M_o)o7_7uX{UYD)%xC=NG@reNEf5 z(mcqM&=kzF2?EQ{A4|62`2fo!r;;tElUO%Bk!)%FCE2pzN34;yBwOZw4+_}-oopfS zw;I^>Q?lguRz8c))sjh;eSoG$N^0o;)i+ZPS%z&mssNF2V~p{ z??-c*UfhyC0kK;%1l5VFhE!Y@RZDPPfC}rP>Z9B-sA~nanEQ!}w)+VdVhCWQGSsO0 zI)pG%IbT%u6wO%(I zSIrtD@?GH*S+=K5#N70e{Wz2TERzi>N{Z}l5cHeH^X5l^M@V+Xs#m|km4?`@01${tS8quo{; znbRsaT9wnQjMgxtb-?r#r6o;M)BhklCO()TKvG?xd@|ykiFO-I!Qc}wY!=iWAHr((2np!o7;#-^T z(a{xb=qaC+Eq}ok)~qCF_a8XWjL;P{<;foA0otV&Yn*dRos8X?GStaT@i0@tLcjf+ zPG*`jO=UicDTB;!f4(ahDTBM}3*_&FA z-gKyC)G7oT)?qx%STd}(l$i9CVV1PtE%xWrQ|`10--C30&-JhgPW%Y+%gu`24yV

`sw~W#X<5!9@wf{xsD~~hzJRh49kMTKT4Im&mEZV(3Fb$Jaq2BGy?g#CP-F z1=csi#We6@8u(}46Gi+{BF_xsiDH~lV$Tf5IRozg86v0txy_uw$?~j^q z7)aZZWdF#&qL@`8#j7%`qH?w<`&YiQh6ZSgihoN4Ix9moA~JllIQu)k(w}f#L4!dU z`**xw+ID`E?pv%K-dU}y{E{mScQelPOK zZS)OBHfeY8EZs2}&*)_+?HCgJz4Kv@8vv8q^fH~!0mGP({_>yH)Xfgn9cFY)mGO#8`EFKmTbwZH^AVoV6JP-{ zfyDqXk`P`LiT%D*WX~1oQ-MbaKX@?VZ@@muP72_!3E>+7p7iqf`b63b=uk|IJ=~ZT zY0~M9#;ifQtif75&~b*xBa-0y zl_4Evoh_NB)8bA_m@Z4B{TJ`qlcvkkYQKTR0bSM*Z4$@R_U}o1c>_dEGhWDJ_j=csvHBRr)@pv=Fc`~+ zR#2mCDDmuBj0c|-xasNfx&yXbEBT^GI9OPF(7_dS@bZ%fkoQEaD`@Ws+P}+B#;&ht z4}^BHiXstoONG?j{A9$tM?(W_eTyG6U{t&EUydfVaJje>h0>%XLoKAqSJ+AE7N2*$ z!GINNm)hH;aW?kC$9^T+$33g~q*ajY#`_A->zbKp*9)#O_E1gQYIvAJ3%6bDn5nM8 z<=#njz$b_fv}3O*cq>pGt&2WBVl%UkK1qr8my77uH5ffnPhnBr8TB-)SYwo8`^n*t z^*>DT%d9a;Aw|%kkJUXA=oc&~FKI18TbqRT^VD}p#3e=>7`T(An-6hVM^QF#Eu-)# z+%TIP6&T$L5-nT|rjM|8&VzV<>W>D8kd^qHr8E;DS0S2;9L!JI#tUmVz18 z*T;zQkU(}9k8TaE;}l&3Z62L!5xSX@u@87!HL(rC)&T9#{9Yy-E4#HC!f0@W6Ved1>T9IIjnAj!vD{OIAe;H;|=o%q;B^H|~!v0ku@{9Jbg1j>b zo0zpqQWSv+i#Kz_M#hU5jmA!vkumb5tNda*pC}jO3Zll5igAHVuZclU2mooR=(zQB`wg4PBy#v?i z)zIAHAw{Mnl!P4xzD$^CxWr(u2BH`s8Q>;c zl${^<{lXJ`dn@|eG|<#QK5+TLB#z3l!=3NQv{*9JmS`MfV;woM2aik9xnTKW4_+3# z%vTQvD@)n3uP(XQ&=OHWi}zBcXFb8AKh&(;7edPqynKHsXhpp=Dm@?ZZY&ehD$}A9 zWu~fIW{U8$4thLf>v6ttKOCx*ne~yllWK5B2D^4aM47U10_J-V#}^bvp)?q^Whs0> zLT6p5B}L;CUrUNR=N+xW^WK_N4cZ^XHYGH3o)*;3WM*p|MHO z2BWA_YVSB#cKIqI(%^aU35Z3Lv|WgTrf(P1@HrYRWx7}QPeudvV0tkBre7PO8#=<6 zTy|$*V389Z*?*@K3%*!8#d`xGl&S874_>D*+}AHaL4k6ZH04*=R)s=7mSF<@Okoeqr1U*}zHGzG-m$7+49cYA z{MvFFf%yO9?cL*=I@15~b51UVaMT145onv700z)57q{Z2yMPqqZ5OB)bnTX);H8&E zORM#xpXS6a1jYS8v^7}WO`>R|vIW{&)Y46&rD$O*^s=-C#kN?rV!>P0D(Cy2lVElC z_x*kU`}EbwInT_QdFGjCo_Xe(XP!amB!9n4lI_dbxH4T}@>{#6eK|=_N8Q3WaEL45 zCP^`ZF#VF1)FQF|odV;SDqkHgpz31*xQZBmqZF{c`pST9-7yXnM^-Gv9{P)if>n_^- zo9rVN7}&6ZOw4OuvhQ2~c?sAk_Kza8eU#7YC;}hx1F_T0`f}WZ56i&LVEh*f=M!$S4_g#HBVh?;H#Cp*A=$myY+qVtl{u~nH6PrKBnAkx zeOZ~cl@J2o*E`jkTDK`vr-`?xWz<8W!?z~tKx|0Rcyscxq4CPyOybT>yyU7W&Rma( zxtK=FJIznPgj|9^CZ=k)sa&lwbY?t3Lo#VH&1skn5f{XQJffQi@!8-_#96{9xye4S zm$PJ7e=fG{+4l$jp&>{z)WEB1Rswr#* z*d=d_-&CGdO@FDrpv5Ut+>gR8<~h=Kmz-_85???lR?s_&vO7a9NjADmeO}vkWas?I z6x`j3mSw@)Y$+kZwQ+ylAGsF6pkv2Lp$453CP*tn65~2kajQSCgxqHQV)i*`Ay`bUhHD)bT zH|*+?=S^Dgc6iI^J_=t^WxmNqCcj1D?jFubU8xh6JoVDn-;+@kh|}26v0(Man{PW8 z_pDx@dNli@D#vpD;-&0bRZhuGf20_h_zA8a(cG6maKOLYZ1(F%ufjKkf^DOh_=xZn zfApuppM_+_AUq+G(^WV?t2Qfcoc&3NBrZrDHxM4y>rt_hb&{HRD*c6@*a(Z1@UVUx z2x7o}U>VWXbqsV0v5$BvXVp(=vO&fMIHU8B5r&*}=TB_7^)oto=C$XPuyE7`B)Yaa zXI^#5xS3bH+lyWC<;Loq6Y0o`n}}M|8>Lqf?DTuVC2NdFy0uGW=VU1#;Jk%9QPz$6=GyUvhl>v5 zxLdsD`@4FXJ{b(Jm(|HRZjpcKLdKyfS*M-)QwAcpM&|*MgiRZcK^7*x@Dutd<5Suy zec1eD@l(pM!<1F}2vw~!mDdB%xUCqzM7sqGV z&RNTTsSI@`{qZMI2H98^JQnk%ko+mlO~2yPcO!m+pO2sK#&C9W6(IGt4h6O&JDf z=+3qLEaaTUZc6sN#wlNBU$0cnul#1TVn9%zx~%q&pa!nFAvA9P5o-ouySUBn&=zNx zxZT$@wf2C*AOxs>Uw!28VI{V#y{jmzHZbf0J;epHn~g{Q&JolBt>a^1vbq|#{giw9 zp1EuBShdO-ht(XicG8Xyg<=?D*w0tZqnNOnbL8&+`(xw(P zK^rFX$#nkAs>x>?-o{yZP<)A0Gkp_#5X5=8W2R$KLxGXyKfearK$m0hzy#4rUN%bD zl9yf$nvw&;M5ZhzVhWqfVR^h}>88071QO4lv2EqxQe*^H60HRD-*?(~{BymW?RTj?GW8n?vYQ1}Iy#V{u`y zIfP=Nm1AKif2n*FrA#yBH{|C_SCPJmti`3mru^vqluEp<7`*h@+KF0Y`wd%1!54Ia z{0dd13o1gWi^?LqOYSzo)f;msTMwUy2Ym;L*6`#;t8-g&*3G))f2iW^6cyOgR!*q)$wzLD6YhIhk& zh5o9#w|?(!OTJOtbp&CzR=FyV+IvTUkQI-Kd>5ICPPLug@eU(*iR?~MuqnRplU3+K zR>od^PNsQs6Z|s^nQ}4tGb`sv|MXwEmlAgF>)?wNg?I5tz_-q$p z%qV!UMOyW%%=%~Qf~wQe_3U`}NevrmO9$815r;6jg7Uy?%c;?H!u)758)en$ecKpY zx+TXMXTXBOywXth)*b%Dc~SNbE4nDyU&~<4(AiT$SV5o-b(!NsUG1>>#s@*EUT zW-HWa$*BuMjX(e&vS$4%HUS=S3TlxyXZsKvc{BaIab?oW>P2*qlpS9e)OV7}xp6yv z{3X=6Lgv4kKvv<^<747ZM?Mz)_mmu0EItieX{yq0nDJYM81yr$N=w9K| zFi$dB-wUHKj>h1p!S7f2or3Luyc_zlNf4$%&lUv9&?)jR&^XUvrwrkA{$BF_3W?t> zWlI}=6r7H}c6mfCfA=a{f?Z>$qnq7@_gmyW!=uXmjBW@;u+(Puj=jdjUlp@`hio6P zcaZHz^6s?%jfEQ-qNFq8FzrFIT}=L-Bx5urKIS$%`tT58nUWVY-xzQMqjrsx%FPkv$9 zp12O9;5UxHbvl~8j?b|_zQ%}cM#aVNzWVIs@u+8N_r@F;&VNJ9f5R!i+RU(Q;eQF8 zvg@ng6|9c7o!cXO+5`Kg%>nG+PMs=YBdo>^F`ps*hS}cAIX>U~YJ7BLPru?WJj5fWk|OW)jm#+e^RB|Qo(2uG$|?somNd>PZltA+vsOE4Wg{I3 zF9a-loQotIl(@{V=t!KCmO+nl8a6m-xzq5nlMZki);np1)9_#LRdCWlWGaBvSR}== zm}TO5uYz*43(X8{MKyQs6nQU+(-?FFR^fQ#COC`|y}oVA*B1#DD{wJn)@NuZ$@DE2 z)8FAo^od#5wQpa|&>0Yeb^#|VoOw3~QL$!e_WwE|M}EB#4!be1F{eWm z9qov|>W@WCR<;PP_2^YNXyDNwiLL6!y>LtigSCgRVHZ@-kXbjSk{yZl(iI6!Tw8iS zz=CNd=$O# zq#}X~SzxY&c1gMh|j+|D)mewY~ZlHFYLwJYP-15i1){t@c%F;>h-dyqD5Snk*{Zj$MMapu#D~)T zZv;5QC2|@(>Kr@MnI|)c%gl>rn-|Rzxn)i-l|ML5OdxfDJB`ukhE5L2Kb|rpQyP}+~)_$pOmLh!=z_^fxfF%IL z1Hi`f=1(youvgrWc?I-tRd%WC@2EV`Q?>3-h=e&oB6_tdEi_`gdwPVsO1D39eM2Of zv|C5L$Nq%BO}=an=~o!ZuYXOg!tIi}Mu$+JG97AKeu^8fY*2m5lWiwgwVK*=PKRJ` z6O&rqcx4r)wkKm_1EAPM7G+`7h8@}(IE6boA68b=d|hM@q&%$J@)JU8K*D?kM9$9D zHcP9u+3ss-8Jx2N$Pm;bp;@W{EFLhMHZqf2MCC4`XIIBg3}$7Z)dX3AeM67zaklgj zjH&7S1+APk+b4tf-4o8}fn?uaY|`{=+E=d-*^sGPa803QuH`YgG0$S~=P~1ha%0vY z5VMRJT_STuO{o4&4e04tOrVsIcak1eA?hVgyA;V}2gyRN0mxI<<}aG} z6%?fZP>I03;|afGg^x1<5JzcNZDL~n>dUvFK4Qe?Tgkmr`Qou2=Qt|SqN%DKOls?c z_`v!QNua)YImQ3!_69PK+7xMNp|Ewqeu}r53O1e%MF>Q`s}rcin1|BRq=?DxoKGcf zAtu4R0FVJ5Kqih({Ix2P)3EV?#s%;_Ws1(}y*4=qt0h*FSo>sL z`g4+}BpW3YBnOOaB(+~xvuUW&PT7k~s2W|Ld8jp~$=FXPbl9^?KniR!c&li#0}$>$ zGTmM4u5a3uT?>!8PUlILy+(`N>CQ|lp@UWS(|wnPPBraSQ>Hqd>3v=O5qH{~>XXf; zqdGPm-_;-KZ2VYN|E{(%6YFd0Mskq7%xx+zsh{u0Zq$@F8dhFiwW>7;hN=CR{zvKT|%whsYPkj9^WHMwa%FPs|DAnlG3s4(|9;{X?wX8gj7{?Z= zDV=V&e5*W*rZi?v@i+}Ts)_C&)iX-thK^RAUY*qU$`B;VbAFHXpkkV$OMAgx zTzt*6P-jWEB~|Pj=Tb~MAmdsup?tHJ2~*JE`|RtQerXTU=j1V_J5$SGOgpsh*ZW3G ziJJqJJ->45i`fhJWUtZ2uZ2_O>oe$F`?|F;!I4F~C9y?J*4Ki#t4pHFl9ep!Z}&Cr zAAg!=;TWoF-5~CF;a|UnHi0`_ik0bp%P!fF!DPk`V6`(mpPk4le0a#HxZ0Xyld}od zxK-$TkPrNW1xL}C6q~#`pwqVy3HZ{t2*DDhD+gEX24Fm4F=mrTRp2{u9T7;C$`Q(9 zT$mM(e|DUe2}CKdrR@>Kh9Peq92;K%Fp0a>&gj#D5tce5JMHJFMqQn(Iw$6PLFLh8 zze@~bg`Wz25knkJu~#v3+H%J2VdONmA!L#hZZ#A~(@_NOgFfqn(#Vai2z#;`$r{Z( zj1lbgY2_poV>MGWP63Oi68=G$ljq*j&Kd;RAih_%sQBdDVyF8bKh66+F zBh+Qx_z^HX!Gj{&&?Cn=hTLTagCwdgBYK%HJ2`sPBgc7^;qQHO^&PCGf(cGWDyREO zQpR9Li~(CU3!Ow5gzWIp;m+UqK^RP12Ba-t*!oaQTG;vB|gR;kPkE z2R(2_JL8;LXBcM*Exd8pY0t*Z>3u)n1q2(f+)`16-1;@vl4N|6MHQu~FTt=J_)!Dg z63GypNwf048AI$9xfGT_?-2VEdrgzIX@c}QN#uME9-sl#W4f#;3Xz{-0fvb@Ru{^S zgfbL`AuT?PZyrgED&N>awt#jCE4tHSI*X`@8M5KlB3;D=#~Gm=ZaO-%k*;~-jzwLz zUy7nPDokbWsF`I4Mp?k^qsy-rtcA-3{T%wAL&mI#%RgpsyJeCE7n{?w+3a#8990x zB+q0|T4Ko}$I6Dd)sxd%+02g894fVHl`ff~Cg-GfOxnkC44s;j$hjBiLRDg=tbU`E ztbDdIsUCLJVjV_AG4~{P!4i|FV@9U7>6&;fuOYZ0wFj-nku}jn4(6tI!T|WQNvTO7 z3>~L*8T&>KO~9YRkwdjk(@EXzHuoMa{@Op<7$fu^{y#hIgXP;HGNms&%mT#KG?_Iu?)63sqMPT9on>N_u30fBdu&ib3o;hP3-|e zw#t*6+;6BBd?_{U+_d0jY2txx6%nszNT^LZbL+0Qb(;Ut;?BpP&L?P0*Ai^7&n?co zv4{cFVoSg@-yNBWcE@gKTD$_d_whg^;6F5&(+1@im^iIclj5dQbkCYKDNiP6QK{)c zI?y{VU7t=B=)REu0K0&m@DWX(5Z56~`0qepecey&H(M#Y?gBNP??9=~_cIh(58 z=QMP|t7Cpq@*7+L%#u{DE}2&Ch>@<~7w${2G?+LyS~Dd%Ig?5bpwQIh8F-+@2Rb8FY-khRwfcI4-TgF2{fxA6V^(N*-@vPsBBp^VLU|H z2$8i|AMt}pX+N5YtEy^2h%DydU7SOJgoubS4)Ko5eVH7P<79JGLos3x_ zHJK*HhL7mz7d-t!L%)Dskc&di-|wQFqzOOb2q+w1C!5)~lQUqwM<`j;H6Fh>U!Cd8 z2DN``PL4LfK(gS4P_1sTS(1nOB{xQ#bBGGwjL5bHQJ0=ml1WHLtpi}qxMl2wsJ5p> zP{4hlZj$GIz@=dTI{_axOQP=h0EK_OyN-#vvt}GQ1G`xKq#ecj)O1onon+m=0LF}* z%B-=wi)-!4x);)FD-($OS1NRUtV6`EUEos(uJjQD6U1Wvn1$c-_*wBggx_N{!`a_~aVB0neh%&ueB&~{ zzzuj#d*%2!wGMuV_!2`xj?-h{E6Lk#beH^ zfSd*DD1hv*5R3yR5@I(YV33!6B{n#a=o$ZDS7>f-!t(HUn9SQpvM{jTI|TnIK-gJ+ zgsmVzBiIr7h)oDy3nQ=CX|EkqK_Hh{&V#eq}F@q>cweq&VA?V|6KLbK|y-y_lSy+qwGm0RhfaX&H^!Yo3*n zo!hASQ&QRtZGNUZd!aVt_|5$J?%WS=VsW0EBg@E<=GuQd<6R~&#)e9LcoX6X=l?!pjE(aZ?KpRxu2FAYn6h#3xP0Cp4x!s$6HP#pwMxaf+Egb>Alz_fE2P zE?%>J9D5>v^Z65q#svrzm!jeHk43@ZuvNoLr%LD_UduUyzH5Wv7}o)1Dok0Pcaj_DMciQuU7( z2D{*$lI5Z^j=yynyo?{@FY!KzANTtpeoTFk6{MhNeTdx$+3+8bOaB46b3f!6vI6us z&{vKKc>tg1NS+7q5g`xY^DN2p06rq*0eqe(c^<$=gxtd?mFcwO=AvcODts7<)AOS+ z2t#H)(rFjhJH!3!LOvX^6g_fCK9Nhj_Ml;i9%WgGTa({o#_#XVPx>+Hu+`9PwXd}r zKEPIpHE<-mC8(NRU-{-Xr{N!7`*x>&Z{@*F>;bF6V>KMN+Doj457|XlLyOh^iq-HD z8)!AOZooe$*f^`<!^hUQTvkKdhRIe#yY;OqtKpw>W;zXStKpL+x5uw+GEa_$Da-gTM%EtCg;ERkIzM-++b3HvKz0{PTXVSsTjS{LGKH=2c%s(TvV@6;(rzD=ULBhU*z$X0+`#7vh3`r0}n+yZlsDA^p zzv~&*&cLJCmlY+Gm@Z_#XdcCilZZHVy+WoVl64em6q&dl8Zf{Ib{_GA#cvLN%kYcr zg$@B5#Vx@o-l}7h<^1G#jm;xEl1py9E||T+U^l?yYV-a_N)>X&?QDW`lNGvR~J1-qIGwhOsjHdw(uot8SO~ z$cm3@ifh&7^aV9d>yi zanb4Yu2+-X0_P>ybu>vF9|*-CX($HgmKtg@h#01DKA5NuW5|K|wT|NaYb@+>Smv_h zOe-{3>?UY+rmhY)@^;Uuu98#x+^FCd|K;j(V_)K3i_xB6!UokTViND3)m*llN?aqG z(!KH1%8eItU_xi9b{P(ZF@-oTOH*B|EymiO^EpnMUiF;a;&zS1wBn3^+-#KhDyg6y zmBl5n2wIFc`2}S+5&E-8rzv_e>N*g}E}E{lf7)G`=wM6XXo`D693o1@n*Kq{7&29U z(hoW{1;T&HXG*R%+H2gfCtJ1hbMbC#90l5eF6aYm+*JEMH|&(>;^C}FJsV1mBX%|z z9E)^_l#^EUWa4AfiG49~o}I%cx#ppPgtZA=@tP7%K}qRVVGSCEvVa4pTfA^pLF9{( zMa8R2@{J{MZ%;xyr{cHy?1|rI;IHO8qJP8ad<_Ru(rfzRhrk&_^-{12$2~*(1sf)l zJ93Tc0%I^npgpl<8kV^#R)+Pz25vvZPe-FoUl5FQtrG!D1dIdC6O+;xga~l$Pq6PR z!6Gf5WIW*UI;gs!MLdy?Qat-sZ{oHR&(1%TLbKQZkW3aXnVRX_5~2-!+*vmFMkdX2 zHXeoIxXUwftO8H#I?Ag!V;HpXTx|%OX{o}pXr^O=q%gVjq+#0G>&~jjPmF7?0%fCewI*}IRCu&mjbl)sRXb)! zt=z)@mjUc-^YCV2r^O28Al+1MPS}o525>2FV4BH9Y#SgIAJgy3u_=7j`s<8gTH>)N zZPcn5)YShe%#9_3}`rWh045Q(#n@wdybf4gbBHIp@>6Jgt0FgJUu*YK50?bK2mrT*@&? zsOi6*+OXy$X|gdeR>zbU2pL*+MW!Q3h`DrIluZ~mx%Oqrc8dS}Qaf!?G{YKCSOb*S zF4WU)Z|BkF(Tk*c>P4ZZJk9ulF7rFvO*RrDN?}_r8 zrorhLnw(Kh#srgL3}!>)lH$_ia^mPXb(}UadK3F7-He0%M5rolEA| zXt)|D6vRHmMwpGNjMpga8?Xu(70Nr)|BDusT}hOg)-_Z)gm9`sVNsifQ^x`fA_)pxLp0_|uvpnNdMye5l+s9(L~lGzxR zF`p9UNT?y8K#Q@l%Bu}+I?Kiv%%{QSh5+wqnhdzJ;s*MqC)rxQ`D0MXsSWme!r920 z9|TJWIQ=>4Aw474QO`-{O0u;0jum61k&Rnyr`GcI{*ONPu#uR43<2XGOn3Ry+i9eq z6I@9K+LCVH>&8ViH1TRdg8 zOiK|=w3Ix}UfEz;sk>7EhNGTi_&JwcN#wdAA`hm8@7IUAjBN0@+Hf{#YmnW%&7{e1 zfi>Qebg$S;H>!Sn$(3#k#OTAAoHllUT!>*c8@7D!7*@GknfCg&%KUuDURydosZ47y zEz`Zf$(1v0;{NCWmv(FPmmYsbm!(0eI<@etCN!KDD&~sy(I6c( z*dIOCsg-5ef3-h4h%ihD48oNovPzA?&1qF;hmdb|uNcI}wh8XanbS?x z(<4jmnNNbV><6CYv>{HT%4{4}hO4)V1n@G(+3WMDWQQI1oM~E^AKkom26b!8ddz@1@s^B$6ey|g!7ll+loQ$=RX`g1nnG4okHm5 z)1Vm+uO;E~PZmHzvtx=jg9+!DhZ-HyI1jAw@tNAc{owNCG40a4 z7h(Lh4DLdVNccH$tDq$i#|xbNy=QOrIbyIQ9i#q=mL7z)z3*j#<#PV=L)_W%%nBJB zSrEIbF39T`giyNg^%h>)QH@$PSqX?c-8EHrR>&In;1aWG82#`Mml9=ng~F^w$SZAF zt8^U-TQlw%pXpfEuO!b2-xx!XJAmT13ahseHyWI43&1}XMR9SG$&~bsHl*Y03!V!X z9Df$dwJ#$$O8y~D;s4y`i+)(6r`C*v+8Tn-iTb$vj#(R0b?Sm?oo4xg0L~kVQ3ho$ zd)M-~@tpV43Wdv45F3zoTmwo0Kg-zy`#Q{``P_6?p3glQw1SA%vkP7y$knRC3bB>W zG-_GLN?{RwR|2Jy@j9e^Ww*Url~b$6CS%`1p+Gw#`4|mnih>qqWd~*(`7QzEUZ?%d z4_CCodZr%{DT{g}I2VL95ivRI1Bk0gAYqlJ!KB}}qM`rpviR18`&|+#@byLpn^)&v zDhuyj0?jQ$r17=e5HTQj ztTKpLj$lYGQeMh8m29|Ws!p_?7>N)n))O@G3vbdLc^eT1Vk4#E@eHW?MM|brOW9v@ zXE(Xui(6g#Fhw;4{@@J7A;ZdAH{37G#$aeajYc)uwK-_k23xf%aKrwCjCCGm3v!O3 zApc;=LOw`A_5gC=;HP;ZWe%kxi*O69c?@i5*aati#yffFJZ=jEy*+qhG}gkYNmsJDWjUa6?~@yPkKSZC3%&tPRxP6Tglv9di@uVx zW504R!x*MLaR*OR=Lfjt#Nr(h9esq69Mej-l&E97=)wFg_Fn@Lt*$wY_0FRNTm3K>Ht zcQ(PF@=J|Sppmy_9jXG+G91()U0~jz0moBcg}^U>_6}n$D5=af2Id-rFh@B*qzk(+ z@DyR|=qmmqJ8DH>zl5Bc0@wRU1UVtk!hMTxT}`t&E348~)s@z<0hdD3mt7s$r%pTB zX#Yo2{rm3vqm=_B5|`V4pdPUUM77(h4U5%DGqkEz+nqS?P^(T~aj{9-nD9K*Y|L2; zbSj&)9gZTP2M+Xz`ZasFY%-HIjj}WpyX@vFmT+3t2=JM$p6GC0uN6b?@BwOgx3 zz-sih4I2G9z0_hiaW+=pCG@NBe&-5(nKsUE+A~o&n&RT~lmYSpT z#V8%}-P?M92vW-Art{O_moI!P2(RLK1)l54y9cq>5%&`IE5}a)ANwgpo9~S5hoAWU zJ(Tbd#@)~N67tR?0Rc?`5HDGE1@hawhR-_i7lo?diN_ysxiOha{Vuitc1d#8((?}_ zKa0?4D)zuI4B?C5!HchPauinau6q~riXiLXM*)h+2*cT-$S*_NagV{B75Gb0rzio3 ze<_ucw2QwbRNe9yh8u#7>+W|NRybCjCmzCqhaQAJO`y*G)B{lGTN2`RJZl)pe!&{q?MmN*vhJ*@9%40D%uq-woxcU zSL-$888;YGX}D?Tm>}r!PWtc$t_Obw)r);-cA7|6iG4TRRRiRlHUFb<&!f6uaEJkg zeQmh(;Qh&t3%L1MbpiJS^LBe~ZfWJyYQJAIn;yn+1iF-%{3!W?SjCOXBkpFg%kFRru4&RODfu~$5 zL_xm43H_vAUlV?K0NFstUj|4Mz8{7LBFqgMK(YtgKUT3n*T1iUgD~C?6SU8>4QGV< z4~W)xn1zRMgX4P_aN{pATd`by{^+ z(%sub@_i{1H^_ZLRV7(p*>NO&g04gUBS_MU89T1?n@JvVoqNpy4h;^GDpE z{}jYm0qtE9_~7#p9pU&Z5%wa_c9Q3}Ccs_<>=kqYbrF1}9T-ZwM6k0kgO|v$@T7|Z z&V&0m$r&WLMwuO-fsJvf-M^JSia>*sZe0P?4+?Ue5jsv2BXO$|5f4m0?1}Q&D*5G| z?7_E=z^V4cHWm4I8fnpF63zI0PzR#?b_%C1tK63f^nyrAWzs+I1Cip+Gl{=Zojob^ zekkFkq8yvD0b%!0%GKdgl#X8syf_|w@*3m8KhBWd z66AQPy9$GO*eSU@2#$J4zz`AR17JMhO%%>HCFB?>dc(Ql5*#H%=C?dzwMbws!|;a& zwgb<3z>gZDsZ9LE5ik4~0G|3Sl}+&eEyg$HegrW+2Ik;B55L6dG4dt&i}qB(TQP#* z7ubQY>7u)IIpJr^h^p)%|2ygEra;!<_t^;KS6(?Qp5=hg#y5iJTY@Y3WMpOavdY71 z_dV~9ks_4D2t})Hg!qia!trdB@y# zf)MIFiHPNB6-@t=j<+slRf|yBqte*amAl1y2+!G+7?YI%{le8YWqq~k-tFlkA2lf} z|DvjD5=^D9;STwF3-Kq%$K^`?9ighjjYl#^h=nQ~^+HvH*iTA4BB`3x15Kuq5Yr z<_!yHL@48L`k<8@d@k?ZlwMdW^lcXkokGkJp|C@UIW084Wp%!_s|JU+*9yG%YwaX2 z3bQGOvDZKTDi{r(t!sBLL_&AE$^3Y)Il{@;X@oEv(`=o(zhldF@!2Vo~XPi~j=7 zx?&dmV^cWJ3W$P^a`NZXm9O1bl%re7&!1>762pnUelH!?^3J8x+`8tI@gC!vuG-5>374QHaUFX>5J;?Cz>AkuBkyx zLY!acTcc1aX|BIWvB0E$QXC!$OWw&B&!Q!zoaZ-^K8M%2(m$>xa6n{d^?feH{7!&= z#5=o5x)=vWeq>m(iJ$O}Gk#8zR0PAx6`YR#U}webqWILap477qsb|ZTF|FW#z_BqG z0zF=Q=2OnX^~_vA)znaxWYR=B0JyTG(az!_=6U24zd>lY&`H5#%y3h5veI$kBlwuJ zSevlH_KZzfW=xQZ`DVO6qA`YcaySMknCW);{J z*zgT*&e=Ad4UU^Eht1HbRqcO$Y~wgmkl84$&cQuGWW%Il*33A007bbtvsR^7;tZBr zzvga0KlWD90UTjcFo#qUa;2h>YW=`fL zjU3ZMyN|j1`USnXhl>8*a5FnE(ULqfeQ_R|jvyxX=qB2}SQ9pt23h-V3fMGjmc-OV zhh4mzg>ZvG1QGBakf@WC1!tM<#W;-^+xp0OrYu?cejclgZAu~MZDP9~Uc!tEkC}#l zf?5!}>`fZWVfUwF1gSZgO_xbHCFyG}b97{FOcfF*D3P3rww*;i2O17IVuKmElj7&~R?X|({LbNy z8B>vG=lsRFiw4=?wkI;QBMwE=EQ6VmGeW#`$8NJyu(W38rZl}O?(8mxYdK{p$U}fx zcA{qcHu*E4UI6^=`*zSzDcj<+^1yjYT;D}tF;{@QlMZbT@ESu%o(sYOZKFpoCKhg^ z^^UnpuQmh$CNoY7`t)(rn3kDTBR#s)6U`D|{=!SR1!Hu(veTpSz5g)$9_4$#C^UM) zNk((DrAkk?D>rlF-jTMZ!vbL8$@2mI>I%-Fp2b;=Ve29d>0uljWZ9e<_6}>4n_Dyi zE2U;MM^WBjZmowm^Vo7v;wEXOB@w2*MvtaWN*eJ>bfYKAX;hPX{yVyh59^Wddx(R3 z77kI9^v{H_lc5$*m`mpMjBi!|RP#!7sfwa(o?Rzk8{D-H_QPFqO`g_o^q0$m6NhhJuz;Omv+uk;G+=vb+8vBrFiYgi_ZP6m_3q5IX-_KN?oS->KeGx8cr*RGmVPRm3 zCa}-pWr6M_kL~Eo;g`ZTA-UxgOw`V$fw(Ovp)SCezi$l0iNwOltjVSd2eFr|U?;bn6l`b1;sz0NAc#nDYt@$>mxA3om2FA;`_4NEU2=DQ&G>Yj(g&hSO8+bTdvqA)%&KDDc)d z5!uX$UDEfyP%(>q|1>4`dEa;B`@*^8-? zK9eFPh8~{JaQ4eT2V$5zho8fon7pX4lT_*~=gGvHZ3u0Qcroxhe&&o%=%X(F`7@4Z z&sY`Gij^(}|13$kH$NmTKcg)?184Gstu6^)NYd=_n(o_9yMiz9-Jgg5omlzHPCdovkQ~F`{+=k% zda{-iY0(zU*@CZ;!K>fCX0d}j{LeHwt~9j57m=wTg!pX!Iz%)bXC$%MvE28Ab6ApYdqVG;n9kt3(!9juNAY)^ig3Atbvz-%y zz*3vNhokh$YPOs?p~Pf&nA~ZC0te6pOz`d-0Dc^Y-#T3#r`6d_ON#eSO;LwOiu$H;u4O-f1a2A}_owlwz>(heW*xfsD&X@8=g$&r~3#=A&WW_)k4^i-_NY zdM2b%vNn+8=(5*GHjTjuz5`(~Qzyr%Hz<@S5Fxk2)ZUuwxCG1t&ezs}I-LHC~G^z7M{ zr>*f+Z>OoXYjF8#HgeqKaOS(+cJgRj>8D`pZ}{uQg^QqYZR&5L~h3tV;)=|1W{ zoYxc)bo6jU*;q-npXQ1QP#ePE5XKy5A< zmT!G^f+eCI+kWK{%@SaO$aB!*mE6fRDBddg4Axu_YOtF`@QQg;;NoeJ1W`reBHp*0 zZ}kM7wsoESpd;us8~puLXhdIC@8qDV97+fPUy_Kz6sMPIH2HCPTx%i?2LwEJv z)%nKX=;86_>t7(sf7^rBiR0V{!+_w=_+etX-4B*qv+S303vc?$?He^*ZXGUcY{qyP z7aG9FR{b&_>O^QoCcp7-exR@KERruQl;gu5G7K`PvUh?9GSxGh*w-ggP>rbc%gq@} z;a)&FPtt|24fhFTKIc`8i(m@l?$M+Z5e1HEa6oEOv&_-h)0ak(izIz@@IB24-)KrP znPHzvanE?0DV&NHi~Z=st0+`q#!4!K9LR|gLxTADfVAXZ8Wsk?e^(Fi$H(Yz&?^x} z2=tmSY(I%xVCCqv7ffkc?wy&%;B{(r0 z_8}GaI+fp|X_igdiz{DqwbHQ9sYVWGIof6GkFMI=cbzZch-_AJbI9nbfGKy^&J91g5q!D*%L z&AZFh7N(ls(#YO^FPw35jE&xLgpnsuQ`SwHnAqdS5f$c3ZA_PrWxKTb6Wwg&RyJ|V zwo@N(bEm3yEv|T>eZ;KyR_%}J7uw~`vISwXEDdO$e`FKP8DU%K0Gt zT@<>sVIgwRm>T{NNH2fq_p@fK`t_uVEGeQc0Nz++tv=(|BW&<+-uZ4_09I=FFFVtn zcHCE1%sC;g#l7miS^N9G6>x|o|D49RE=M$brsjJgl{V0mgHxGVFW^kx46&<8e|dh@ z<74r@ZlN`}S((`8CQJOZwxw99T^6~^#}iV%vL3zMuS5VcmXwKgoL{b6f?dp(PGS|9 zWngdpD}u3d%#6u_yPn-#8}l{6#eGRr|Fy`!2%t@g&r1nr+n>)L0DLdli$570AyHSXr7d@xDkrOBteuO1sk4$U?i4=;m z*ja@y3H&?Vo_D$%-svt^LW36D1wEHcBPApxINCs--xfUW-3{&CJ|+Hdg75E(P^yUp zJK>{;@4J}QzkC;i?>yZN9^ZF`yL{gjuJL_WNW=(y*tZ63q$e+&xx9SGqO~i_(1374 zg~qR#pSPG^O?M+q74KGsQ{Q}~>ij5?{r%(gg)v_S+#09xl6Il~!c>agOt4%jG)v}^ zbx7Yq!Bf-SP~*pUyC2_GetbXh;rnm$#V13i{nbZ2U;p}K2sGk*b4FEnS+ElKd2y4O zDV#Zj_0yTJ=pNT3b8P9xDadhe%LX>VE%}A2JzLD)hsq9zjZ2Xdc~5bjDwFA*B)%KI zxQX0Sr*}kOy1=cXIXe{>f3J_hW}gqACFEc1CTNxgM`2F4a3-6^i;Vy_#Cwr(S7>(Z z>OVJ@>AOSv(@6Afm?)eluVLyK#&=TiqX#+7Qg^!)d@P5q`|+%&lu(D@0s$?zSzafr zxgy5Ta6H`&!o~Co>3`_<+W{Nj62ji52s^*?7Kmbh+r*9_7M7aZ0twVD@`^UMTjV7^ zryFCz4BfC&H(WOW&<)8DX&oAd!JhehfzRspWOX-Wf#+iW%g1wJUIrOsiUi14kEGic zeDci>rkf3aZwlOJPgR>LEv7cxjd*xyopL1?QdA8gC;ABXKfbXA*x_q}e}TaNoxGCg z<>+qT+M?!f1kk7U)uoLiM%Z80SbakRW{G>{;nnx7)%-_3EWi9|5&m|6_cdxpy9@b$ zYqxI=RT$h{`K)&=md?Iz7kahs2=yMI{BrM>nQh?pYfw~`EqN5wdgui!V6mnjojhMT^k zPFmyj<6Xyao`H?DDi~(WkKbqy9K857`&T*UCW@LnLVN7Zpf=5C&QqVJ_ij~xWr6Fs z=~egU|KYGInM2L^O}9JFJuXG1IqGg_K0eB?CRLCbV*`m7%i|m)!a~ZXGlD?Jw*YGtz2bzhlf}-|zTSWM*juMa^Cp zwRyLEZLPd4o*qAA_lWh?))RMZfm-G zt#IOgr35=NjukVW6f;&gGq2AXiu!rS)RD_q1v%dmNa{`pEY#$4$c=xuf459PuMOM3 z;ip}-^y~ZV-^Bb~c!TIO;uEfzp#N75-|u}0uKWKte1mSp=pFo8vAL3hja zT`pI+_2jkIKx?b2oT(1Bwrb3+iPca4~MSk@D&vV?)%G=cUusk;J?G=3QrX_;NU&+y}c+!Gl#FCTU^SrUXzp^EAJ z$#QHKm3zf;##3MSW~)tq8LhF<08LH5sxA^Z$^U!>0U zq%F$jH-C|H<*UgVrunKxYlyO2ctx05AeI+}GFZrU{%MkbrrF?i6{P)MmVw|_-srSi zQ_NUcpu=X3SrIXNR(_`Yjb(Lm{#SoTRLGd<8MM^CP21InrqA9YJZ-#?MahR;>5UvHj{yN%_esU0>2wo}a0 z12KG;#P7ESiEoO?Omc8>iE_U!1^`TWit3#5^g`%->5<2ql|6KwqVN=0cdoKrV#|Ta zVab&Jwujkp^YkF-0g**>P7uKsSrk-CoMSDw+LmWQLI#8#$6`d0VHakEKVl*~Dv#Vq zRj`@6&S!yXu%|MbeKpn)%#RegM~adt*jHhU&BW4aOXjR6awW>JcPZ=`VWN)llE9d` zlE4DHuzYk_GlPW?2m(cupeHMA5e)Q9v!1E=25y@Wx9gzO8S}lcQx?A#!y=S zaO~zY7(N&@r;Y#fy!ryu|Hyno74MV%aJ|`= zu5ToNjiQp}6>7ex!Xj|g;TN4=@AT)hiuq{OY7IZ;E082<^jE!Zb>eSEYf@;pH@(qb zpV+2Zf7JO=YRoZVTE)rtFuVWt4PR!K$c z=d~{1ia;}1dSrZ*x;eUw**xw%vpZ-)Q18xdr*H`!cDXA{b{**5?Vb=+iTBEqiZ6Fd zc0PKO**W1BGrPLz@Riw?B8#VYnmQqc%1BF;@Q9~@=7N4=d%yk?b@KSv?H5pDd+l(x zDn4o=4Sr|1!393-CMRbUNP!{_g!c6i9M>3F#rfr4JKdv5297!xaBN~8A2DG>{j_cN z@!Q5pLf(r|;b>LHOXk3xQ&S~7Z%-aIyPBZ&&)(nMLrV^ZybrH;MZ6CEU-7D`9mZ=K z!7DXcq*@)*wlwCSf~X($;m&s;#s>E&V%N*~(S+9t5hpfDHcx7m>>ly>2)A`xee$+R zfuSEos;zdcuU^_5xN{mX{COC|_TER;3D}iO(=a?_Z#tctype9(gF#8!F6YQ7+AN}4 z@&AFU$TEF@8^%<9U;knq$SP8oW3#}AUjH#adfS@y6S^qa0u$OLw2QeY?Q*A}jssYn$E81Owy5uPp(5iEQFL3FUlqnZp& z-)lnSN2%D_D%^=Skue^IRc8Qv#M@5B=CVku3#d6H$id2xUKnTp8#>Owi|Vb?$hFuz zh}^OKz}Vgy>RE=?&?f8ym#eKt3P*Bmv_)-yc~9?r^Lr)rb6V*l5 z*6vAW6o;m(GL*!9peXa13?;o_o;;y8g9%Kwub&huWhGgoso4!R9YQk=`GM?3A+pHY zEa(MLeojFHdXww>L|77?%uqA7$&!^+Rv;n$wop7qp6v7}aCj@xzO3(@Ky-;Yt!3NZ zjI531vaUkhgI9Qp4SXu_xxlS~8-B{tqyG#cVbZ|ad(vjPXM{gdCxs6UnBpmA*-q(( z8(CWDLD>5)?72EaKVGy3WJN4OBP1R;cAX0LPoZ! zq&cvM-g2x?VKvI&ngeG!OFGo2&xNf4wwxQY*i*G?Hk_`34N zhDgq>(zyx?;+k(x*(xx zdy#wj|6%N1z@n=0u=5dvFvgG3^jljyqC9olq??v7(UokYbqwH``*ADaRgI*?|e(MkVyham1t$|xUwg{f*BF%XK#4PEL6|x&tieDso*k4ihU5Vm>-HdT*}*T* z*kxfRRfrw)sk9I~&c>>w_B76vCN+oLj>P~wpc~@g+Ob!Vt#9Xlmu<+lun4oXyule8 zzxzT>JF@Jxd|H@pj`SeU~5TCt!%NO`W% zbvDniaB;-A4dIhtjhwu2Ytd9qF_x_-Z=Ghy-hEaTwg?wD_g(nfDqQiKyCH7u)OEQJ z#VtC>U04!{evBFE?WgJx^{v=xaTr(S3!jnY%4u;+LF3)ut1>YB#kiOAUGh(Ordgib zk1kJ~e27N*!lHLx2uA&jbRkP(je53ksEgS~ZjLnw#e%1Cuqhw8LK%UMq!NtwgC#I?Ly6}~j7em-X_KfCSj0S&{NL(z3vHnPvMe<-|} z50GX{B|DdWsqjYr`rWse^(k99;in1booQz`wX9UYJ!7?^m!#&tC11yAJB zL!S+ep94BE{X86b^a#F|#P69<^XZne^ZGW!{w=#FukYM`^PicDv-B`vSTZlQ=q&zo zk!DL?fAtyp+>i@zMJ`ounsg~9ejH{d;m?fE@Bo}swrO#7sNvx4m3Ms*+8_)6y|vFB zLUAU!C#m1Xot#c($7lR75Gy^o@;@@wdHtuSXQ`c$%-@c@8GwkJNPyi~iV0Dkewr*x zpNVKT zc^(I=#$f2MGHeZ6*n1h@_zSk(m^nWp_Y=9fuOddW`X$0NI5J6>sjZNO*=4;d(!wfa zN_$!_tt`AEO{q-_uaK$ivfdSGaPRA5PYcn??y5-R(0~*9!6SOl4VkYUypvN5l*B(T zAbI$G#ZkX?77WMC_cS__|BAA*?@Y~u84;3AB}FXl0(`H)RbwB4c15WLcW3@kXna** z0i6caZk69DuyCnx+X&^-kmWVoMvRDGx~A7VVH-y7_SE|&cVE6MLyiSQG^TB6Ctr>i zTF)0%&Z?6nX4cqv1`6Aml?aV=O)W~Vf*SobhjgzqODvWu|T=@;VH5RKB z$kV9ALMS$G19=I{Edh&Hz%nfX@J_U|*99EM@8CGv&dUD_4qpHV!C%x3A4|Cb_+vPA zQ)nWm)`z7vw5wm0*c#f|5&`z7?TKLunZ&8D%-Ob1wlOT@Zi%TWW>~M>`Yu*SX>hcX zR2rQ6(0Ngn$_?x8Ny&l#b2gPN#(Nms)`d|7CUH3X*A$bOlN;5Er3Q)3+Rh3n)0Z5R zy76DZNpfH9Qr!0E4y2Vo$On@7P3U3}#x3?-`BJRivqz{95*_suTcF@qz`ueO_~P*Q0xUo{eOTegCho+Qn^ z8f2f24z(YNshpPE%fE3-mNNISoGoq749k7>b_U(vVO!kJ9wt03BUPbTCsX|XjbI&l{v+y1rl28m>D`mO5KZ<|y=N`J1 zfjvFp@o4a8*e=P^uE6C*I8tj+xipeA`Cs)0NoiCm{1HrsSj3br!Vk+M8mbrF9z`Fy z^f5_ko7}$RSvW%s7^ZBLyAM8BEAw9Xls!d>jmptuIyad#r z@(OK ze=hS+;WLj4U>i-y>G)a9xiFX$vSdX2ioDGiZO6}))oJrFqN0NchI~1G*-^@A3c;D& zguwhKO>!=$DJra9D@C=l1C)+|v)w?4Qijnn7@@vaVNh6a%!n+z&a5sq zCE}fA>|51GDV$Ma9>TMe;AlC)Rfy{MKg+AAd^O6O!*ZPxXB3s$_W!5MX0goDkTx!+ zn{1T!#l0oc45av63$^+`@s^1&&WU(^jz5(YZPI_GccoY^EJUPt1<~6RDJe5}1z8Px zXB2z!|Bxkw#i@D<>jlbK9VJpu`12+Fj5AH}>)b2h=bTC5Ht$qLAzn3MsFE3l%-k?# zZW5i8$++o{-gGicRD+DeG|ex#y=qdsc-_;z|vr z1*H|G^`#f#_GBuEHWl{HO;=?H2VS5lmgRiy=nG{RGBtihUAiUT=Aol6@T!Lo^g^_CrcA3P+n15?myEn ztg+Wi=J+<543~{H_ZrljJA8ky@)xeC>_XZ2nh6_CNHZtUT)WK4slv~P?>MHZpG!uH z#StWSKmB5l-#+_(`#_4PozBNKbqQzi$p%wu9WR7+LU@1>PN1-=UtPa5P8s9r18eDU zhPS?1vlRf0Tl(9rMMBW=Zh!m17ihbZ0(l5H-a#Op&}>FvIAy0mKZ<#~f0JiFZYyZy$e5KUsI+= zWt+V=1-`^Lu65ixZM${a_{T9k7QM=ZPuT7tz&#!9r_rsPJ8iUA(Qaa++&5HH*L)gl1Fe(?6TG?ur&%+FYo&GW z)663VA#vAfc8EUUuAf?`$K!KyS*q7|_;m82(>ZCw4cWBOpZY6NwUx3JAMlg?GIN_7 z%IYdOnQi~+(&P%kd{LgaDG%HcFoV2?Z`dU!DbQkOvskT`RaisN@r*59h1&&gCk`)N z_2W5=D>m>S%-3=CmS2@c$2Z7WZk@49saI=Z|Vk++S%Hpe&TQZ5cpE}J~k9CxJC%79!>`lW7rP$}H z`l>(b#YPH5z?!6j`avr0xGf*fDRJ+oT;Zq0Zj)LRPXtdv7T&3quAhvQCnc zn1L@=ohKvMrWiSauZ*#4<4TiF+Iz5QVsFaR=7&_|lSLxpxE)`bD`Mm(?XXQ|Q^BxW zvRo{Quwxeo6{g&pC!-vti^*mVr{CZAjk=dVVo@tl7FOmO-1S_PcA zT9m|zVEZw@ScqLa6=zKpt}dtkKyoI5JgR7{_$v(?DXS2V7m#;q5RFwTLc zR?+KP)yYupA#ooJB#+b7q^wan<4MM1&%Z$I`3or{VK-Pg!+eS45P?ih$`M8rwZt+; zImK6@i)oUvuImAO-}JzjCei0mOHhSRu;U;5L4X(rcOQxjLr1Zwv;slumuwQfR(5+9 zR=sbJVml}!>{06}*rQ+X6~?3krjtE-afDK; z4Y$V=vMnME*dN>8Q@Sf6kUrC4f|5RF8-p-@=r~9)4xUo{cZ3z+rCr!D2X9WqtB+)X><^hy+OtGAv|7iFdNj-^38e3Z~OREX(2Kq1LY~EJnWw$ z6*IzZn@%~t6^Xbq4Mg<%m@O5F=#+L;ood^9UDKqoIZipUs}wDM$p>M!s#B_`0ak#i z@3yf*tvCz8<#NI@hhSj;)3)jqjo-hz7Zx4H`!u2Wbjy#?SPRO&6eB7jLdH8#d2{ohv+5n!MtM zsoP>rq6}1%pe3k#(9=Ks_NExe1O0_@Y~~{MM>4zhBz17#--eoiT!eKi{TQnV9&nCy z9*TLM9%)j|s!mSDKsqZhkfw~|M15=((xH#2=?4RZmCwh=aQa{g^b4IC3!OnJU#lqW z3X(+aN31_3Lu(vy9g|5ClTT5mQB|JH0k5`2w?+TqYzG)Iuk&B^gzIyR(p{{EeuBNI{a^X!O<7Lc}Dt8_r>6MxPyNC>0STG9UEVz>(Rwc7z{=zM;mpTwH0HWYr->ZP#OF%1=tZ-TUl1`F#KK|)|3QFoZ) zsFQH-wr(rxP8H6o1bKu?q%;W^eBDHh{{slq{TIq z`m|8ci9fJqt|&JL3H*XsgWb!~N^)P6vVRa9=}}9qdz^qJbspuM|&D2&}DD?Kackk`d+k5l8S9Y@JC{wVve-FL=+BO?& z6MK7Q6;32dL@XxGi3X}(8djkluD&OXI^5N+4Jk{Z(_+8_J_>FL1?rVEf_2$Eua zy(jY(dEO6qvi>3|ZB?J#xQ1S~BfHJ^ber#%9U6Afuc-AyjRvbvT}DG``fH=gkJhKIlmzQ@pP*R((f{i1s2BCWDlWOxJ0#L6ZJ7`LGRQ@8 z#q9}o>_MV$PTY4YCDT)IZxZ|2i~rCE73l*L_2DmqJ}lqKexO9?gLQw0wv9RtZJX{V zdm=pD2jy{!M}_ATv-qrJ_)(9md=e&$Ds3edvRfrAo=~V)|IXS2IIekTg!`6@IGzx2 z%5Ive@Go{*2wo zoO=UyMuGF$ljF0`2IhiWT;ke-U)FFptPD*bqbO~!wx_y4n2mU!r@9F`l-j7Gk z=gbw#_;&j}TdB+-GgoZKg4MBlyzQ*8`Y~2db9q6xnZ|>qh3GG{%?Xe0q06=DVS3d$*h@TYM$k@K343625 zq-*FWdXsF;G@7JRXKXJ*uVE+{A|}D+3;AOl*Adyh z+(Gk=>^E?sEppA36Zr_tD=cUlzBz^15Ldl=@}Us7ERCjKa@)@R?59WLUOcPeF4;KT6T^$L0^zC zEl@GObs&Uh^&%mPK*dCM6XLT31hNl=Kxc#tTvc)G^hxLdJ9ARlm*`zh>cW~RP8HWL z%yRJLOJ->`-sooEo)prAvXlOgP(Ka zo&EWe1zcj(?M2K%;9)2Kb#g|y<9_r)82-jgrG9gGJ=;QwWG9O{zCDSxi*HXl-nJ|5 zMkAGMzLtZ>ueh8;URCU56I}FEv11cgX>^*F0ys4f{rA+nQ`qZNoJQBDPtxc*QM8bq zxfoL3Q_svI@K&y)^<Y}hzQ^9kXj{lJBmDjVyBDmq#G#$GDuN3{1ZTCsXTDt;sM~P&q8hfTyRe-cWiG>kn zrQrOU_!@Q>6`Y~Oys3afkcE@T_4OBo;r*$uR^~pL%bugeHtQQ5_LH~)L2BE2Qn43+ z;qm@Dfr_6~^B%iRFBmOQ-X32oXTMUIJxQfV1nUG5#ELLKBba+~**FpATODgoDlYpa z@IJ$aJ=EZ+&OJY|GujDlIWO{n8namq`NwLDYcP(gq>T zcWC7QMLwVvcuO{h15`g2`>`+zqBlvFp9EJ-L7(NHAFHD@8GVog81*;(SO|S$ zrCR**Q|062KJLP(d9=!E}xZeVC*Xh5wzPhMWLA#sa+a}3Aa{{!_ zr9F*#bp_z@;UJG+Qv&;%iXmVSdDKZpzR6`IOV|;LXP?pU0;T%$?K@l^s6R?QB=b_5 z$meki5}(IT5T7md8RYXQeUPKD$k}1~?B-KYa_~tOEXJhZR8muOmr7mq@G3l|2&%w; zV7RwOMA47!rT}EnAizB-$l&8Z+)ny04jA&gWxyo)))Td|d7Mg-8Z}QlPe0F4V2CLQ zsx>p&QD`N3L5J_6o|~y-9qy!&^s`zaGa0sIufJq$tQ{rqOwJ~(wc7aiQt;9V}Jh{cVsp78Smm9>>Ck4n{8C$qE`7> zZA2&6@yH2aN+nC^yU$L;+VU6SsAI4$8n+=4G8`q6SAJO)(@k@R6F5b&p%@Ko3j}s> z&Q%nn;}D5))6c}wA;O7D3=fJ?_er-H8+&=5+~FSn3~QuJB*lpxcb_Q6j04#% za$l9Pj{*>G9WPC;7iRM`$gLw)5cV@r8SVkkct`JG?+5s^Iy-uwfOvj6PQ%1|;Z#Lh zQ2r0}>9|OLT;B)T7Rrgf(05#15BflqbRUxRkR5De5KwK$AIAac>*Jmu{ob3$aYsE? z3$Q{)a8IDE(EU9+4el*rWmLr7ImH{dgOyTvn9_Ef@awkqC$nS-8H(&JixJ~n?}xhp zB_Z%Dm~B;dbQ}l!)=wHaTZd368bpiW9hLrKIv|Bj4)(qcfD@x8DPhH&6t3Ay%}P!9 z&<)=n&*lkad^yE?V>^3Zh)&YjJ|K|TW5Q)b*2WIcaWD{wQ`s|o*jnM(=~n>);}RL$ zMx}(6H{I3V!;~t-8LT0i2UoG{tw_O!9xm~Lbt2Y%uLn+h4(JTj*K8TfBmk)K*Z)*s z>$~dNdo&n+61qox~$`%VFhKGq`ZBZeHH*a0APi!Lz3U} zZm6ie^eNt%+u6w=&|f=J2x#hY&ktbJJG@7|>D~!2;JY#aZKR2cxF4M2&EC#j6h{5X z1zHyOXd~s+MjW#PZNxXLyN%o{w2@n=zX3v_3wP>oj=vb|;zAp_Xa(Rv8~K{TVjG!6 z_{271708I5;(cH{dsm1?{e9C4ti~6p{%j!~Qlb9DxX*?9L+yokw~?=?6r|Qg|NqoR z?xUDLwGqd2V%<5e+{Sn+)1ZD(PW9hFq~%48QRel|sV}WuRuey6#+_I#?gRSS} z%aq5KH_IBN)ycKAYKc6$SN!v$meFvy|1V{#)eU5nu9EAMYn03+}}EiHaXsY8m9cy580pqtn)VtTcsWA zT5WH(8XIjYX)2d0z`Gk>RqPoOKdh>pj^=c{8Qh=!b2Nn6>)abqhHJR>Ij5t%>Sy8EVfRTYs`y5`O{x})~iX3am zqQkE*oW2fQi@ANJKYoYSGxI-TrP@%;cs9V;yt;W{3^9skG;Q^`yVUBta`;eG(hqcp z3d^J73zS3uhMlTWMk4Y&^e-I zQ+FHa{|l?9EI5F=smp-rS32$~{bXQdCib6dxy)~yldT$xvy|9e36&KV zblGe4S+niF8g`xTjhv)ZC*8s+54PWguL`*o%O&^3EPp9{$)Zrn)Voov?yVNKhA`I3 z7P$vB@ObsNls;j4rZLnJ*^xmoKCQB4wt)HB+Hx+L%ufTJF$#yjxR^^f68+1-WwvRS zd3C{(zT@%W`3O;h#*$(~8>vf;npQWBlnao3E%Ayb6iXy5+XL2W-EG)ElRMPz4m0I* zLz0mdH!uk{HD6*WZ>i<5iCtEQyJGmt=yMgO#Hfvw2RMXtmxA5hfmh6gxiPHl_B&x+ zkFfDWF&?%y9A}4z64P{+6aEZrF@0>Vu@tt@9h??RP&OKM~)PNA2eeIXQ@=Xg_k zN%=!IrM@Y3Dt(vbol0|lpbzPn<4vu-nU6%ajo@M6V|(vX_Ko}>LYnJ3`nA~lwcL#C zqz=sSoK3e@s`}KdToS9SRir3Vu3=XRFLxB?-nf#{No(^dSiVS^h1fAlbxkk(Rtb9~ zpyNlwa<;qWe_j|ycaln?gBe=IP2b$r(k3tC(rm!s>8 zwZ9okqggsv=_?@yzLKxJGgEOK55F`aD#Rh&RA zbiEJdB;B))aOLj&4VN~cw?SLia~gFOFs5+&guY-wwMM0?cR#=`Q2sH>@8aThMN+e} z!a8?D>;ol3`l~(~7aKOH87HnePk#ws8Z%Hc!=K_F05n(&DfyDzVEvzQm3g?)Cz#A8 z23S29WYu@J-z24RxwOX&^wVtbsSlVzWU5n|2adGZXWVzpw(r<9V#@cp4g2S?MJber zlwb$I)#p>C``QTJi#_30WqbF3!0r#iGXtIu@M@0P-al3& z?(L0z^p}EdGf`h&4|mCZM}ZF;*NcN~(`kSomQ^x1N!{dD)onb+HWHS>H7Yu~-n~Yc zm<$bpoZ)L#FbTJQ4c^4z{hE|f+9VEc$GGCcsx3;Ht(V5A|Dj+~f&gWBxm!YqNEu{# zvTkK~9?GD59?GEG*HanhWqV)yfL$jVX<@cw2`Pi^XWQR`%3vH08=oQzs4ndzYV;Z{ zrp_igw#we1g2R;6(Yc$1@mB|ZgOF**f~(Dq5N)?(;G}Ht{14b?BDRTRI-Wb$DziOz zOqXQ)Gw&nHE6fQc6>{7rH)2RaVsy(Q6%7JCzx#1PGOp#44sN)DgYN(GtEYD*j!>;> zmTFpD4be$^oR71yB#ZXtA;vr?pQ?Uv>;brW2IZP@#cv)lOj%$o3BV zfNd6ehJ<-R!WV2&-4c!ynwvi1E;J&VGvV<45B}UuzpuES@wL#_u*ZQ%zJ}+n!sw>u zp|QEE%3m{ZF=NNhq}&BsE=Mb!x}L6Y#LZpmQC*e#JniIpu5l0dZHUU#PTw;qW-{IR z!+tA+WE9bowXeD+=M`a_1Do1YQ*a2$anV0@cbi$;iZM(>)apI#UzF3Voo_DH&L5$E z@uui;b;1ATZtQ(~=;eu=a7oVN)Ob_0<42@w5m0>TH*0I9bM-}YT$AU_YE?8u&3wiM zVCU`tqip{oncKCB99&JbI+U(}!!1TB1v8WZU7j3}bo0@mL%~Kt()4myUviF9Iz~a# z#!-;8(b7}WpG@|)z0cA_Nn^#X`DmN%6b4ldM+1h*W?TC80O;1UikeYWmVL2g7m-y z1gMgYOVX*<)D<87++E9MC9k-i$|D6+tM#A5h=1;`V@Cn==ACmx`4Y21h3(=}-kg(a zNX;otP7TrL3th#d@e^#0G4aQ2<^rTHN-aoTM5(EffT$adMBgLv-G&|#guP_5j?(^G z0cj6*IcR@4H6H#k#?*LWUWr6UC*7lD&N*z@lMO0=5a_JrfL~*{j_g4A0PP(_zcrf7i6@Lp~x;FZCxNZr# zj5q(FdFp@Q@~ygqYxMt#%Q!5EOZN%kieY1qKqtgo)?x^Y@fiL3c8RzgSO36S{QS?3 zs8sZ-8-{4$Y^h>e1^z9{kH#+>N}4c3*^a9U$_UmZrA>Mi4jFU%4O1GGQunQ=Xu}cn z(7x}pS1ALn!F+y0iWRTuqqgXyj?-3pQ8e3i1kemT!PE;o28vq})<^}Ktde@GRJZV5 zcg<$i1lLiz4gP=L?We-yckaG*L_-?2+wJH|yK0s6c!y$CT`f?U2Q z(%UyFbhkKzb;+V6$+){`u*WG9JD5R>i(m!fq1&N-EiC*{HAbu0wD|CIM| z4I3#6tyA7{_(+@eDpr{eAIXXoSF2rCoCLP1JhT}oxm2N&Es$N9wG-`K)`XkPWqALN zRZ;N;%5gP0EFpmX?QZOo2>UOc9dXV;oV`Teb% zgEZpcTaw>5B)?HT<@fAU-X%5cdn$wJt2`^R4O3U*;Kd`h<%C<v-zDK}Dq6OCJp`K7Pp7IvdFmF$&s#fWckdD6|vCR?WQt5Y3JED3W2dHSG{)|BU zd^tNnMXo4qj|Ao9dx(fXN8&$A;=dngy2Up1DQ{*C`!tBABlAd`EgF^oWU%seHfu(* zU*dlB4ysWCs!?)Q9YDngQRyB4Du)-SbY7s+CH6!W`IL894SOeuisTd{$mzZ#wh_b; zt*6ZNXwV9wZX@4yzc8~=<~kaI{pp9_#nR2_TaC(TN_!JIs$e$RZ?$jKExluNs(pw>Oas3l4908(fcHIJ<#4=C$TO%`wf*(=Jxz(}D#A7noz3 zwBhzd$@)jjmq(|MaDGA_=pe}W4qj?#A0Lu} zBay;TwOmd|i)JY4wBPik;@kceS2O5>K+ufwt3lfv9F8>)FM2?pBh_qv&AC(eYRC*_ zjkf)BNusn?ns}9HiyEtgb2pW7wzY4XLk;vRJ61*Ficq)^4mTIUf6!FW$6Q|-rV1+=g5DUtt{qi`zLWZ5 zAHxBBp*OC3x0!lrUy(oXUJK|Ku1Pd5_D|yY_nK85cuYf8^ULHwZ$6*|sW#l;L=$sfeGO|q~=vq;;wQQAqDyt=M0%U{HeuHa*qWvfD$ zr7cU|$d^W%xV+Mki`qVd()j+45mI&2lA4JC%FZ5!Bw??YH<#A!B2(nJ6%PYlhgr4_ z2KwYC>ky)V_K#jzX>`F*&qtFK5dYGcY@zaxyRNl_P*BHNN8jFB&*2Cm$D2{N< zp#UZ>?}QMC$LdTW@k=g+G$aRx(xP_bGUH)Na$Be&Dp5r6H(VOJ1pbDvQxseZ1d)rQ zyBR^hERi-4#y&l)UJ2EUQQsSagHC)(bSe!BaR)SUg+;h}cNks~NKNjh7pcZC{l%DK zK5Ucb|KsosY><*=O{5h9ZT{b=^o`?o>jg47aW%T+Bg2$?moptvgOS;Z!?ZncJYMht zTQ>=#vh7ZGZ$QReNAuy)lQ0!IeAxJimF^hCV&sK--O~{@MZT^5b9c*GG-aiIhP{X3 z4|^nD*qUbq=N%_giEuEm`{eNGM(uXL?UTd4hpl?sj))OO_77)UtBM>y9|wgwJkhBi zBpLY@SA4&OJx&Gfx*W&dhudr~#GP+`W{k4Is7pT#+LPFCbj>Pgw*sZ3NhIA!D2trz z7fM6Z<2p7To;VDoZ#tX-|68c0R`;|)H)WA7$G5)!McWHa)(f3Vy>4$<~>hLC&oI(so7ivdAd=r)S0V^&C?q8=otWsex5z^oDZ=BH!$$s4fjDl}eL4HEM^zidh zQwrRPP1f1id!gkaM-s=<0_=2pf~yB_#rIhItexmRvW=yRlwimHx@YK&)==9u&pcRC zVQb15Vg;xIJ^{aZ@~LV$TSU2uQP@6riwCe2q3gihtIV>ebsniW>nWiChK$p^5)MFO^KI z%i-Tgls12h3eT)Z=j8V><@eI(-=)nbOT?4`<~$yBM)_n(sf#Z`ec29r*cD2}k|-@y z7orPYG|~I|Hr7S=Skf51W3Q)GI{J!jkB16f+hVrmbre0M`EpgIWo^Q$lwUOo8m#WO zROIkcxliErCB-Wec7+Nj*Lc<_Yo%B*7aXo=4*N=c4?KAjy^FW8Z$&(tqVKV->MfHLzep;w1iC9=75?LME{yB}?*cmE@5}(vG!W z*QPB1zQkBT@oBnRAF71LgGHD3z@{Fdfj}f-9BQ*K~Nqy~){Z#zJyEKFBnupVNBt#C^x{z^{ z_M|paGsvaSzh{R&QZvGZ8F5kq;IAgAKHEgceYQfB zWT)Z^XU=c4&T5rb7TO;12v_Hc<$YcVmdzkyR@|x%9i~jKKZFzmzXiYg1adX>#0i!5 zA;EHn8tk}Dys7rfI7}>OA3fOE8~z1fQBwOT1^ertX!bn^n&;B%R=%!~J0$D6LIje1 zQ$&&v5y>6z2|nrUsXtTvH=>yApzSWQ19t!Ih0Q{*Ph36O&EGHJucEtg{t{p?Lu7CbFJJh3291UAmK>673lyeBRcu%Y!Rnk{ITh0_B5rMYj$cWeTeHWl9-3YxIREfs~ zw6!w#|KKvUfLw3VwU>=bk_R*Aw<=1p;&G$UwwEfqxWn%*I;48N%{{wO4!nZapsBKd zQK9a3>U%8SJ$(W^A>edCEp_pG?Astz+xv&wtgD5l>iA%b;!2l9;(j3r5&~t<6WQ(K zrL6r=Fdd`f-A{Da-OTsci6F!_YZ>Zo{h?xbc{}lJWSe`ks-Nm_>=^;jbSXP10=iQh z55>Dj2%sSCN$;`7JCN1@(rbql=k7o%C&vjxkfypn(PT`HcWb(7dgMJ;a|aT7LEAqL z(ZL_Km14<8mgW~!Ma+D@sM|PWRj`Mp>@&fe^d8$N!f2J-jE6KAF+-`yshUYTv~kPy zsc>OO&~e~UL4HL3Uk()vqRo9$z=Ior$327H?5uvY+CfBp+^AH%30!J5#?7&%>I3)#vvbT*(z*lT2O5DXvPi+ftGS zgFBHm<0HqDBMyxkSxbG2?_d3xo`7Re_|w52ovSf29t-rr9`)Vv-rWv1M5Llsk#^}| zoAvS)nazKYV65EYbhGJHP4QSiZ*UW%ZO0cVrzUa5JF$2`v_5pa$@&W&M94_Rz)6x~ zQ=bZBF#)bm9uy_26uCAL*Y7%5?*Kk7zXkZ}uPC&R3rT}*Ul3BqOIrlMj{A?>dE7Kf zNOXVi=d`huZbF2USx1kfmbm6$kqcRTfA816>PMlZZYW6FL*6UJF+`Gqk5UhWZ`c8` zCf*lm@=3&6o>63AJwMW{>PU;OQ>ziYBK3GS7;*?!O8 zx>9VasJxEJsGH%7TcQ=;ch0q#Z8LG|1%r;LjBh$w8^KI|;~+JpiSp9?pdRaP#$j+S z)B)-7=<(iZ4%RFZ&?@7LA(TTPV52~Qaf{Q!Zmt)I-}W(=fkB)l^zsVTx(dG6+G@>O zql^7YsoNgrJP$}ra*4M2Bq$iXGg8~UAAwH_xMB>znvWn{;a)M5@kR5BT-wMAc?e>( z8PszW961TEv*&mADLAqpf#2pB3yvH?0Qpy!&eUqOnUw3kXYd1iDv_|wqA>5+r+>w{ z!^cyzfi!gv_Lj&DS>$F|mAMBMJG!*ZTLR>4?$kC{XTZ;=;K&wW`D6~~+0a>Vq`LT` z-_G~JNP;yIM^}__+H)QCwYPWIk9DVs_3Svl8}qNrt?XsO0OcMDR^k4a!nq7u#>w2r{S+{PYCM$)E3P2$YZBi@EdwQ05PElMB`U+Jz7g!miMQv>@NY{ zWVVMK@&+#&3^hYtT=NX&W-OkOW^>TX8)z!>;=lWy4E99y zi}vl`%`ypQnv66>rc&JD$k-k?Oi5#3(iV4pmiPEp_Go}1F8lU@Hfz@vdD&oFUx6XR zmdd*4H}cv>%b2oScemhuH!z@TEW(Od~9yY`GaZgPgp5>jmmF*E>;1oyNvIDIO*zL_!5k{>@lkr}x zc6HZ_aRx{{#)4^5fSw? zEeaRL97heDM^Acw$@HqWvK0XsGFv`iB;gjadF-dA}5v8Yj>8f1CuqhjjwBL)obh3(ILbo z&f4#7we>kb6L`C)%&z-+H>UG7c3pU2HlO0QSIw-kJ03WsZwqs`G|`QKISGRe34_?H zV*idIFK&dn=tZm*Kmg&kNFg;fk0v=D87*I0JLE#mvAX~l0? z2^A1JOr$bESQ&{%H!IUyRKKq>{CW!pK@}h_k}9z6AeLx6R@wHM`Lm^y^ zM0!WKv@*IGFd?D6O*-^xyLFB_MaG-~d~XxJ{}J)sP54GsF?$eST1O?}d$Y;-rGO7* z%a?|mCyr8aMfP<9W@Zu5N~#iAxqufZ>jHRt@;CxKmIZMB6F7BqMVzmg!|G{GqT&gPIS{T4Ji}=eIaeYm={;;$8L0o$~W)ZGgO~&K@ z8&_U8uG~N4+6z}JXB1uNB_Np-_v`TC11q@|H-?*s)3I&nt+TYTH*PT(t|BZgMFh(x zoZ^}q)d`%3e;bjmj*>~HB((daxRBjoRjQwsGDCpwhng@acRV8!UPpwt+S%wJ;p;m_ z65$Uu`I-d6(-TyW)XLaUA{2G~V{^Ur@8zq)cUD;cXGiI(#xUAI+NwF(s$oeYSCVLY zm=zSXR?q4T-#M`qZv=>UQh~e`*PKy}QuZ(*o)dQ!SB{uD@n|}j zs?4-*)J!Dz+KqIHnD8ll;LrsAvzQ({-+`47gth&Z|9}-?-@d;mFxoHPm?_@p7}68g zqR_gVZkQ)_lVVp~gSCiay#sLZ{yTR*yfqw!>*uE6cbA!dh{9cY^pl96`nhy>>#k<} zR5sf_-U~($+!*-I{2=%(Am2q$ScglfL9*pv6DoJf|7@$DX&sg3KIa~(pGqDuGgJlJ zU1Q54yx)U^a-c7FXGoylnu%uAn_f$U`9x{S)!+O7l0wVO zX1sLzP`nwT$`NZfPpLQ(ves-W+S;VM>Q|8lSC44z-Pl`{y`7-YAw0k~o{(shB);Pk z?R-2Yu5J3aDoj(l>dw6b@F2zNcdJaheb4$^65k3XcC?pE@DO16bbf)G8i4MYBvCo1 zNuk}ogEl&KM55duEQa9X?lqm1hUzCXir1mXQo@ct>n^ZXg!p&sxK zcEhU=z+=6eH2h49s;S5n!I!u<#6DYs*8yTfEExU%1D_M|nNcjUD4O`I_#A`J;zuMF zX%oK&pZAvxDX@ez@vkAQEg7P<_}zRF!fH#G8{}+=?QQ9F^UK}vNmTVJ8bY_sS#G)c zmyvRSXUUMe z_*anYqL8cj66R!_#ps~Mq)YT=66y|@likNY_rkW!MJ(9iiL~ zzh#e`f4duK@dtiOgPTvI#Nuj!hL7F+dLgevpkb$*w{&OsSpAmmZvG7+{T)C5Ah5KF zG;9J5HEw2}K zBV{7GRsq@~%X&BOqfgH(_)N6CiHGLtQ?bHtsc`e>h43=JWv!b(*9{t1$6LzX{AWVB zLV=&vZvOP0^cVb=VmIF=q|fv7Ctap<$3%YSfFI`Oe-Yr$LebHdf4cdf=`-W6_>8o? z?B;*$hLkZK`Iox+?}YqQ{rooopn$86FSzxp|iWYm7kGOsFJ%W~2&q&2aO3y8&c82)d@b z`A>xW$$ow(5W7XXh7ny;-25RSEeVAqEZGD)SThQp-}0oJ-zTJN{d^XT}wL>MXpQ&lB?h&}A9m=BEhZ@465$o!cqW|4o-A&dpB{ zU|c|<-h37k>x4wl7hRTUH~%Chdd_w66OpoAr0-0Zr4L#(rDU|BSgIx5&5sk{AIE1O z{&D2}P|SN2yvW_WL%?yki?<=kCL|Rfz%seT?}9)9NZ~@s2+M63-?v*tialMHn=U>| zNN?!kBT@EUvFt}w_L_@-O-QQ~WCRWRIS^C)0iZo%`O(FPQU;F`pLg+6lz&Gozm>{= z=i((od0Us|TNl5y8?k3|m*s+sFBHmd6bSpu#sB?I`kP&r&t0PMJ(ezhHUMuF`F$Py zo^gp4p(sPqR7;zSpGJ@}O7Pj6pNhj}g)rcJZkI`=&_40?@F>#m7;SV!j|7gT(Tw3m0f0rq~Ez zi!2|x_(Y*>K0XIq>RtRGA^%^xEIVAhRtP`ag@EbYI#Fg*NoF-JK2}JZA~2&yvPDSt zJk@2Xa`8z5%!yqVn~VQ>e~{SXOe*`Xi~nUm(ewn$j<&q*;(wsej5K^kS}I-qx7`IY zMuUg9T>M2Le`*)svEOuVjY!8s1nUhK|Fw{&7wGr`$>ky)!@4YH7ylV0dXl;<#V-DA zH`wAqRCbk%KQEL`2BbOSAx5M4$WzgH;R z*-7##aPc40XU0u@Mq2*r;&*fx$hh7qu{`JEKNRx++R4`f(JE0sS2_VJ*Tugtq+Leg z-uyNsn?yQ%otCFuyn_-w7dtJJTzpkG*y68Hmd32BTp0IY&@;4})H8M*4Q$3pR6p>! z_)0AqG9OMigO(2e&Ya9Y?;4ac_`A%${7l!N*n43I=zg{d{lN1}9yO#}5F6&oWy4qi+V9iZH$@ z9w-?-kMA$QIR?A_*wCAz*5goZ$&l!wM|g#hcF@o30H6)GK8?=eBZRp96i0X){k#T= zCq1|xc67DaFcmb!xo32#XtRnx^7Ho!dF%aX%AOtm(Vy{hf};4)&xcS7UOeHu_M4gy zW>onrtn+yv)oJm2fbw{SwLsKyoH>TD_}})=Imdan_|XB8E8pPe?6OybO5ItIeld=|yiTyIPD%LiR)Zp=FbEB3fR{|9=0>f`}h8`u~E7 znlaNq`HaT>&3^TGiTmRI^GYm14< zjk21mN1pM;aMMQ^4tBzc=9K@u!rivNe3aW8fT4@#Jm1mX*Fd7pmpFR$W3Brq$K5iA zsqTq4tCU66tE@$fr6H0ZEAfh|r1Vs7EXUt^ce3`DMl#_gNnax;sr&8Sp|PdP9q(({ z>GwbaN{{7*V{XJBzk9!Mo`db$k2G4qK@$MJNmBK-KINv&g4}eh^y$V+((3o)<+PNj z47o&-wgX{BiZ%mp21(Lt@ez`uZwyhz8DEGqB-|7FZ68ClV#Bv#_5?NCMsN-56uIAq zXpwYLh^(SWJ5Mis>gL56tde4lgyvP?Qiqo~6I7Fw<<5px!Xrj2Zdfy`Owo!Zb9*&B zUW*5usBFWj&GDuK=_T=Wpo*=fC|uulsy-pA{1~3zwn|tT#c5|nA#5Ba(LSxXgMF2N z9kdAnW2(d%0zw=!~P{y62u zT~J8oD2JQ54;l)Zzfs*YGiwzVOj>4}nlBe+tty{AjZ-N-4@)H4XvIX?SnO3jtx$}B zs3FzCv-@O5kF9ZKo+?i_Bt%eXXTvJ%QSzaMC@#n(1<{H*N>wJ;@7%bQC)kAj7;d7L71MZd`D6!A#+i+?cr~caw!-4d1@i=Y99uZ`! ztUiSuM-Y*rK`FfID4F}XTJc+^bn@hwc~Vu_%yd`DTOsDBOA|8i_)cC`!>aQ#98lvf z$R*#!-PMEs6}O5xNzEMS{&z8Fiw}!}g`SxbWGyMUr>(!Fe37}J1-DZe6C~{J{UxMp z#n3g@7h&icisK^+A6O}tn${`QOK+pr?A8$76LBVJ)}=lsDBUGNzm!BgJ;!VO){nGd z%0Hz_h58q?&zKgsR%quf7kTOaI&Qy{GkW3`ctc0RuJ04I09PiXS%6md3&p^pLcJPu za7AvS#1V^om-gWdWMVY63w9Y{1s;18X#E}^A&DcXUHC@D>9zNSe$z)ErP%OIm|d%8 z9Ry2Xp(uY^UlB?ORqNO}ic3|lxQ5H)iDG01KgfKR5(4ez;ke-m8bME0;k&q9QMJ%*gnU4~sby9!k0_NM2U=QR{zC-x07l~&+ishu6)S6WX$g6kW#{koQY5cj7z z5(Reg{hwGH2y5V8=KoBq?~xXM;FpP!N7Hct=qMd%tL5qsE@q!lhGELhaiCI}8h5Ow zTHADRF0&F@gpk_VwtYCuVEjFJ4#8HlkG)OB0_nE*_OZ&I-}ZfMUC-}#_p!A-zHw7? zNn%E&k!1lvb%~g7IH|CypVj z%HYq8tL}!@TcdQB@n*O0X28EyR5QoDagKlcH8~o7**+(iGQTAnj}B$R)|^X9S7js) zG{j=_8Hg3c(HRp3lxjz*Eo$vMNz@P!Tf_^O+L}beO|6O- z@HP#!P1WKI*r~O)PSF->op#3BPA^U?|KC0V)Xwzn%=>@8@A-Tl4=4NVz4qE`ueJ8t zYwZip9pO`h$tJy2GqfJ*uGxexsb^C!Yt6wpf|QX{eCI;BnlG~!BarkMG70O5a0 z)F%VYxaK+c^v%A{1bFPLQKQ3B$=(GW)%TYHYX+%n|D4}@wxU}Jji(9;vwmN&B^>P& z`1pkQkq%JcA0y_{kIK5j>71v0*H22&D?xupcr?49TW71}0AUz23;ARwu^)o;ORiW?5p~ z?^9dJqqA&*jGe%;xHL;rFd7Ff-zg}e1c0CS3m77;e%={Dl26cQ$Tii1d|Ih2okN$O zW0`4tlu^Atva)2Mx$A!P`=19)0lzQWEXfqcE|UoFOs0SAGTl5;v{{rXj9nfo%p5s3 zuz#dEgsJJ5x+7KAZ%&|&g(2s*N}xMK$kGe!0wS@95zpd7btKN^DXy!7KA%_PtuuM% zi;9zKO~++Cz+4{50}Mtxfccn!8EnCgvDvv6jczH{9n9>sL!Uk?!9|$Q1}(%Lxuy4S zKNAQ`;qR-FUlTG*yp;FGhOBxD@K%cNfcVxeh*5tZYvd&Ujk2V_-yok((AW+kJ`>=< z?O#N9=_r^dQ(hzc0YDIXc$`Q6VxtffxX%2Tc1<6m1fx z1TzxKv=6UC3Nl^=9Cx&T-#4HN@>I6)Y+ZXI}W3@>O|QX1**3mT4*U$ zn`bcg1z@7qO@Uk2-#jA%@1yWplft@*pa~FXIYsW(@2sWAnv}S^-TIHpvU)EDoTmNV z4z+uJh;0V5LA-4X%AXMl67JBPn!cbBh&|T+6 zj{0pyno0EyjqM5Z7J7>>&a3mj8&HQ5!$bBj2pZUp{}w-r(eM2LXZ!JJ>)mLpd0a^Q zntAi)Sqc@v1OX0)*a}0)_^@*%sBTp4)b_XP0#q-Es$Wg72_K0Z$ZJg{K!G%Nnav+j zJ&JMf{lHnLsGh+9iERyp^idNHK`kJYU3CwmK>It8=X5)&hiJ zevJfXL@2-$Jb~NvIk+b3)L52bVg`HO^Irq#`N~x!zgdMVNMTaoEEIuug|bND3|3+o z4T4b&gYmpJ7I*KG%aw?Ac#A4QBnP32W~wgiL~H=cvU1So$S6<;V|Ekc6;_)^PyYm-XPeeG*d;@Mz)mVs1#?jw>dJm{(H>Ev$Mt9JbQv3#6{1{uP8uE z5v_O33Mdz)jPj%XphUIiW~Yj=xv4ABO-xxOZBL{=K0uDLDcNZ$#m`8p-Ecm4UyHX{ zd2NsKLW@J`{6NxjF}$gJ8JE`LEPb+ZancE5T>q9dyep0nwcT)1+00p2JcAUsifP^T zyL~N+R;OiOi`t#bT;X5|Py};-na^bJpKdqc*9-yy^wIDA-IOGSHkV;vO{JOHz+3r= zVP$T`UoEBHPnea_p9LiRb$%0bX5R3PWSE z_&XBw^LD=IA5LcQP0uv?qOF_bTQg(AbrE{Mmry zqx7=Z>$*$NcE4l00#f7PExLCk7CL~^_DNJzCF2yQ-xbXhl7MTLO+>@P$(E{7gjAX}=-R|V7?3)CT@5i-Au1Z?^7w;#L7`Agq z{@tF3Ch#g|dO!l^X1UDjFNW?^u~KhZQxm65vY}O>sX82mpschx%z~Kr%^a{^I##w- zvQV%?tRLW=S?ISztncH8Xd%YX5H>7o#aLUkAm}Q={*P8!&m9A{Sa8#!2HShi4Diuo z-#<+(0_1=?MUFp^98Ea@5Vd?PgSprpU*8~K-5fS3hD4RXUcmM2-YGLXv!ib5sUsxI zklj##d(ZuLgY^JOx5eFsX;1D+k=q$*{YN`en!o6}dPdgs<}p{-vB@u3-z2%(zW{Fx z84!@~TH5M6R<$?LQr=ooZm}dnH@IFs=4`j^tMhJMM7cVS8K4hZ>RjGqEe<>`y?)Hu z>~`+bd5ILuqmycDoc=xrto9FNQqi+B1W3JuD_8qrX9NTwR=iKA&eUVgkw56&> zXKfO4Zq`}q+&y^TtgEW`x^^6Mmb)#@UZ+b}(PDKS!)7l_xx1=b*IVbdJg+fGVy0pq&o z+o~76?1YbB5v^Ww^kVOE<>_O+Z{p!SrffOJMjW0XV%gJJnV491Jkb5}A6guk);PT( z?{DjP!s$Kj_V(#||De3u)%z)VbSc?;-l)H~W1;Zr zNBNVsrp*<{Sm}`&?@p~tLU21^Xg*eaEKbAIqUo7xjbbp(a|qW5lQo5Xm$8+3f~;w_ zv2k(B%7EkLbJEn)yH1H;c(<(eR@JszewkQbi)K!#^Ln$CRl;c(Qykt*8lN+R%jv4k-b}tO*tyf& z%uha8wlgTat}dAPIce&ggOUqBCYR&%lgWoNJVD`@4C`MLKCZ>pvgF6)tl&&O;;9VJ zBeP1vXXcm8GKbIXe~QN3fR*T9yOM9t;%DJH?(mtt3zYfV;-r*-ym2)1Gb&-GH(wh* zF?xqMRc{zir5IUpcv2Q-mRTkC80y@?pjjny0i1m1)+maAE66v`(&}FeMY1-F&)pK3 zsh6j3O{cQ-n82|Iv-FupbK~Rk$MTKE>G1MQK{vm>v1fbL<~zDMUzuHJA^kkMW^-iD z>FwE%wPxpWmUZ6VS;~r;y#>nRnckVo?8m$;x+Z(Unz@YLwRjpZ$<)J7FfB(>tFIY0i)m)jix>KF5 zfd5Yqui~p2u>rADaZBC|IQVi_9nE(eotA8edX^INg!~z^M9l%sQ@0&Ly8vhl0aSSv zpa8<5V1v~THL~%e7_|Tm$iUCkL{C1@cp!T6o7VLtf3qkh*daOsxye+f29utmVc>ve zo2!TiSI{6>Lqd?M=2bYuqC)fyvCRwabwJ}GuRyuIp|iO$s4 zi1P`PxG7V;y%W*xy;I2}Nts+1t!-SKR_APPt$HZY*~lfdcze^7Ej#c?Yw>G*tuvx( z&88G>|EQ7dD-m5i3ELz7tZjUpQBytXN7<1Pr}xKik9hxj;}XODI8s-ho~17oXV)6P z)yuQ8j8mK;8(5s;-o3VU3PzxHoU_T>TcK=m^fvh)8%8#6=F{rBfdJ5KI>je8y8!8L&~CRt6fWS zpA&P>|2UDVT~Cy4iMBN^u2`oAI(rG7oS>6i85f5~3!#%* zX^Tpe&f_hn$|L4zN>w;!;S6`<;^LN*YZi`SLoJn48@G;K_;LH!=gYeLz55EiMQ-&H z>Bl_He$GDJ}b~`EDwaSkDm>01@R|2c5 zmRA+4RrgU<#fq)*3X3lxI)X$rx?k|BqN#l^dYc03`TCaHq-LD3QYSj4>^qFp_-aKb z2~(;QO#D=GxxB!liuP-$^??I!zZD%%o*p2%$y zTN7T&ekIZ3X?1$IeMh~$yOc*;dXM@aZr2lSTQseWi;oIGk*$o|2cYdr%X$l*`y7@b zuuHO=HZ?Yj>z>4{&ROm)f3i>|p7k}FV~mk{)Y5F;l5_77{cB>3Xp7d&er(Eb6SIvR z4$12-P-f?PF{1g$(Pw7n&lkoHoTqEbU$)4)a^r9Wqvj^A47=Q+INOnM-6_Scm_76- z%6CQIa(y1DU{|{+auKk$u^vVs)o_Pk_H{l-pm{N@14N zT#hoSqD1kLW@ReJj!J7uO=QP4o5t!=b1F+x%w0*a*q*!TFsJY&rV{nsO>1Yn5K{rm zP*yMAhkXnu@I&z5MIeB;FZ72_hBks8w)=M|;NR$z4A`8q)z@*q&JXjwO!74?!}TIj zyF>lH=ba?!1l>1ukPGGn?E#e!z~wP8Kt@=rn`H3kts}3E)wF#gZfQLJR%Q|#ki0p} z9z>-bYnQ{y%fy;~>sA4vI9ERKNRp$vJ*Du&UZhr$)Rby~ejL9xkYDXa;k`n5uNL0R zg!jwB`&!}sx@tY~d|NH#7fwwQK0WLI@xb7>mHwY;!ndc%4=o>Di9jPim|+=7YdrKp!ri|GSvPY}%k(fip326V;xicceA+#qK?3`*-;VJG!E3gA{IDHG}ha_XE<4moTd#nSy@D)?4o>c#Q2kD_8n z0eitFB2FUWBN{~zIYC2+tdEdaL4Hh>bp-hVPa$G)C5XkvP<_9-xh$x?6tOrJ;qhgG zcxN=x24egy6XS2lOFi^9TaEetvZ|uy-r;TPY4pg@!jWe1hMt&?pGJ$>a!QkSX1REjT z^kk_0Ucz`KP779H+l)*}G?SZ0_AB>IzqydhQ_0Q2%(B0km%=e_WC}y1_&E=@sFBeJ zGl)~^*){n_Y}DPB6u3anBJ4XamCrZB;>+jB2$0GP;Tky!_9ITYht%tjba);Q)jP?A zWA&FLtd28sMP^fEi7D!gagvALzT90rF`>Tt+bh)BK`{yc3?;AN*p z>vZ;BvLy6aBJRTZ*mOBpo^#n0e%2Ir-4s!^Wf9KtFxn#TF7 zvaTig;BdxGF!$HT%2wa-4OJK`iX|0}63N%#q#jZL(gba_rWH;mYDJNo9(DI%OsKh@9I zR+06K%>*^Bx=nB5^-d=#7Mo~uTBdOptA!j&;gs4;;W!{;7?^b$Y2{fi>@pXAor}PA zDWZZr8W`dpZ9h?fpUqA7jDylxMZ%lJ0Dh~y%7OCM(l)fed?N(D$7Tw~9nLv{sJ6n2 z+T!pCV;weAM68=0PM%xHsh zg&G?4T?tqI5WPE!DO-3KAP_G&0q_t3Kngt2hE#tUe~`hNN~qPJnM1iowLu}`$>dY6 z;5K_6c`~__OW9_Bj69hcluOZO&mm7Hn{wUNX8#3wGLKTOk!|)z$dj2yxkj|vGs%<5 zpj>R5{bBM%oXaI|v+K!|(NQj0oBbj3WPVP$g4*np$&-12a!K3lspQF|P_Do>`~Bp} zOrl&&n>~pp3L2p>*is5IC)}RbA5l<9!j3f7|QkCVOXAcG9i@fABXM1 z&9V(S@2{=Qm$_g+gb92%fR)`VOT17GU&~(58D~?geLD|&(H;)v0W{$zaKUT zu@Bev!-Pu&p0Lh245ng7Ac{Sr@8YdEk~Q~MI!-$q&VPbdTS9QicY zCbk-3Cg$kXDC5Xf&9dIh2EV@oQyq9sVRF~k=p$OJo8-?-oCT7m!@ z%)=W4vy)7w!!0}JrL{VrPfSZo&~la}Z#Qwxc@xsSay};Vonb%WT%2?cdjWQ&l!&$S zw7PX18H$?_bM}lM@kmlvMU$ksrCCD8-f+GtAa~mL{+N%Ig4U&(O(2Eoz+u)=yb{+z zSQ-`EDlqnkm#);U(3$oet`4N?w^j#HW#xbItrNxPm5!x-7LhGLq&okbl9R{S@TI%f zJS*!uT77bV)iZ0ll8+|Al(z-YW^>f1!wb3}8JN#56~IRuj74CR|Hno~+%14RJp`wKOP05jYtnp1 zR9@m-IGFx3q(6>#NN9<3F_dmy<*fGT&F?TuRCoz_gxrhR;nr*-@C~7^p!d`hBo;w#KBAsRXtEJ>h4Q&f^o>Kb~1n75wIVmR=!?`px&TGC~}7_apoJ z9uw6n9lB=G1lbZfZs4}Nsi?W%J7l>UjY9;NOH3&3dyy&{OB=RfIm{lBXZ6j-5W*v# z+23oFD`l~|i0g*UceW^Ls6~e7sb;aE5)EQ&A@z2LzY7l?GQ5lHyIF}i7k~j9Y5DS%x`;p~ zoLZI*#%PVfK|@WtzMX=61wp<_5p-WRZIFec4a)|abr?o(qj7=CPyaE+(`Pkm?scDD zU|{a}9y0iS8i_wan$d?DOL;Xjf|4h#(8bRJ0~n-Wh4_4TAXQT@S)n8IxbT`fT}hy* zY|P&nv7hm4YNceT2q#*frC~!<2T-}6)Tf+SU=DZ&9EvbZfJ;-C#5crAYoj?-BiTX1fwE(%Mx}R{(d*wu{@%` zELxnOj*)#9>GIhv5)SD%-p}<1SBj77JqtvMY@SO z)SIkKE1booEJwNQB=4-pQYQ+<*Pu8%+PQ8`S7?7sKzLH>#E4fDctyn2;jp@H98WjSt43cg(CGYgpqfc`ky~#3W(`eF9p*GX#aPuN7!i!yQ<{e@e#9Doy;i?c$ zTH;JF)6{h*RjQ&5TB?K=&2f~Ck=mRq%Epk(fvu{I;1aK1d8XM5lgL0*#g{CsX8 zTgz4&h|QE2uekEL;DNqYr^M>P&tW9>#wb^nTq{);mxi~5&uq5hh#Fo-nLE3S+W&|- z&vjFb_1nkMXV zGFhAvd_~{+N7kNkm}a7=Ow8vLdef*4SU|^jc86!GN|QCSi0XwOitT8{jyN*^g^!^g#1`2bNya8Uf8Sb0XZf-sgGBODt(Uid^0LYzHWjP62B z)pew%jNmV-(fCSMTr+tpvYfQCFWSkRk>HzkEnOLxu79n2IQ!+!?vGD==*2pa4IN_+ z)=Z!?c8eZmE7wn%G7na2s+ASzs^r%k>dH{YOy`CLP5&q(I4dSHX!_=i;1GivaP`hb zKvM5~A|p6~iNIJZKj`jIN9~lW(irgje(6D;+&-p`wf4E?UDHrAgdykhzWg5hqx60e z$z0%)V=XQ+YiTOp7PINz{ofye3l?8IZh7HsMM@5LZr?(kz@m#fd=JsCuMR{NvG!P0 zkF5~ZoH`YqJi2Tw&7{%xhrpjar%*?FaY$*GWG>V6c?4~xh#qL}IMTyzQb$iM+VP;a z^J|#=i60%PNIDsCew6i{1IU0gdjf5yD54O)B)@H3R5cfQMs`uYM~YKVtk+1Q0OgCK z9kgK_GK6%68p7#@(Ag11Wib(^?gQPJn-rexIN#I6w5zNgLnWACIzA7lkexUn)$fuAspmm(cdYI(ZBFlVlccHS_e3Ch@^8FRTV$`yDN3!N#@NHAalq_#| zq*8bIKzmPI`bqKm5bK+y9MeBQ%BrKiX?%Y$ZAhWi2@|(%TDJQ@H?%*)fi#mq3q=iv8SiDSq6>Rr^wR@*DNdXx07He^rwIdfeMsWMmFM$m>naA+lXas5ZHhO ztpboMu^+3r+7wKRCKjOvc1)mIE&wt5+k@7uPs+93tju(q*jCdB9!Jvg;uSqw=i^Hw zJs7DPf+vy+)G#eNCQpso5x6n;KwXbL5a(G?!BePluBE&pwg+<9)G?IeL`p};>5V<7 zHI-;5zmDQ2olPb+k{k7dHB128^mCsvoY~iJg%o$1F zLAl#BN)`5zmQ&ccV3tPg*~o>kqb%A@>Vg^B`U5zh#B2)Ho5n1mwI&YWnl81E^}HZ& z{#-Tf68iVemXlN8JTRUttFcxu0}7cPvwE5?UGGp_#%;vnixRy_VKgcGMzGDxRB?Z9 zy2OU}b;_6E4DE|6OhztL&y88S&bu|4nwGIaQq2XY=wM%_m9qCtE|8a2@1`%CuqejM zN@^P8mgajSzJ-C17dw(%!18OC(KMEo7{rK1?Yhj4#dCy@2Opxaf1e4Q$Iw3nr2EJqR4LDowEiwo~hzqvL zA-?6y`Z2n4Aq}B%u&{6uJ5H#>f&q+;DAINb^S4CHC8m2_r6J)V*wMkWQL4NME z_%f%;?3nU%b?(%M5?40P>d6bB4L7L79eZ_uY|TzmBu}L6Utt+b*OOXa+&_*sd;uGB zBz)*>gy&893Apz9-j-uwq2V(evP!M!$vt|PuBg$*siq_=RPEx7QM6BuHQd3Dj@4go zt4rLlcjGZEKT;&8j!c+%sM`5BBJJdCm~>Z0?VLhsVB*2nv}DzX9f@o9va*CyMdF{P zHg}{Yr}h}$rml4KCaJ%hfuojFdUVh1-8N}iV{5`=io6(k;?CB*9kdT~{QLo|zSRA> zY4tbLl5`I@mUm2Q8 zb#oxB()v;>IE@IW{V?U3fBTnu@})zvu}8d-e79~PzjLRJH>3^B^RI%vChz#Zs^@Gy zj{KY}w?0q4_lGUha;nNFbzfKIENF#CDy5@qQ({D8a+%ByvzjU#MXPE~>;6`iv%K}v zU#mXiA1lPk!qj@d=6bcLd0#kE=eP)w(rIbY|qmyM$yTN&Zasy*!cn^<@*E z@KA!*G)~X)Ms6Gy;B>rK)id2+K9gkBb1IUz>raVa&UKr@5*|SAa2O?cZ#9MMxiBLa zZjIb)Nmf`NYb}FH<)POVWEH>Z9&37Qwdvl9vhtqV9!JNCpgq#drf8T#VB>02WWgJ9 zVF)6qP9RlbOag0P3_2sVK0B_W0Fmx?9gh7`eNb1-Gtx zDk0D7=K??Uf+M#Y_tky??c@|^!6KsxSTqFnl7?mWi%NcW69 zan~S<3X>up6 zwgh%u-{12#?)@Mm{&(a{Ls$p|5dS(p+28Z&e(M(`ach`FFt>jxZ~wgMlINFlq8*}y zKeXo;asv)A&~m}M59oULV_BQ&UZd$AH^;VeBRaWz&T{u!j}ah3pS7s->Lo;MAE<6hw9G+TohyP=Y4}8Ncwe>Zr!;5OaNwEbYJ{% z8N?y~a;r?ictL6(l=HIwg22@svzsTY}t)@)Qtcg#bBFM=6B5^Mg`&_ zCO}(vk*vC={n(kS5Z%ZQxzQ|AP|rNANYO2QF2}wfdlht{$;5XfNt(_~a7VMEH~~d0<|Q~*4&qouaEyBB4jiL? zeg}>=0mt9{3mh|^%b6?S__zCqaeVrpa4a6ear-~x7^{pPeOJ6bPb*szuz$jScy=)U z@lKWJ{_k)K6OSn3WUMM?s%a|Reze&wqn1dQwkWU$H2z#sova$O%&_q`JM?V);i5V* zevh{;z8=yhRT&05ZQJrnxjY4n7vaMA`&X>TeqkW@sjt{|_VdEtL!ICc6N2rZ4NmY0KmLNy z(7*pA>eJiOGjFAPt`4PJDcAXZ&q`twvqWC!a zZ>tPbaQ5EukpdkL*hY6vwBYMZz$ap_vdtOSo(b8MJYBdKslEyztvga+YEfqW1E9N&DyyKzngQu(Xp}( z^ouw`i;Xzz1G(S%xp>J4A48q>y%K>RQnT+A=A8I<*0-G`n9aWC(uO%(Z-{FinBG<= zZpw*COFy5(j#TeSsCqA2uW+kx6x}!Bin6N4rheIWk*%*+H`>%O?v{qQ z*m>I%i_%2wh`2??FHVr{Vg3EI`i7bG6OLIZlqs$x#`7tn*pV+vCWzlZfMIRhUFz(4 zEJ82Y`ZZPa=Hcp~t=>%0ww;wSd5S}nDkc1yTk|)zkvTDtJLYd-;9~<11_`=`h=kC8 z9}g-G(tm&6i}w5V3@!upDa1Jg`R6G^-F+#@mEFb?w!oQG3$$>(6|!k2iSXu7=((pkMm2wc*p9S z*-u<%*+ru4o1J^uh^_3HvxM#6`Tgj#r(SvIe&+0{QwGS@*;Cu!xj*qN_03DAO4zMhx?!Xp)TUX8lqd&wZTL}pDIG1!F^Z$osIrDTE(sO zs9`w)--qps)n$n`1hD0gN(t9%TAoI{X#-tVs>p4-8GpXa&-G&lxxT&Z7T1GzgzN3; zgzIh253rFRPN@(QiUHS0NXGNozf5!;n6$rj61IWiZawP=fvLGi<>!^IJ(KqDo^(cH zy_+z8bK(%2uZHqv!v|SivSb9AF8+7Cp6BQFiPJOc#8RTL;0=5{EOxC z6E%`rZk9BA(xy6*;Sz;gD{*u6XiGl1TetFcZYn)$4bI?d?s1_ z?L|qx(hfe`6VUO_UYm>xPpde7T~4(tyWdnE?b`p=-qXi!9E0>6QSR>%oMP(@$KgaQZVOE;dtk)$H`_uZTb&ErYU6xIk-WSO(U*3- ztip@oFkY6mg-RJHE7pNtjhFg}jom5}*_?~ZUXaBHBwSRFPh)lymzd?E+Sg3kPQGHX zET6mzi%!eZqFsCUB6M3(A*=_;SVH^Wqw}!O8#N)Q zS$ZTC<)5bhsamlngrWacodtt+E&@vbekqB+lS(^OfUINo7fKs&@ukywgy@|z}Udnj5}VJDOc z=dnoH5m+b<>BNc``JTXcb3QRy=RF0TaK&^!$03rxROOgqkPCT22lLeFgj~5pxoUOt zzxFJatftPK%XD?BJ8#A`)QS3J^r3O)Ts++bhD#0~Z{Q1bUPs313tpe~>6 z22%@u&)+Mr{^i{$}0gnEX* z#w*qv^CR=(!4`HA6%#M|L}0}Ft%MZ?c4`hfGS)EWKgf&yKaboA^WEEEg1uvdNMkq9 z5tZ?eu6{oLISftV4vnacrwwln%u1}~$|cY?-LcA4XObMJs5XMmlAS3|^r{Z|9*!+5oi9WWFQ+|Mz*X{VdpsWmm| zO>U!Uo7wbYiRq=jVp$X1^9QWQ{|kL@;~Lziy?iL6?NF4O@6%*{T8$#p<h?cq)PdD9`@^fE54GKf%5bG6B><=liU zNqhiRspmW2%jF03oO{YRuPRt#CLHYavbxY>M$G|KSFkTrjy7O> z&)YOBUj2<;Zw#N$bK7mE9o44Yk0Oxd62zG*F*UPdGuIq$H#1n}-fT8C&1va*;$^lr zJ}Z8gFqD~1yF0lib60QCQJVR=v=#qsX-KPCWEHEOG zHglNc?op|3sIGo%mQgDEx8|Lc{UBw2n=WCDxi#O!2m185tkrOULj7!Q2}7CRKrDu4 z#$%e<%*w(Sy(lG{Nt#OD=)Ts1bqdH4UTAxxyX?rv@tFu(`ML+<0~;G@U#Prq zwn)2?&jZ_*DsgA7dg`ouPN#d`O40SVMW0E+E}nkUm8D6B>!DTx^iYPD&^@tL-wu#%`+TU0&# zK-N0b*$_*wY5v!)vN!m!3ziA_Uj^G2*VKC60Rm<>BP1lrU? zPk-l-78igp;0uzKF>A*9Foux42J=Ef7tpbS(8a;5{%YRB8E9&&X**cbhvP-z_u0|= zgz{?PwL<0TLVPj@V(}8FV&i&3ca z58cI!JGth|DA(0#YJQfzY-&Q{8}0Ab+(X;52IA==H*GJM$)nj2!_Nm;JA&k(v)Q?@ zB#0`ik%c2p*KBr^1u4@R#HZ0Sgh`!?@y&)_f><^~`9&}`&NUB96HP8({6IWne6D$Q zaK*X|u>(e~=J?J$_g%Q^%-x}gi{ojEA@Rvh$gz9YS@-nM*rU4Z?~5+H4@Sie^rF*$ zI1$iSFhBy6V9$G@kG6-;&)Xpb06hRi`o>bW4s5)mOZc#eZyw2rSd*u@KYX3dRHHYC zWsGBZUZ8R5IC1<`tvQV6wrRPSx@Y1BHo+Ky{+OK~E5UUt=oO)a1SQaccNP$G16Saz zy&&SJTKEezS^aBdao?NSv|Vf3!J9lw=m&8_9Frc17Gp(#&}Jj&GIKR~eY9Om9Y0v) z9tp(FKs-8yo)zqaT6WI@4i!2y9W6c)kR`hQuIRI%3-6vjh4^~zK#FWun4a6tbWlRm z5igSi0#sRClrJ0ur_gpSAGUhkGf8k{M|`vX$%u7DAb}kuC^3hXMh!M7IZ7PA<`xAC z1qysaut5FAi&B| z_}AZwck=K5upNjkfq#Fh9ndvs?*!`jazFlVB7rG1(5E1LogIp!n72as1Thr2@xq@^ z{{dTiCV&kI6Z*yPKR`ba!bGMgKM*I36@o+!_X9!Ur_fKK6MmDZH*Lp8DBo{`5)$UI z!K@h9dSP<_8Zu?3H~yUd0&Pw=;?8NDJ;S-6Ymy6pef*<5cL=BrZi&-mg5C9|7NcpG zZ#rdk&oV>&2SpDpKPUQ(xp3~ZPw<@F1HE4x=EFG{)lfMr^O^~tqoTMtUmCze?#|H| z5>)A8KYXD#9dvE5-v;tcq^jMskiUKq{`Cu@&tw-aoIXvUXNf$a0~%RM7;kFPnsy<6 zPW0N7c{>QEdQ=j53zD6qz^(kdIDjr}_1b5W5c+|X&yQG}r%4wSo!J~#6ged2k#|VB zU(bD08|=VQMZ16*&$Vc|U4TclUg~JY2mXqHNVWAz%D|()+aC0rK+%9V%Njx`=Cc&UiuH`JZ zYY%&3gg;p|O==i9DAn}?bdQl(@S76Cc7m%>26}(axQ-F?=V}$4(}AB$z?Wq zV8i)70X%LcqWZgPXj~4hfPNN0B!0I`Pe5o*?hLoY`YmYSuJ(hdQ?N-aLa{IaUAL6? zyAlX$6TkNZ^>W4Z;Fy{m^sDx5dNRy67e6)I=v=|Swtdc8^}}m;C#^12+qfP2sX1nV zaLzAt{}R?->q(I{WS3z4xpOawVOdWPfD~Z07oX2CtvHj2+YwFwT+i)bM@^7T8Aon8 zt8C@pI5JnJp0Ordz5LToV&r&yq;G7lstT){w9!qhIPjvo+T?*Zgf%0TH|>B;jTf{+ z3;bLZR%Y;~eLS}dx5=KPMcl+ZJ+~|VZFOXV(bNj-l|XKt9+EMJ#+v$0_hY9!h<88g zVOfW)qUVn;>k5K+M_@%yV8sO4z6aFST9VL}#)jxkTWnmD(P`2(@5&3Mbxq)c9Y9CE zuH(D%p2l5d`IpVqUTr#JEh1$el;hwbYlEKi#%k`!)deu_ z!g169rYP-3(?Q~xTq2E@Ms)O-_w<#^VO1dch;<7of-ESK+wx7vpUZoGDO75GntbR7 zqg^QPndVO_CP~=C#B+^-<}{aSX#$mmRk{>Pt6R7D0q!)jlH%2;r zCVO9A4K^d2-Bae}SZwZWqlvC|KW53XNdHjKb2Rb0xt*l zDS(fSb6a>X=1+*y2z@-ga9Vy*O|%&{maqGLd&iFQ9(TDAhk1;&tg@I@O)+@UC3kY~A2hXjcv`&#a$*XrxfQB)@i{uoxI@xXF-N|vWR#$0>bH0zn zR2~#trnW+xPtkMP>E7PQl+$Kj!?6uBymGVg^#r4VN{)G#2yy34{c8zYcdF5yj1Eal zn>`z?BCgSJUOyG{$43M5wAX~OL+?&*?NCe`KYh%2zI1fZWSTdmP8-j6254VR2-kxK zygMU~({hc%Vx%@GHq(y&aS}axV1W#bZ07bB&or8L*dRr_25|g?)~)mqa&HehbR zgaGUif$X`^-Bk(VDM{*v;L)lXjFH=Gb{5XoreSWn1C}@$4_=knxV_cfj_{PUI<2#? z8j@*h!YWq1vj~--%Tcr5)KqK^?7Q1O5*^PP;PdNRzxe80nv6YTd% z|FQk<=K*5y|F{{ap|#x0sdU0aiK)R@qz+oZhV7^AYHIhyJU21Oz{_zO!`k7-z$CaJ z7(+7OTI3&`dfdW&$3Nqavz)h5x6uDF`)6?e0V&bYv-24T!KJ95W)fhVJ`GA-;Qt2t7QOBqR-?P-aGwf77k^F{lDbqZPU~oYAWF3pb5X)FX3#v^_xSM465aK#AjLt3#UwALqJ%pK=RiVk zZ7^k6G+f|cPDMR)Z)(FR-){!>!0q$!|E0NTLc*86g+Db30;YC?1KNSPDD%U1(3Bzh zzi&rW&P~ikF$o-=Qx8u?(GDBfbjXz=OhuE0{u3sa!~J)sYviBiawh;7EqEAwc*i`H$e0m@qkcG!2*^*mh5V2Bf&ORnP*wubB4$FN z0#Cp6|q6gP45;2eV#e`5yq-BiC%OK1%A9oQ1{OTWG-B0ep@z93Vz zAMC~X3m2Xsvm>J;D(PO z4dy;9GtUyS@0Xwf{QPcKVl7-Dsog z{IG9-sFQ`h^#>FLPWw0e_WzPS^V*<%{paoYk+J26Ho^Zvd#2qlU$^|~|J(8<%o%<{ zJ*3dABzv4%#nU{zA?6718ytPGK420|X3Y9*n?8s5I)!i+hPYdX4{sBkKWrcZwQ$VC z=@A_iT|y+&rN;r$f_0QVRn4kc=@k1W3+|d(4tImaut~K*6&?JmYFAZQo&MB-RkN?~PK=-!xB+`<_Pw z$-qx&!%Unsc@tirbV?69BZqo_9UM>E5Mihn!3U&weW=xcI4#ciFxE;DUxwb)$nT`| zt}mzQ)X@s~M}+Dhl+BfQJk`+i##(tg+-(v7vu0IboVRg}?$Zh34tF|bGc~HT5_nPA zP=rNupMqy6D0!l2gG9eqG%bT=s-2=KTi28YB@nM#O&pq0&FydH4q?YL7P6mG+W_X{FG({m|5-b? z2o5FQRi(cmnk)(9jp-lqo$~?F9)JQ>iX@Bn)dhOuxN`HZFZG-|j$n&^mCJ$^735y0J7aY|~goDi|1PN|zG#vW-L1r#90p!~O ztProM+B!ooHddSVRZb+~<$X5J#YRraROn5uDtKbJebuKBx**AV=UO5CVD4i8Ml`I#L>=_htQS*W4y2(6C*gv4wp_)B4@*?ct>zUwJZOU&dDTl}%Y=kEWjr%4$b807Oo# zI5~aV4>f9HP-U3mD6MyWWP|4s{pW;%r+=3&!J+Y`aQi#mZgUCue2cX_hz+eMU3qZD z&hoMNyR*Mcx*kn+V&o=%0l@U0w)lbe-4~>aqkKDQo9j!9)k0u$t*Wsgm$TJ94@+W> z5@?!=!DTTh9iCo377JraF3|mJg9NaG%>MvxL977xx!d6$jE3Aq5fa)2+>QuoRiU?EHQAtsY@&bMrxi!o@`&cPtj315xA!jM z0|nthhzy`Va_MT1{z=PP?Y?cZsnYrjq6`%Y70-_|2t2S1}%1GH7*Km zDLf)w)P`&gYwt!k`1|1W9|}|ipQ!{NbSc@LOt?gk8PWxMVO!ZpdT_6%(a1R=dBuyu z`Ep6!=tXU@js3R3^>89PnQNjS(qAdi|2|sl`Z6KWVpu7Hiq+%jl}OrGD(cv^rf1ih z!3fy{9wPZIcX7EM^m=R%8yr^{J66-Us14h7QN@pUa-WP;Vjn$NiPq$Vqn{`tUTP$f z2AVl7s^<3c-1BD6Sq}~JJkLF6;|`z!Oq~iVxd{G+X5f!`xSucIw2*)n*H9o4@V_bY z;}6h)8zGTL_6KY22eId%vB#li5Tn5aX(7!iUR4Or=)MRB7&deA^D&>-X%4W#QK08V=# z_`)4&99jbAecjS`Lz-Lsl-c#=Jd*|Wp#AW#n>m*{Ry{9UZ0k^D^J*=^72NZxP7}}V z1tMyrIyQ6~AA3LphdO9cCym|FZ)4VL7=S16&BO40mcB0m|F=XqIg6Ita_&P0acFgk zIznQQVFjZie@yw|tcHfQcM#xnX>QzNUf`v;SnaFVo*kTYZ@joOv!O1~aX37EJ)a!) zO6r)3q|~9}!+OT^nKUoOI_i9_s$GWE2zDwF)Oqs z{fIP6kUD$#ZBiHYfi$-z|2CP!z=N)fi$qdmA#I;Q;i8Gksq!Q{25W>k?{tWAQb!%a|hSv>DZb7vdD7*ia6 zZR;kqTx7xUt}gUqe4ke|wL$eD^f>BS^>m5p`4aBA*r4#zMPo}KFlB)PX#sT@Xjm6+ zBPuv6&AmDuVEy9)Y=yw3g);yyB4}O_lv)adQ=HEVwDpf!&BB-ka@;;<^_{_v`Xjls zGeOU3zc>4?wF-)8c(^*=M24$Jb{54R0Cp>z7L9EwJQcL49oZVnZfC)QQ_|cUQ_X-v zXaL$_DSk1ZjWcEoy;~cuCj2%r0WYen2zD^Pqe6~dQnrtB=3}@sSTraM(%mk1Vm${v&Z=rR4NuU3%W__hwc~p+j!V> z4Dxlqo>+2v@Gz}TngQ4leRY&Kf%is8`hyeg>j_R!Qu$+79!%ZYux9LSlrCtK=7wp_ zf`*z`Z26Ze>iUjbDyl7Z>?%GMU_i_MHMD&>5vtQ~`ZcAeRhm0(0>o7daRp_ZaD(VM zX9|Xk#`Tfmh2cqTNPiTh#ua#lwok|KOJr5QNMu_?fpI}fG_7IH=v#tus$dWB)3{w< zwjh9cfb;?UQdkwccc>4tf`|HGJufKV$YIi-KIv(O{)hzS>pApohtk%gu=$4??P~*8 zFR?RKtHkI(EcXQY%`r0IQ_VFRUmpOrne{WtY47=MiyGJS{dTgA4Vbw*6hmf^X9wDQ z5AXUij$_o=XTbE2`kC);q6^Jtk*z?kFOdB}9Xf$ec!{9TRy_lCI3cjJLt%WM-dMLI z(^E$~4nZB3Bu5`l4Td^Al;xQa*swMRm7YbVXMC3i;8P+b%NkuSS=5%=*sw;1k09FV zQ`pAlc4xFC68f~6xwh0`&^X`xtFJmn-Mn?Bd5{yvg*3jt|9D%wU+-Md+#6D)RFf6pPVEoAsm$;QY3Uu zf)GI5^23%N)h$LE0d*I!AlTZHSO~iH-s-w*>uv8QQA0r7)o3M9-G*QgsI)>`MXlR_ z8j(UPwpH8JUF$B^y6t`~)z-RdzMnY>h(GGDd-u7Y=k+}PfhXt8%$d37n(LaGGxHv> zm%;>wNZ0AWpJ~qH%c@=H4tSAR-FH%)rb$&Js-Vt5C`E&R6dU1Tk7o0#lLS-DdztFCf<1n_)JUbP?! zM-ozxC`7A8FY%Q6PAu=y?^Wx2a`i5qzTKqnQ0m`R>bsSCw^IMUQs1T2A5iKKs#QHY z$N2;SwX`HI`@AeYH=jbB?okZmXWZgoR~RrJ;#1_jBmD=#%LP`|-h^mg{m2LUReLd+ zoIC>i6K3b<3aP0pgEU`ZKC4x=*P-f+SS1PGb%i0aCC~F z`D;(?%%oQ|391if&^lGmYJP%#9~%k|A{%|mUdqleLy)STepOEgR>yd9yN~zld$14b zsb9@{k2ZISzxmifwmLk+ZFPV@RP9X;L7wm~L`nAZ{{?cT7En`11!+EpLH6dU&&cIz zJI1rBy;jwMj1XBOPA~{bfe@K^QNL=R!6Wb2@2ecc91w?znZI}p*%K;9x%Y}Y7%Y={ zZq`Q8xDuVdhw-QE7P;oAhw?t(EzV_lU%cYSJ;au};nX3E9J6-ZQ_b6Aa7vkO@+;Wc zZ{8-}RuE7*%KU~{r*d~6>1aO}5Jp|$1?t^}BMQfOkFhYjJUhg++le6iPUhoO4=fMK z-G6BIQ@Q$m=A9lea9$H5x3TQ^6H(Mj5|e(P`z?=2)fuLX7Bl}PW=CaPv7aRZPFjJ zZ!z>eA7oM;%eALDP!Sfn&dzhUdxK6FlJE@kN5oybz>({a<2POsHUi80rKw` z-6Yui7;5X>S(}I9OoxP{bk-Ed*bwY~IqWFvaGYCuqU%8GQGTr(b`dU$wVk z448^z>O)01wP*x`1)N?Zg*e4YBo4;lt6-D%+D171sM$@}fuD5ty^=_p5%Nzz;#U$( zyR|&BxVYpI+H4h+XlRCJa#j1fA7+Df`h8(SLy;i6ARySCkU#1{o$B2sP>XbG>vBEU z{!CEk4E5L$_kcP#3w$lGugWt68^nn|n=qvgS#zq7u#h#{RoD;HCWM*v(TTDRR{gsP z`i`W5_BZuf`%6Ik^GzgY&}b6#=c`Ld`}^_WWKF~647xkWoF{?^rb~?PZGw}9ZBre? znSEDjN~v-;3Uq;dWgxA3Wc;#uYu21s?WE1$(^i#xm9oL_WUCYrjZf4f8i^F@K1cT> z&Isi*BnU2}{A3%*b|qaVGhS?chQELxE5;gvt$QI8=pJdeQxb;AWY8u#&7MgT!fl%& z1lO3zMT)@Cp)3u;j~8n$!tdROY_I?vvYD4~H?m2l$BRLs6VDK9$ZW0m68I|?h3?QF ziTHu(P#rw^pIh{>SH8lb)1AZKQY1^q~;593SjZ~j6?rE9wG>i zRds*P*q!q3`HqSee-~0{w#y zJMxG!@=k}wAdlXtfW;K^eGxatREC*%F-32=w#6&T-U{8gnB)&x$glnicz+yja*({I zQqm}>9HrTF#k`D11s4kvNQY&gI-Hjqj>FEv(k!mD5(8e4^>`+K467{cmxxb%M$uYTi+2x6Iy;r*(8-|rf>N7BGI{>-Ni$)~dBhN_t3i{vu}wrgMB0RKHS8`BhyPP{ zmxZ^O!K`M`cvvREaeg&3gGo&qFucy%Ylc_znh@4Nwy|LZIliDGTKSnwU509d)**`y+0L`-0Jua z@7T3t>4}~ZIINZRC$=6*hl(7I@0K%hyS6$cPk)t#xdQ*{nl0oW>@8<$k$i*` zZsaJU<@;SFL}hesYCN$0=-t4#SXH|sGs%$3AKjgOm-EIYzWc0Uoofy3{q`NZgkZq%effvm9Y^G;WeVzMsh&ZG=)x!950S`=wB*U5GV=kfHr107>2LZaJUYs@0A zB&NS#b&zz)4lW&s`-xV2J-i);S*l z`*%P7@0u65ga0EAU=CkH_BW$WZDVk7=onc|^$7I1W$W?9+mPwncq(Z^*ttY$Gq}&j zcS)gFLqhKN1X)>)LIxjzs5O~#D2umm6Z|EAKw*_$h21kEVkuG9jiF`hmF`#YMThmv zj$WR&LXO=}R@L6tqr3(`tAwg~)#~?j{)^um#=5X)-zmUpGU=48wepjs-Tn;I|;uz|{ z=YueoMTcdu!n}Qd&Ib*Qy>H`#e2XzNi2BWbQq7IMe?0iLzJI;|1_3_ERfB?hebD@6 zT;t3ZD4~pkTyOvX;b^ZiCVzgt6Urk3B37j0GIZDRD+*o+@o;USWf96}_$MQ9d}1ce zc_AT|4Q<^EnIzn<@89RXie(Wn|1%iQ2hHO8{<)ChanUQc9~TWSRJB4j zg$Z{f8?r_PJ_wGG_#h$iL2!x02Qhh+x8EC)a?K5yuWy;ps^^~{kf1Mk0CSB)-n7{J7XTuG-(YZphjyJDjg$Fzl6G1sflAIAg z5zW}Ur6tAbIv4B+QVGuN5%~GBY3&bk1VAs!!&R3%?vRFwWXV^4^dV>%8so`epJ$zXm$JY zJ!dL|&8?SoL%hpec$bZj%-St&5cqt{TOw1Lk-T_i)- z$8YUZ`f9G;;8VEv|KIT`FbXv_cj;3W^Wjsvt0520r~De4c}t)2B!9rCJWGTJpOViX z>Qllke#%1r$#68^LOPaDh84as)u4&G*{4LT`k(bFJ~O{s04r}M^OvaCTl$xz2|wA` zI^bW%di59nMa93hf7ywN4J34W^oT+C5=3imhO_h zowR#cr2~G%->09@=mSdC!7PtViC!DN1&n9R|NH@y&a9t*zOMGES=)z0=dEwl<<`zw z^O^y(&pF?3WEnF3tN&n*XyQg>qhW+jLg^<)OkB0&5wKdQiT*o+f1%$^YZO%M?j0Lw z9EceEC-Q$RW!6wnt@2*#A06K6eh(0^?|w<>tl<0T=XRG}Mgan!v zi$J06?=P%B=%FXk0IU{o;{RsLC z5)=((NZu0fkT))44FY6Mja-g(=x&_nA1I3Vv%tVN0Zj70F)+tZ@;ld?UlTHsaqIkQ zy!j=9B;O^h2+4gAAnp1d@{0<%mgSgncbi{K@|!5i;1UGCD;lU_oSB7;pmMUNyYo0X zO7NQ#IWvd!nQ`j)Kd04%ttcD!Q(0T9{@rS2)mT9WnFZQJZyC#vP0v(j_@RCw`XTlE z5c4!eLJFe-aqtecdLU?&n0bc#y6_04J^!jn)eRloBZ3y{ZA|g^m)wv;MCT=Yg?!tq zavi3n;=YGWB5inJjPLCm49>6?$3eQ@c7bm{C=2$CARdI3HO;3J4(RJY zr)6R8d0e7wJ4&M-^6h8KwYg`^!(zI@SI-d6{O26p{OXSf_k}H®jZL9}6A*=AD5 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

ue0_??HI#Ud!Zmu7SPk7pF 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 literal 0 HcmV?d00001 diff --git a/firmware_update/latest/update.json b/firmware_update/latest/update.json new file mode 100644 index 0000000..4343b57 --- /dev/null +++ b/firmware_update/latest/update.json @@ -0,0 +1,164 @@ +{ + "version": { + "major": 1, + "minor": 4, + "patch": 5 + }, + "firmware": { + "file": "firmware.bin", + "md5": "b8c880418a180efb23260ee093d13e61", + "size": 1409568 + }, + "files": [ + { + "remote": "data/ata-boothifier-upgrade.html", + "local": "/ata-boothifier-upgrade.html", + "md5": "e452db020a5ad660599129ab35e01058", + "size": 16736 + }, + { + "remote": "data/favicon.ico", + "local": "/favicon.ico", + "md5": "ba4c4e3bf5e5db2bbfc56a52f3657d79", + "size": 1150 + }, + { + "remote": "data/css/global-style.css", + "local": "/css/global-style.css", + "md5": "217a9cca8b4eae2d28fa8bc5a0f6db09", + "size": 1239 + }, + { + "remote": "data/css/nav.css", + "local": "/css/nav.css", + "md5": "e653a75433056f28e3f410f567b8622c", + "size": 1538 + }, + { + "remote": "data/images/atalogo.png", + "local": "/images/atalogo.png", + "md5": "5a18c88a4ea80c8d8d0ad52b2fdbbadc", + "size": 16690 + }, + { + "remote": "data/images/favicon-32x32.png", + "local": "/images/favicon-32x32.png", + "md5": "d80cf74ace3a8be487a5158bca48f9b6", + "size": 2428 + }, + { + "remote": "data/js/event-box.js", + "local": "/js/event-box.js", + "md5": "553f26707686038e275227a97eb96659", + "size": 12341 + }, + { + "remote": "data/js/fwUoload.js", + "local": "/js/fwUoload.js", + "md5": "d4a734cce529c3adf831ea46259f9c2d", + "size": 1524 + }, + { + "remote": "data/js/hue-select.js", + "local": "/js/hue-select.js", + "md5": "0a58f1a339af5c54aecfc5ce1aac7168", + "size": 6367 + }, + { + "remote": "data/js/jquery-3.7.1.js", + "local": "/js/jquery-3.7.1.js", + "md5": "fdb81281d3773a7462998fdddbe6f5bf", + "size": 196885 + }, + { + "remote": "data/system/tunes.json", + "local": "/system/tunes.json", + "md5": "814999e88296bee179cef5d394f3f696", + "size": 1149 + }, + { + "remote": "data/system/update.json", + "local": "/system/update.json", + "md5": "8046dbf9490cfd88fe5970b4ec1ac2cd", + "size": 91 + }, + { + "remote": "data/www/about.html", + "local": "/www/about.html", + "md5": "23f0c991c5c67e7eefe6c24aa50b59fb", + "size": 4222 + }, + { + "remote": "data/www/edit.html", + "local": "/www/edit.html", + "md5": "fed238dcf87d3797b08ed371330ea800", + "size": 5546 + }, + { + "remote": "data/www/edit_old.html", + "local": "/www/edit_old.html", + "md5": "9aafba533ac77352d30841cdc34db0c4", + "size": 4240 + }, + { + "remote": "data/www/failed.html", + "local": "/www/failed.html", + "md5": "a24025d56bef1cd2ed5375f6e8853dde", + "size": 828 + }, + { + "remote": "data/www/files.html", + "local": "/www/files.html", + "md5": "52d82d4d23b038929691cd0fb20e8ee0", + "size": 9369 + }, + { + "remote": "data/www/home.html", + "local": "/www/home.html", + "md5": "767fd73b733d8e37dc79252fcd20b610", + "size": 7822 + }, + { + "remote": "data/www/index.html", + "local": "/www/index.html", + "md5": "af690aaa4dec02691ffdb2765c837370", + "size": 806 + }, + { + "remote": "data/www/lights.html", + "local": "/www/lights.html", + "md5": "272f8dc423923bb5026837240f654efe", + "size": 19056 + }, + { + "remote": "data/www/navbar.html", + "local": "/www/navbar.html", + "md5": "89af40b990e297e41e116105b04b66ee", + "size": 533 + }, + { + "remote": "data/www/ok.html", + "local": "/www/ok.html", + "md5": "db42bd2ff52293c3b4d6ef62fb4e3a42", + "size": 865 + }, + { + "remote": "data/www/setup.html", + "local": "/www/setup.html", + "md5": "19e1f419844852800757ccd36bb7892a", + "size": 11349 + }, + { + "remote": "data/www/upgrade.html", + "local": "/www/upgrade.html", + "md5": "f4a3efb67be66b5214d905e605984c93", + "size": 9080 + }, + { + "remote": "data/www/wifi.html", + "local": "/www/wifi.html", + "md5": "c6e55946a02cb0ff28689dd10d265987", + "size": 5320 + } + ] +} \ No newline at end of file diff --git a/firmware_update/loyal-column-439819-e3-8cddff2ee2c2.json b/firmware_update/loyal-column-439819-e3-8cddff2ee2c2.json new file mode 100644 index 0000000..a012359 --- /dev/null +++ b/firmware_update/loyal-column-439819-e3-8cddff2ee2c2.json @@ -0,0 +1,13 @@ +{ + "type": "service_account", + "project_id": "loyal-column-439819-e3", + "private_key_id": "8cddff2ee2c2b8fb9c895cc02832cace7bec8dfc", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDKBvkZwnQe+yRJ\nMnaDYqMYaYsmziC74D4cdIOhLcLfUTB9r+VX0YwGuPEDvqqRKfEt57pxN4U+pZEz\nIt+P5J/kKQ2S9mBd5n5/YK5mgbGpErvV1OSBX/L01lW9LD3rEh3HqVcSPtqToquV\n9MuERSdQ9HoPfaWRKVxMYY5lrmericS9p0OndjoR99rTTkwvY0baSRBEL5lsFpaX\neuzVNxtQPe3uGBAbNIROoIgV+2zMC0lEu5IkqAfxhPmwYbBxIz39l4Z+lSMjnc6y\na5O37VYId8yc2UEZVpVfQrFzRX5bs5oq4DL5s6RQAcLsjHEBc8IDCWKn9iZpLQoG\ndVPLAHz1AgMBAAECggEAMtARLDDz6uzowf5Ov5n6AoRdSuvSYN60UIzfpJTxbCCs\nDRjGGtHqIbC3ceUtWmiNsCmWA67etRSIki+FwlfS/Vxz/RA5ybRVAa2r/71EFY3Q\nL6aluIKNBZa5JuTvR+YdFmBZtY2YhUSHWqag34pTKrHq12WXVIzuGYn5+kok6ghA\nyGLBDRlrhMwmTR14N6bEG/I81T+b+Ms73nvsA5OzmOFg0ZB+2DmYM9qS0lXV/RbI\n3eptVJbFAvrjqnQN3G4Gzh61EJ+b2FtFqQNFSNpH6YKETfW4kCFS0wz96xvsQ1I4\nNgb+CZSUwzXSlTIfbAs2eeMdgPhclk47QQK3aliKWwKBgQDnaqwQeJ0oy4FagcfC\nCc+4CoqIpAhxxgepztjaK3nKaZW+qnIxLTm+ysUJ1OYy6O1aFiBT/SID3HtQQkXm\n6E876Uth2EWVQ0JBWlNtyzahteSgfDyLxDD1vhqdKOt6YIiB1CMJu0gmN1YAXINh\n3UOJmvf3SMeSJfop9LqOSTFQbwKBgQDffQ+hq6kjUYqaq3+TpFSPtX/xzyBbVhZu\ndicjyxySHPHh5CsOO45oRwT5EIQaNqj1OGrQdf+cot2PaTCN/Xcfi+YmeiWFWcBa\nj+eoH80EkqXX173lpKJ/q1j4Z02ju9OkLFo+xKpPEJ/tyMQJ+Y32lf+lcJAYbQ6G\nWEE+vRUy2wKBgEJADaZhgUyOhietA80PFgJwMhzQKQK5WLRKXVaIH8PM2mvNTAe8\nFM4EKsiXxJdKi7jAoNyqmTRVyI+/iVeDpjtWnJgUXEdRDAS9oSjLhoZhFmTDbGnu\n7r1LgT8A2fkc7beNTcKZdRxLtzk/QvWfbJhWXyrSESBL8wRT5ZyaY53NAoGASo8G\ncAE85DOH8iHU5gSk0WzTynA3c0E9KozwcdiJJ5XQfHQKiS0FTXSkDBOefsBNjcHM\noM88/5y2HeoR8MzmuPeLSLrnWZ2ftpbbyhMR6ryh52hqSfqq27zmabjNBhrbeKHz\nWY1y4/2/3Sxleo5u9h0EtMTgzcVUWy2GVs/uCpECgYASxJ1x3Bw3W6DnXhU2UfRJ\nSC5hdxnaWUqvmZmtRIsVzxgg/ZpAPY5cefhLUz70KuLcFF0GOrYUOKrknsHtHinq\nL/+E3IRtfHzQOabR5uB4WFNodJ9cr+pSIiCtC9FxUWaO2xpa4xr2wC04qfQyhhb1\n0x1gjcafgbY8FPIbKdS/1A==\n-----END PRIVATE KEY-----\n", + "client_email": "boothifierservice@loyal-column-439819-e3.iam.gserviceaccount.com", + "client_id": "103041587650332588407", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/boothifierservice%40loyal-column-439819-e3.iam.gserviceaccount.com", + "universe_domain": "googleapis.com" +} diff --git a/include/ATALights.h b/include/ATALights.h new file mode 100644 index 0000000..1004ca3 --- /dev/null +++ b/include/ATALights.h @@ -0,0 +1,77 @@ +#pragma once + +#include +#include +#include "ColorPalettes.h" + +#define PIXEL_INDEX -3 +#define SOLID_COLOR_INDEX -2 +#define OFF_INDEX -1 + +typedef struct { + int AnimationIndex; + union { + struct { + uint8_t red; + uint8_t grn; + uint8_t blu; + uint8_t wht; + uint8_t extra[4]; // 4 extra bytes + }; + uint8_t rgb[3]; // Access RGB only + uint8_t rgbw[4]; // Access RGBW + uint8_t data[8]; // Access all 8 bytes + } data; +} ANIM_EVENT; + +typedef struct { + CRGB* leds; + bool enabled; + int size; + String chip; + String rgbOrder; + int shift; + int offset; + int powerDiv; + int effSize; + uint8_t bright; + uint8_t i2sCh; + uint8_t core; + uint8_t pin; +}LEDSTRIP_SETTINGS; + +extern LEDSTRIP_SETTINGS ledSettings[2]; + +// Forward declarations +//extern CRGB* leds1; +//extern CRGB* leds2; + +void Lights_Control_Task(void *parameters); +void Init_Lights_Task(void); +void Lights_Control_Task_Resume(void); +void Init_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder, const String& chipType, uint8_t bright); + +EOrder GetEOrderByString(const String& rgbStr); + +void Lights_Set_Animation(int animIndex, uint8_t red, uint8_t grn, uint8_t blu); +void Lights_Set_ON(void); +void Lights_Set_OFF(void); +void Lights_Set_Brightness(uint8_t scale); +void Lights_Set_White(uint8_t val); +void createFirePalette(CRGBPalette16& palette, CRGB color1, CRGB color2, CRGB color3); +void loadColorPack(COLOR_PACK& dest, const COLOR_PACK& src); + + + + + + + + + + + + + + + diff --git a/include/Animations.h b/include/Animations.h new file mode 100644 index 0000000..33690e5 --- /dev/null +++ b/include/Animations.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include +#include "ColorPalettes.h" + +#define MinLoopDelay 2 +#define MaxLoopDelay 200 +#define MaxSpeed 100 + +#define RANDOM_DECAY true +#define LINEAR_DECAY false + +#define CYCLES_PER_DIRECTION 2 + +void Animation_Init(void); + +void Animation_Loop(bool volatile& loop_active_flag, int speed, std::function callback); +void Animation_Loop_Limited(bool volatile& loop_active_flag, int speed, TickType_t duration, std::function callback); +void Animation_Loop_Cycles(bool volatile& loop_active_flag, int speed, uint32_t loop_cycles, std::function callback); + +void Anim_Rainbow(bool volatile& loop_active_flag, CRGB* leds, int size, int speed); + +void Anim_Fire(bool volatile& activeFlag, CRGB* leds, int size, int speed, const CRGBPalette16& firePalette, int shift); + +void Anim_Color_Sectors(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colorPack, uint8_t numRepeats, int speed); + +//template +//void Anim_Color_Gradient_Trans(bool volatile& activeFlag, CRGB* leds, int size, TPalette& palette, uint8_t paletteSize, uint8_t numRepeats, int speed); +//void Anim_Color_Gradient_Red_Yellow_Violet(bool volatile& activeFlag, CRGB* leds, int size, int speed); + +void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colorPack, int speed, bool randomDecay, bool shorterTail, int cometMultiplier); +//void Anim_Comet_Rainbow(bool volatile& activeFlag, CRGB* leds, int size, int speed); + +void Anim_TimedFill(bool volatile& activeFlag, CRGB* leds, int size, CRGB col1, CRGB col2, int totalDurationMs, int shift); + +void Anim_ColorBreath(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colors, uint32_t timeMs, int speed); + +void Anim_GradientRotate(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colors, int speed); + +uint32_t getRandomValue(uint32_t maxValue); diff --git a/include/AppUpgrade.h b/include/AppUpgrade.h new file mode 100644 index 0000000..20d5516 --- /dev/null +++ b/include/AppUpgrade.h @@ -0,0 +1,175 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "AppVersion.h" + +#define DEFAULT_MANIFEST_URL "https://storage.googleapis.com/boothifier/latest/" +#define BUFFER_SIZE 4096 + +extern TaskHandle_t Update_Task_Handle; + +/** + * @brief Version information structure + */ + + +//bool versionCompare(Version& remoteVersion, Version& localVersion); + +extern Version otaVersion; + +/** + * @brief File information structure + */ +struct FileInfo { + String remotePath; ///< Path on remote server + String localPath; ///< Path in local filesystem + String md5; ///< MD5 hash for verification + //size_t size; ///< File size in bytes +}; + +/** + * @class AppUpdater + * @brief Handles firmware and filesystem updates + */ +class AppUpdater { + public: + Version localVersion; + Version otaVersion; + const char* bucketUrl; + const char* appName; + const char* manifestName; + JsonDocument jsonManifest; + JsonArray jsonFilesArray; + + /** + * @brief Update status enumeration + */ + enum class UpdateStatus { + IDLE, ///< No update in progress + MESSAGE, ///< Update message + DOWNLOADING, ///< Downloading files + VERIFYING, ///< Verifying file integrity + FILE_SKIPPED, ///< File already up to date + FILE_SAVED, ///< Saving to filesystem + MD5_FAILED, ///< MD5 verification failed + COMPLETE, ///< Update complete + ERROR ///< Error occurred + }; + + /** + * @brief Constructor + * @param version Current firmware version + * @param bucket Base URL for updates + * @param fs Filesystem reference + */ + AppUpdater(fs::FS& fs, Version localVersion, const char* bucket, const char* manifestName ="update.json", const char* appBin = "firmware.bin" ); + + /** + * @brief Set progress callback function + * @param callback Function to call with progress updates + */ + void setProgressCallback(void (*callback)( UpdateStatus status, int percentage, const char* message)); + + /** + * @brief Check for and apply updates + * @return true if update successful + */ + bool IsUpdateAvailable(void); + + /** + * @brief Update files from manifest + * @param manifestPath Path to manifest file + * @return true if all files updated successfully + */ + //bool updateFilesFromManifest(const char* manifestPath = DEFAULT_MANIFEST_URL); + bool updateFilesArray(void); + + /** + * @brief Update single file + * @param remotePath Remote file path + * @param localPath Local file path + * @param expectedMd5 Expected MD5 hash + * @return true if file updated successfully + */ + bool updateFile(const char* remotePath, const char* localPath, const char* expectedMd5); + + /** + * @brief Get manifest content + * @param manifestPath Path to manifest file + * @return Manifest content as a json document + */ + bool checkManifest(void); + + bool updateApp(void); + + String getVersion(void); + + private: + typedef void (*ProgressCallback)(UpdateStatus status, int percentage, const char* message); + ProgressCallback progressCb; + fs::FS& fileSystem; + UpdateStatus status; + std::unique_ptr downloadBuffer; + bool updateAvailable = false; + + + + /** + * @brief Verify and save file + * @param stream Input stream + * @param contentLength Expected content length + * @param localPath Local file path + * @param expectedMd5 Expected MD5 hash + * @return true if successful + */ + bool verifyAndSaveFile(WiFiClient* stream, size_t contentLength, + const char* localPath, const char* expectedMd5); + + /** + * @brief Update progress callback + * @param percentage Progress percentage + * @param newStatus Current status + */ + void updateProgress(UpdateStatus newStatus, int percentage, const char* message = nullptr); + + String getLocalMD5(const char* filePath); +}; + + + +// Queue handle for firmware update messages +extern QueueHandle_t updateMsgQueue; + +// Message structure for update progress +struct UpdateMessage { + String message; + bool complete; + int progress; +}; + +/** + * @brief Firmware update task + * @param param Task parameters + */ +void firmwareUpdateTask(void* param); + +void startFirmwareUpdateTask(AsyncEventSource* eventSrc); + +void loadUpdateJson(void); + +void updateProgress(AppUpdater::UpdateStatus status, int percentage, const char* message); + +void sendUpdateMessage(const char* message, bool complete, int progress); + +void handleUpdateProgress(AsyncWebServerRequest *request); + +bool checkManifest(Version& remoteVersion); + +void startVersionCheckTask(); + +void versionCheckTask(void* parameter); \ No newline at end of file diff --git a/include/AppVersion.h b/include/AppVersion.h new file mode 100644 index 0000000..ecdb755 --- /dev/null +++ b/include/AppVersion.h @@ -0,0 +1,47 @@ +#pragma once +#include + +struct Version { + byte parts[3]; ///< Version numbers: [0]=major, [1]=minor, [2]=patch] + + byte& major() { return parts[0]; } + byte& minor() { return parts[1]; } + byte& patch() { return parts[2]; } + + const byte& major() const { return parts[0]; } + const byte& minor() const { return parts[1]; } + const byte& patch() const { return parts[2]; } + + String toString() const { + return String(parts[0]) + "." + String(parts[1]) + "." + String(parts[2]); + } + + // Comparison operators + bool operator==(const Version& other) const { + return parts[0] == other.parts[0] && + parts[1] == other.parts[1] && + parts[2] == other.parts[2]; + } + + bool operator!=(const Version& other) const { + return !(*this == other); + } + + bool operator<(const Version& other) const { + return (parts[0] < other.parts[0]) || + (parts[0] == other.parts[0] && parts[1] < other.parts[1]) || + (parts[0] == other.parts[0] && parts[1] == other.parts[1] && parts[2] < other.parts[2]); + } + + bool operator>(const Version& other) const { + return other < *this; + } + + bool operator<=(const Version& other) const { + return !(*this > other); + } + + bool operator>=(const Version& other) const { + return !(*this < other); + } +}; diff --git a/include/BLE_SP110E.h b/include/BLE_SP110E.h new file mode 100644 index 0000000..db2a470 --- /dev/null +++ b/include/BLE_SP110E.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +// Define constants +#define GET_CHECK_DEVICE 0xD5 +#define GET_DEVICE_INFO 0x10 +#define SET_IC_MODEL 0x1C +#define SET_RGB_SEQUENCE 0x3C +#define SET_LED_NUM 0x2D +#define SET_DEVICE_NAME 0xBB +#define TURN_ON 0xAA +#define TURN_OFF 0xAB +#define SET_STATIC_COLOR 0x1E +#define SET_BRIGHT 0x2A +#define SET_WHITE 0x69 +#define SET_PRESET 0x2C +#define SET_SPEED 0x03 +#define SET_AUTO_MODE 0x06 + +// Initializes the BLE server, services, and advertising +void Init_BLE_SP110E(NimBLEServer* pServer); + +void sendToAllClients(const uint8_t *data, size_t len); + +void process_BLE_SP110E_Command(const uint8_t* val, uint8_t len, NimBLECharacteristic* bleChar); + +void Init_BLE_LightStick_Client(); + +void BLE_LightStick_Client_Task(void *parameter); + + + diff --git a/include/BLE_UpdateService.h b/include/BLE_UpdateService.h new file mode 100644 index 0000000..6d77d94 --- /dev/null +++ b/include/BLE_UpdateService.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +void Init_UpgradeBLEService(NimBLEServer *pServer); + +void bleUpgrade_send_message(String s); \ No newline at end of file diff --git a/include/BleServer.h b/include/BleServer.h new file mode 100644 index 0000000..670790f --- /dev/null +++ b/include/BleServer.h @@ -0,0 +1,5 @@ +#pragma once +#include + + +void Init_BleServer(bool isSP110EActive, bool isUpgradeActive); \ No newline at end of file diff --git a/include/ColorPalettes.h b/include/ColorPalettes.h new file mode 100644 index 0000000..412df14 --- /dev/null +++ b/include/ColorPalettes.h @@ -0,0 +1,45 @@ +#pragma once + +#include + + +typedef struct { + uint8_t size; + CRGB col[8]; +} COLOR_PACK; + +// Sectors +const COLOR_PACK colorPack_RAINBOW PROGMEM = { 7, { CRGB::Red, CRGB::OrangeRed, CRGB::Yellow, CRGB::Green, CRGB::Blue, CRGB::BlueViolet, CRGB::MediumVioletRed } }; +const COLOR_PACK colorPack_USA PROGMEM = { 3, { CRGB::Red, CRGB::White, CRGB::Blue } }; +const COLOR_PACK colorPack_MEXICO PROGMEM = { 3, { CRGB::Green, CRGB::White, CRGB::Red } }; +const COLOR_PACK colorPack_CANADA PROGMEM = { 2, { CRGB::Red, CRGB::White } }; +const COLOR_PACK colorPack_GERMANY PROGMEM = { 3, { CRGB::Black, CRGB::Red, CRGB::Yellow } }; + +// Single Colors +const COLOR_PACK colorPack_Single_Red PROGMEM = { 1, { CRGB::Red } }; +const COLOR_PACK colorPack_Single_Orange PROGMEM = { 1, { CRGB::OrangeRed } }; +const COLOR_PACK colorPack_Single_Yellow PROGMEM = { 1, { CRGB::Yellow } }; +const COLOR_PACK colorPack_Single_Green PROGMEM = { 1, { CRGB::Green } }; +const COLOR_PACK colorPack_Single_Blue PROGMEM = { 1, { CRGB::Blue } }; +const COLOR_PACK colorPack_Single_Viloet PROGMEM = { 1, { CRGB::DarkViolet } }; +const COLOR_PACK colorPack_Single_Magenta PROGMEM = { 1, { CRGB::Magenta } }; +const COLOR_PACK colorPack_Single_White PROGMEM = { 1, { CRGB::White } }; + +// Dashes +const COLOR_PACK colorPack_RedBlack PROGMEM = { 2, { CRGB::Red, CRGB::Black } }; +const COLOR_PACK colorPack_OrangeBlack PROGMEM = { 2, { CRGB::DarkOrange, CRGB::Black } }; +const COLOR_PACK colorPack_YellowBlack PROGMEM = { 2, { CRGB::Yellow, CRGB::Black } }; +const COLOR_PACK colorPack_GreenBlack PROGMEM = { 2, { CRGB::Green, CRGB::Black } }; +const COLOR_PACK colorPack_BlueBlack PROGMEM = { 2, { CRGB::Blue, CRGB::Black } }; +const COLOR_PACK colorPack_IndigoBlack PROGMEM = { 2, { CRGB::Indigo, CRGB::Black } }; +const COLOR_PACK colorPack_VioletBlack PROGMEM = { 2, { CRGB::MediumVioletRed, CRGB::Black } }; +const COLOR_PACK colorPack_WhiteBlack PROGMEM = { 2, { CRGB::White, CRGB::Black } }; + + +const COLOR_PACK fire PROGMEM = { 4, { CRGB::Red, CRGB::OrangeRed, CRGB::Yellow, CRGB::Black } }; + + + +void Create_Red_Yellow_Violet_Palette(CRGBPalette16& customPalette); + + diff --git a/include/JsonConstrain.h b/include/JsonConstrain.h new file mode 100644 index 0000000..dc3e2c7 --- /dev/null +++ b/include/JsonConstrain.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +template +T jsonConstrain(const char *tag, const JsonObject &jsonObject, const char *key, T min, T max, T def); +const char* jsonConstrainChar(const char *tag, const JsonObject &jsonObject, const char *key, const char *def); +String jsonConstrainString(const char *tag, const JsonObject &jsonObject, const char *key, String def); +bool jsonConstrainBool(const char *tag, const JsonObject &jsonObject, const char *key, bool def); + + +extern template int jsonConstrain(const char *, const JsonObject &, const char *, int, int, int); +extern template float jsonConstrain(const char *, const JsonObject &, const char *, float, float, float); diff --git a/include/OnEveryN.h b/include/OnEveryN.h new file mode 100644 index 0000000..a70f602 --- /dev/null +++ b/include/OnEveryN.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +// Generic class to handle intervals +template +class OnEveryN { +public: + OnEveryN() : lastTime(0) {} + + // Check if the interval has elapsed + bool ready() { + unsigned long currentTime = millis(); + if (currentTime - lastTime >= interval) { + lastTime = currentTime; + return true; + } + return false; + } + +private: + unsigned long lastTime; // Stores the last execution time +}; + +// Helper macros to generate unique names +#define CONCATENATE_DETAIL(x, y) x##y +#define CONCATENATE(x, y) CONCATENATE_DETAIL(x, y) +#define UNIQUE_NAME(base) CONCATENATE(base, __LINE__) + +// Macro for ON_EVERY_N_MILLISECONDS +#define ON_EVERY_N_MILLISECONDS(N) \ + static OnEveryN UNIQUE_NAME(__on_everyN_); \ + if (UNIQUE_NAME(__on_everyN_).ready()) + +// Macro for ON_EVERY_N_SECONDS +#define ON_EVERY_N_SECONDS(N) ON_EVERY_N_MILLISECONDS((N) * 1000) + diff --git a/include/PWM_Output.h b/include/PWM_Output.h new file mode 100644 index 0000000..935dc5f --- /dev/null +++ b/include/PWM_Output.h @@ -0,0 +1,42 @@ +#pragma once +#include + + +struct PWMOUT{ + float max; + bool visionCorrected; + int resolution; + int frequency; +}; + +class PWM_Output { + public: + float currDuty; + bool visionCorrected; + PWM_Output(int8_t pin, uint8_t ch, uint8_t res, uint32_t freq, float maxDuty, bool visionCorrected=false); + void setOutput(float duty); + void setFreq(uint32_t fq); + int linearizeOutput(float inp); + void setMaxDuty(float duty); + int getOutVal(void); + void setResolution(uint8_t res); + float getMaxDuty(void) { + return maxDuty; + } + + int currOutVal; + private: + uint8_t ch; + uint32_t freq; + uint8_t res; + + float maxDuty; + int msecRampRate; + + float standardFactor; + float visionFactor; + +}; + + + diff --git a/include/Ramp_Lights.h b/include/Ramp_Lights.h new file mode 100644 index 0000000..0979131 --- /dev/null +++ b/include/Ramp_Lights.h @@ -0,0 +1,29 @@ +#pragma once + +#include "PWM_Output.h" +#include "OneButton.h" + +enum RAMP_STATE { RampingUp = 0, RampingDown }; + +class RAMP_LIGHT { +public: + RAMP_LIGHT(OneButton* button, PWM_Output* pwmOutput, int min, int max, float step); + +private: + PWM_Output* pwmOutput; + OneButton* button; + int min; + int max; + float step; + float currentValue; + bool IsOn = false; + RAMP_STATE rampState; + int tickCount; + + void tick(); + void singleClick(); + void longPressStart(); + void longPressStop(); + void duringLongPress(); +}; + diff --git a/include/UriDecode.h b/include/UriDecode.h new file mode 100644 index 0000000..1e4a331 --- /dev/null +++ b/include/UriDecode.h @@ -0,0 +1,21 @@ +#pragma once + +String uriDecode(const String& input) { + String decoded = ""; + char a, b; + for (size_t i = 0; i < input.length(); i++) { + if ((input[i] == '%') && (i + 2 < input.length()) && + ((a = input[i + 1]) && (b = input[i + 2])) && + isxdigit(a) && isxdigit(b)) { + a = (a <= '9') ? a - '0' : (toupper(a) - 'A' + 10); + b = (b <= '9') ? b - '0' : (toupper(b) - 'A' + 10); + decoded += char((a << 4) | b); + i += 2; + } else if (input[i] == '+') { + decoded += ' '; + } else { + decoded += input[i]; + } + } + return decoded; +} \ No newline at end of file diff --git a/include/_FreeRTOSConfig.h b/include/_FreeRTOSConfig.h new file mode 100644 index 0000000..8ad85c2 --- /dev/null +++ b/include/_FreeRTOSConfig.h @@ -0,0 +1,30 @@ +#ifndef FREERTOS_CONFIG_H +#define FREERTOS_CONFIG_H + +#include // Required for compatibility with Arduino framework + +//#define configUSE_PREEMPTION 1 +//#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1 +//#define configCPU_CLOCK_HZ ( 240000000UL ) +//#define configTICK_RATE_HZ ( 1000 ) +//#define configMAX_PRIORITIES ( 25 ) +//#define configMINIMAL_STACK_SIZE ( 1024 ) +//#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 64 * 1024 ) ) +//#define configMAX_TASK_NAME_LEN ( 16 ) +//#define configUSE_TRACE_FACILITY 1 +//#define configUSE_STATS_FORMATTING_FUNCTIONS 1 +//#define configGENERATE_RUN_TIME_STATS 0 +//#define configUSE_16_BIT_TICKS 0 +//#define configIDLE_SHOULD_YIELD 1 + +// Additional configuration for debugging +//#define configCHECK_FOR_STACK_OVERFLOW 2 +//#define configUSE_MALLOC_FAILED_HOOK 1 +//#define configUSE_IDLE_HOOK 1 +//#define configUSE_TICK_HOOK 0 + +// Optional macros for runtime stats collection (if needed) +//#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() // No-op +//#define portGET_RUN_TIME_COUNTER_VALUE() xTaskGetTickCount() + +#endif /* FREERTOS_CONFIG_H */ \ No newline at end of file diff --git a/include/global.h b/include/global.h new file mode 100644 index 0000000..b81495d --- /dev/null +++ b/include/global.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include +#include "appVersion.h" + +//#define FIRMWARE_VERSION "1.0.0" +#define FIRMWARE_VERSION_MAJOR 1 +#define FIRMWARE_VERSION_MINOR 4 +#define FIRMWARE_VERSION_PATCH 4 + +extern Version localVersion; + +enum COMM_MODE { COMM_WIFI_AP_BLE, COMM_WIFI_AP_CLIENT }; +extern enum COMM_MODE commMode; + +typedef struct{ + int count; + TaskHandle_t handle[16]; +}SYS_TASK_HANDLES; + +extern SYS_TASK_HANDLES TaskList; + +typedef struct{ + uint64_t chipMAC; + uint8_t macByte[6]; + String macStr; + //char chipID_2Hex[4]; + size_t app_partition_size; + size_t app_partition_free; +}CHIP_INFO; + +extern CHIP_INFO chipInfo; + +// Stack, Heap and CPU Reporting +//#define ENABLE_SYSTEM_STATS 0 + +int findUnusedLedcChannel(void); +//void read_system_settings(void); +//void read_app_events_settings(void); +//void read_animations_list(void); +void report_system_stats(void); +void print_chip_info(void); +void get_chip_mac(char* macStr, size_t size); +void print_ram_info(void); +//void Pulse_LED_Status(int mSecs); +void printTaskInfo(void); +void printTaskCPUUsage(TaskHandle_t xTask); +void printTaskStackWatermark(TaskHandle_t xTask); +void addTaskHandleToList(SYS_TASK_HANDLES &list); +String getMacAddress(void); +String macToStr(uint8_t* mac); +bool updateJsonDocument(JsonDocument& doc, const char* filePath); + +void Log_CPU_Load(void); + +void print_task_watermarks(void); + diff --git a/include/my_board.h b/include/my_board.h new file mode 100644 index 0000000..4b092d8 --- /dev/null +++ b/include/my_board.h @@ -0,0 +1,37 @@ +#pragma once +#include + +#define I2C_SDA1_Pin 1 +#define I2C_SCL1_Pin 2 + +//int linearizeLED(float inp); + +typedef struct{ + int8_t rgb1; + int8_t rgb2; + int8_t btn[3]; + int8_t buzzer; + int8_t touch[5]; + int8_t shield; + int8_t relay[4]; + int8_t stat[2]; + int8_t adc1; + int8_t oled_dc; + int8_t oled_rst; + int8_t oled_mosi; + int8_t oled_sck; + int8_t oled_cs; + int8_t ext[2]; + int8_t rf433tx; + int8_t rf433rx; +}BOARD_PINS; + +extern BOARD_PINS* thisBoardPins; +#define setStatusPin1(state) digitalWrite(thisBoardPins->stat[0], state); +#define setStatusPin2(state) digitalWrite(thisBoardPins->stat[1], state); + +void Load_Board_Pins(BOARD_PINS& boardPins, String& path); +void Init_Board_Basic(BOARD_PINS& boardPins); +void updateFanControl(float temperature); +void Initialize_Rear_Control(int relayIndex, int buttonIndex, int rampTime, int steps, float min, float max); + diff --git a/include/my_buttons.h b/include/my_buttons.h new file mode 100644 index 0000000..972b40c --- /dev/null +++ b/include/my_buttons.h @@ -0,0 +1,29 @@ +#pragma once + +#include "my_board.h" +#include "OneButton.h" + +extern OneButton *boardButtons[3]; + +#define Update_Buttons() boardButtons[1]->tick(); boardButtons[2]->tick(); boardButtons[3]->tick(); + +void Init_ButtonEvents(int8_t (&pin)[3]); + +void btn1_click(); +void btn1_doubleClick(); +void btn1_LongPressStart(); +void btn1_LongPressStop(); +void btn1_DuringLongPress(); + +void btn2_click(); +void btn2_doubleClick(); +void btn2_LongPressStart(); +void btn2_LongPressStop(); +void btn2_DuringLongPress(); + +void btn3_click(); +void btn3_doubleClick(); +void btn3_LongPressStart(); +void btn3_LongPressStop(); +void btn3_DuringLongPress(); + diff --git a/include/my_buzzer.h b/include/my_buzzer.h new file mode 100644 index 0000000..69aeae4 --- /dev/null +++ b/include/my_buzzer.h @@ -0,0 +1,41 @@ +#pragma once + +#include + + +typedef enum { + TUNE_BOOT, + TUNE_SHUTDOWN, + TUNE_CONNECTED, + TUNE_DISCONNECTED, + TUNE_DONE, + TUNE_WARNING, + TUNE_ERROR, + TUNE_BLIP, + TUNE_THUMP, + TUNE_ACK, + TUNE_WAITING, + TUNE_LOWBEEP, + TUNE_HIGHBEEP +}TUNE_TYPE; + +//Tunes +typedef struct { + bool enabled; + int cycles; + int pause; + String melody; +}BUZZ_TUNE; + +#define TUNE_MAX_COUNT 12 + +extern BUZZ_TUNE buzzTune[TUNE_MAX_COUNT]; + +void Init_Buzzer(int8_t, const char* configPath); + +void Buzzer_Beep(int, int freq=1000); + +void Buzzer_Load_Tunes(const char* tunesPath); + +void Buzzer_Play_Tune(TUNE_TYPE, bool async=true, bool priority=true); + diff --git a/include/my_oled.h b/include/my_oled.h new file mode 100644 index 0000000..08fa290 --- /dev/null +++ b/include/my_oled.h @@ -0,0 +1,17 @@ +#pragma once +#include + +extern Adafruit_SSD1306 *oled; + +void Init_OLED(uint8_t width, uint8_t height, uint8_t mosiPin, uint8_t sckPin, uint8_t dcPin, uint8_t rstPin, uint8_t csPin); + +void oled_ShowInfo(void); + +void testdrawline(Adafruit_SSD1306* display); // Draw many lines + +void testdrawrect(Adafruit_SSD1306* display); // Draw rectangles (outlines) + + + + + diff --git a/include/my_tsensor.h b/include/my_tsensor.h new file mode 100644 index 0000000..995007e --- /dev/null +++ b/include/my_tsensor.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include "PWM_Output.h" + +typedef struct{ + bool enabled; + float temperature; + float Setpoint1; + float Setpoint2; + float fanPower1; + float fanPower2; + float hyst; +}T_SENSOR; + +extern T_SENSOR tSensorSettings; +extern TI_TMP102_Compatible *tSensor; + +void Init_TSensor(uint8_t addr); +void UpdateFanControl(float temperature, PWM_Output* pwmOut); + + + diff --git a/include/my_wifi.h b/include/my_wifi.h new file mode 100644 index 0000000..2745c16 --- /dev/null +++ b/include/my_wifi.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include +#include + +extern volatile bool InternetAvailable; + +void Wifi_Init(void); +void Wifi_Load_Settings(String path); +void Wifi_Scan_for_Networks(void); +void Wifi_Start_MDNS(void); +void onWiFiEvent(WiFiEvent_t event); +bool Wifi_Save_Credentials(String path); +void Setup_WebServer_Handlers(AsyncWebServer& serv); + +void handlePOST_Update(AsyncWebServerRequest *request); +void updateCallback(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); +void updateFirmwareProgress(size_t progress, size_t total); + +void handleGET_Query(AsyncWebServerRequest *request); + +void handleFilesUpload_OnBody(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); + +void sendHtmlFile(const char* filePath, AsyncWebServerRequest *request, String (*callback)(const String&)); +String fileManagerHtmlProcessor(const String& var); +String HomeHtmlProcessor(const String& var); +String listDirAsHtml(String directoryList[], int count); + +const char* getFileExtension(const char* filename); +const char* getFileType(const char* ext); +const char* convertFileSize(const size_t bytes); +bool writeFile(fs::FS &fs, const char *path, const char *message); +char* readFile(fs::FS &fs, const char *path); + +String varReplace(const String& input, String (*callback)(const String&)); +String getSoftAPMacAddress(void); + +void Wifi_ConnectTask(void* parameter); +void Wifi_Check_Internet(void); +bool StartWifiConnectTask(String ssid, String pass); diff --git a/include/system.h b/include/system.h new file mode 100644 index 0000000..ad74af9 --- /dev/null +++ b/include/system.h @@ -0,0 +1,94 @@ +#pragma once + +#include +#include "my_board.h" +#include +#include "ATALights.h" + +typedef struct { + bool enabled; +}BTN_SETTINGS; + +typedef struct{ + bool enabled; + int freq; + float min; + float max; + float def; + float deltaRate; +}PWM_OUT_SETTINGS; + +typedef struct{ + bool enabled; + bool vision; + uint8_t pwmOutIndex; + uint8_t btnIndex; + float min; + float max; + float step; + uint8_t skipCount; +}RAMP_LIGHT_SETTINGS; + +typedef struct { + bool enabled; + int height; + int width; +}OLED_SETTINGS; + +typedef struct { + bool enabled; + uint8_t addr; + float setpoint1; + float fanPower1; + float setpoint2; + float fanPower2; + float hyst; + uint8_t pwmIndex; + int intervalMs; +}TSENSOR_SETTINGS; + +typedef struct { + bool enabled; + String name; + uint8_t core; +}BLE_SETTINGS; + +typedef struct { + bool txEnabled; + bool rxEnabled; +}RF_REMOTE_SETTINGS; + +typedef struct { + bool enabled; + String ssid; + String pass; +}WIFI_CLIENT_SETTINGS; + +typedef struct { + bool enabled; + String ssid; + String pass; + uint8_t ip[4]; + uint8_t gateway[4]; + uint8_t subnet[4]; +}WIFI_AP_SETTINGS; + +typedef struct { + bool enabled; +}ADC_SETTINGS; + +enum BOOTH_MODE { BOOTH_MODE_NONE=0, BOOTH_MODE_ROAMER=1, BOOTH_MODE_STIK=2 }; +typedef struct { + BOOTH_MODE mode; + BOARD_PINS boardPins; + BTN_SETTINGS btnSettings[3]; + PWM_OUT_SETTINGS pwmOutSettings[4]; + RAMP_LIGHT_SETTINGS rampLightSettings[2]; + OLED_SETTINGS oledSettings; + TSENSOR_SETTINGS tSensorSettings; + LEDSTRIP_SETTINGS* ledStripSettings[2]; + BLE_SETTINGS bleSettings; + RF_REMOTE_SETTINGS rfRemoteSettings; +}SYS_SETTINGS; + + diff --git a/lib/AnyRtttl/.github/workflows/build_linux.yml b/lib/AnyRtttl/.github/workflows/build_linux.yml new file mode 100644 index 0000000..8aa6d36 --- /dev/null +++ b/lib/AnyRtttl/.github/workflows/build_linux.yml @@ -0,0 +1,182 @@ +name: Linux + +on: [push, pull_request] + +env: + # build Configuration, i.e. Debug, Release, etc. + PRODUCT_BUILD_TYPE: Release + + # build Configuration, i.e. Debug, Release, etc. + Configuration: Release + + # Required for Github Action. Unit test TestProcess.testKillAndTerminate fails to start /bin/nano with the following error: "Error opening terminal: unknown." + TERM: xterm + +jobs: + build: + # For a list of available runner types, refer to + # https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Configure GIT + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: bash + run: | + git config --local core.autocrlf true + git config --local advice.detachedHead false + git submodule update --init --recursive + + - name: Setup python + uses: actions/setup-python@v3 + with: + python-version: '3.x' # Version range or exact version of a Python version to use, using SemVer's version range syntax + architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified + + - name: Create new environment variables + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: bash + run: | + python -c "import os; print('GIT_REPOSITORY=' + os.path.split(os.getenv('GITHUB_REPOSITORY'))[1]);" >> $GITHUB_ENV + python -c "import os; print('GIT_BRANCH=' + os.path.split(os.getenv('GITHUB_REF'))[1]);" >> $GITHUB_ENV + echo GITHUB_WORKFLOW=$GITHUB_WORKFLOW>> $GITHUB_ENV + + - name: List environment variables for debugging + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: bash + run: | + env + + - name: Configure user profile and directories + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: bash + run: | + mkdir ~/Documents #required for TestUser.testFoldersExisting() because /home/travis/Documents does not exists! + mkdir ~/Desktop #required for TestUser.testFoldersExisting() because /home/travis/Desktop does not exists! + + - name: Deploy 'tests not available' badge before building + uses: exuanbo/actions-deploy-gist@v1 + with: + token: ${{ secrets.BADGES }} + gist_id: 58cf6c72c08e706335337d5ef9ca48e8 + gist_file_name: ${{env.GIT_REPOSITORY}}.${{env.GIT_BRANCH}}.${{env.GITHUB_WORKFLOW}}.json + file_path: ./ci/github/tests_not_available.badge.json + + - name: Install Arduino CLI + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: bash + run: ./ci/github/install_arduinocli.sh + + - name: Install Arduino library dependnecies + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: bash + run: ./ci/github/arduino_install_libraries.sh + + - name: Install this arduino library + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: bash + run: ./ci/github/install_this.sh + + - name: Install Google Test + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: bash + run: ./ci/github/install_googletest.sh + + - name: Install RapidAssist + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: bash + run: ./ci/github/install_rapidassist.sh + + - name: Install win32arduino + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: bash + run: ./ci/github/install_win32arduino.sh + + - name: Build library + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: bash + run: ./ci/github/build_library.sh + + - name: Build Arduino sketch - Basic + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: bash + run: ./ci/github/arduino_build_sketch.sh Basic + + - name: Build Arduino sketch - BlockingProgramMemoryRtttl + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: bash + run: ./ci/github/arduino_build_sketch.sh BlockingProgramMemoryRtttl + + - name: Build Arduino sketch - BlockingRtttl + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: bash + run: ./ci/github/arduino_build_sketch.sh BlockingRtttl + + - name: Build Arduino sketch - BlockingWithNonBlocking + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: bash + run: ./ci/github/arduino_build_sketch.sh BlockingWithNonBlocking + + - name: Build Arduino sketch - NonBlockingProgramMemoryRtttl + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: bash + run: ./ci/github/arduino_build_sketch.sh NonBlockingProgramMemoryRtttl + + - name: Build Arduino sketch - NonBlockingRtttl + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: bash + run: ./ci/github/arduino_build_sketch.sh NonBlockingRtttl + + - name: Build Arduino sketch - NonBlockingStopBeforeEnd + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: bash + run: ./ci/github/arduino_build_sketch.sh NonBlockingStopBeforeEnd + + - name: Build Arduino sketch - Play10Bits + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: bash + run: ./ci/github/arduino_build_sketch.sh Play10Bits + + - name: Build Arduino sketch - Play16Bits + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: bash + run: ./ci/github/arduino_build_sketch.sh Play16Bits + + - name: Build Arduino sketch - Rtttl2Code + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: bash + run: ./ci/github/arduino_build_sketch.sh Rtttl2Code + + - name: Run unit tests + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: bash + run: ./ci/github/test_script.sh + + - name: Search unit test report file + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: bash + run: | + UNITTEST_REPORT_PATH=$(find . -name 'anyrtttl_unittest*.xml') + echo UNITTEST_REPORT_PATH=$UNITTEST_REPORT_PATH + echo UNITTEST_REPORT_PATH=$UNITTEST_REPORT_PATH >> $GITHUB_ENV + + - name: Create test badge + working-directory: ${{env.GITHUB_WORKSPACE}} + run: python ci/github/maketestbadge.py ${{env.UNITTEST_REPORT_PATH}} + + - name: Deploy test badge to gist + uses: exuanbo/actions-deploy-gist@v1 + with: + token: ${{ secrets.BADGES }} + gist_id: 58cf6c72c08e706335337d5ef9ca48e8 + gist_file_name: ${{env.GIT_REPOSITORY}}.${{env.GIT_BRANCH}}.${{env.GITHUB_WORKFLOW}}.json + file_path: ./badge.json + + - name: Archive test results + uses: actions/upload-artifact@v3 + with: + name: unit-test-results + path: build/bin/anyrtttl_unittest*.xml diff --git a/lib/AnyRtttl/.github/workflows/build_windows.yml b/lib/AnyRtttl/.github/workflows/build_windows.yml new file mode 100644 index 0000000..18a9db6 --- /dev/null +++ b/lib/AnyRtttl/.github/workflows/build_windows.yml @@ -0,0 +1,174 @@ +name: Windows + +on: [push, pull_request] + +env: + PlatformToolset: v142 + + # build platform, i.e. x86, x64, Any CPU. This setting is optional. + Platform: Win32 + + # build Configuration, i.e. Debug, Release, etc. + Configuration: Release + +jobs: + build: + # For a list of available runner types, refer to + # https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on + runs-on: windows-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Configure GIT + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: cmd + run: | + git config --local core.autocrlf true + git config --local advice.detachedHead false + git submodule update --init --recursive + + - name: Setup python + uses: actions/setup-python@v3 + with: + python-version: '3.x' # Version range or exact version of a Python version to use, using SemVer's version range syntax + architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified + + - name: Create new environment variables + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: cmd + run: | + python -c "import os; print('GIT_REPOSITORY=' + os.path.split(os.getenv('GITHUB_REPOSITORY'))[1]);" >> %GITHUB_ENV% + python -c "import os; print('GIT_BRANCH=' + os.path.split(os.getenv('GITHUB_REF'))[1]);" >> %GITHUB_ENV% + echo GITHUB_WORKFLOW=%GITHUB_WORKFLOW%>> %GITHUB_ENV% + + - name: List environment variables for debugging + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: cmd + run: | + set + + - name: Deploy 'tests not available' badge before building + uses: exuanbo/actions-deploy-gist@v1 + with: + token: ${{ secrets.BADGES }} + gist_id: 58cf6c72c08e706335337d5ef9ca48e8 + gist_file_name: ${{env.GIT_REPOSITORY}}.${{env.GIT_BRANCH}}.${{env.GITHUB_WORKFLOW}}.json + file_path: ./ci/github/tests_not_available.badge.json + + - name: Install Arduino CLI + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: cmd + run: call ci\github\install_arduinocli.bat + + - name: Install Arduino library dependnecies + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: cmd + run: call ci\github\arduino_install_libraries.bat + + - name: Install this arduino library + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: cmd + run: call ci\github\install_this.bat + + - name: Install Google Test + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: cmd + run: call ci\github\install_googletest.bat + + - name: Install RapidAssist + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: cmd + run: call ci\github\install_rapidassist.bat + + - name: Install win32arduino + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: cmd + run: call ci\github\install_win32arduino.bat + + - name: Build library + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: cmd + run: call ci\github\build_library.bat + + - name: Build Arduino sketch - Basic + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: cmd + run: call ci\github\arduino_build_sketch.bat Basic + + - name: Build Arduino sketch - BlockingProgramMemoryRtttl + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: cmd + run: call ci\github\arduino_build_sketch.bat BlockingProgramMemoryRtttl + + - name: Build Arduino sketch - BlockingRtttl + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: cmd + run: call ci\github\arduino_build_sketch.bat BlockingRtttl + + - name: Build Arduino sketch - BlockingWithNonBlocking + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: cmd + run: call ci\github\arduino_build_sketch.bat BlockingWithNonBlocking + + - name: Build Arduino sketch - NonBlockingProgramMemoryRtttl + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: cmd + run: call ci\github\arduino_build_sketch.bat NonBlockingProgramMemoryRtttl + + - name: Build Arduino sketch - NonBlockingRtttl + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: cmd + run: call ci\github\arduino_build_sketch.bat NonBlockingRtttl + + - name: Build Arduino sketch - NonBlockingStopBeforeEnd + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: cmd + run: call ci\github\arduino_build_sketch.bat NonBlockingStopBeforeEnd + + - name: Build Arduino sketch - Play10Bits + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: cmd + run: call ci\github\arduino_build_sketch.bat Play10Bits + + - name: Build Arduino sketch - Play16Bits + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: cmd + run: call ci\github\arduino_build_sketch.bat Play16Bits + + - name: Build Arduino sketch - Rtttl2Code + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: cmd + run: call ci\github\arduino_build_sketch.bat Rtttl2Code + + - name: Run unit tests + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: cmd + run: call ci\github\test_script.bat + + - name: Search unit test report file + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: cmd + run: | + for /f "tokens=* usebackq" %%f in (`dir/b/s build\bin\${{env.configuration}}\anyrtttl_unittest*.xml`) do (set UNITTEST_REPORT_PATH=%%f) + echo UNITTEST_REPORT_PATH=%UNITTEST_REPORT_PATH% + echo UNITTEST_REPORT_PATH=%UNITTEST_REPORT_PATH% >> %GITHUB_ENV% + + - name: Create test badge + working-directory: ${{env.GITHUB_WORKSPACE}} + run: python ci\github\maketestbadge.py ${{env.UNITTEST_REPORT_PATH}} + + - name: Deploy test badge to gist + uses: exuanbo/actions-deploy-gist@v1 + with: + token: ${{ secrets.BADGES }} + gist_id: 58cf6c72c08e706335337d5ef9ca48e8 + gist_file_name: ${{env.GIT_REPOSITORY}}.${{env.GIT_BRANCH}}.${{env.GITHUB_WORKFLOW}}.json + file_path: ./badge.json + + - name: Archive test results + uses: actions/upload-artifact@v3 + with: + name: unit-test-results + path: build\bin\${{env.Configuration}}\anyrtttl_unittest*.xml diff --git a/lib/AnyRtttl/.gitignore b/lib/AnyRtttl/.gitignore new file mode 100644 index 0000000..a874d7b --- /dev/null +++ b/lib/AnyRtttl/.gitignore @@ -0,0 +1,4 @@ +/build +/external +/third_parties +.vscode diff --git a/lib/AnyRtttl/.piopm b/lib/AnyRtttl/.piopm new file mode 100644 index 0000000..1e0f6b1 --- /dev/null +++ b/lib/AnyRtttl/.piopm @@ -0,0 +1 @@ +{"type": "library", "name": "AnyRtttl", "version": "2.3.0", "spec": {"owner": "end2endzone", "id": 8296, "name": "AnyRtttl", "requirements": null, "uri": null}} \ No newline at end of file diff --git a/lib/AnyRtttl/AUTHORS b/lib/AnyRtttl/AUTHORS new file mode 100644 index 0000000..ef27329 --- /dev/null +++ b/lib/AnyRtttl/AUTHORS @@ -0,0 +1,5 @@ +# This file contains a list of people who've made contribution to +# the project. People who commit code are encouraged to add +# their names here. Please keep the list sorted by first names. + +Antoine Beauchamp diff --git a/lib/AnyRtttl/CHANGES b/lib/AnyRtttl/CHANGES new file mode 100644 index 0000000..491a02e --- /dev/null +++ b/lib/AnyRtttl/CHANGES @@ -0,0 +1,32 @@ +Changes for 2.3 + +* Fixed issue #2 - Support for PROGMEM / FLASH melodies in non-blocking mode. + + +Changes for 2.2.1: + +* Fixed issue #4: error compiling example code. +* Fixed issue #5: Refactor build process to use Arduino CLI instead of Arduino IDE. + + +Changes for 2.2.0: + +* New feature: Build option `ANYRTTTL_BUILD_EXAMPLES` to enable/disable building AnyRtttl examples. +* New feature: Changed file/folder structure to be compatible with Arduino Library Manager. +* New feature: Using RapidAssist 0.5.0 and win32Arduino 2.3.1. + + +Changes for 2.1.229: + +* New feature: Implemented support for RTTTL in Program Memory (PROGMEM). + + +Changes for 2.0.179: + +* Library converted to AnyRtttl. +* First github release. +* Code originally release at http://www.end2endzone.com/anyrtttl-a-feature-rich-arduino-library-for-playing-rtttl-melodies/ + + +Changes for 1.0.0: +* Initial release of NonBlockingRtttl. diff --git a/lib/AnyRtttl/CMakeLists.txt b/lib/AnyRtttl/CMakeLists.txt new file mode 100644 index 0000000..1e51d78 --- /dev/null +++ b/lib/AnyRtttl/CMakeLists.txt @@ -0,0 +1,160 @@ +cmake_minimum_required(VERSION 3.4.3) +project(AnyRtttl) + +# Set the output folder where your program will be created +set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin) +set( LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin) + +############################################################################################################################################## +# Functions +############################################################################################################################################## +function(GIT_EXTERNAL DIR REPO_URL TAG) + # Find the name of the repository + get_filename_component(REPO_NAME ${REPO_URL} NAME_WE) + + # Compute output directory + set(REPO_DIR "${DIR}/${REPO_NAME}") + + if (EXISTS "${REPO_DIR}") + message(STATUS "Repository ${REPO_NAME} already exists in directory ${DIR}. Skipping git clone command.") + return() + endif() + + # Clone the repository + message(STATUS "git clone ${REPO_URL} ${REPO_DIR}") + execute_process( + COMMAND "${GIT_EXECUTABLE}" clone ${REPO_URL} ${REPO_DIR} + RESULT_VARIABLE returncode ERROR_VARIABLE error + WORKING_DIRECTORY "${DIR}") + if(returncode) + message(FATAL_ERROR "Clone failed: ${error}\n") + endif() + message(STATUS "git clone completed") + + # Checking out the required tag + message(STATUS "git checkout ${TAG}") + execute_process( + COMMAND "${GIT_EXECUTABLE}" checkout ${TAG} + RESULT_VARIABLE returncode ERROR_VARIABLE error + WORKING_DIRECTORY "${REPO_DIR}") + if(returncode) + message(FATAL_ERROR "Checkout failed: ${error}\n") + endif() + message(STATUS "git checkout completed") + + # Delete the .git folder to simulate an export of the repository + message(STATUS "Deleting ${REPO_DIR}/.git") + file(REMOVE_RECURSE "${REPO_DIR}/.git") + +endfunction() + +function(add_example name) + # Create custom example.cpp file which includes the ino sketch file. + SET(SOURCE_INO_FILE "${PROJECT_SOURCE_DIR}/examples/${name}/${name}.ino") + CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/examples.cpp.in" "${PROJECT_BINARY_DIR}/${name}/examples.cpp") + + add_executable(${name} + ${ARDUINO_LIBRARY_SOURCE_FILES} + ${SOURCE_INO_FILE} + "${PROJECT_BINARY_DIR}/${name}/examples.cpp" + ) + + target_include_directories(${name} PRIVATE ${PROJECT_SOURCE_DIR}/src ${BITREADER_SOURCE_DIR} win32arduino ) + target_link_libraries(${name} PRIVATE win32arduino rapidassist) + + set_property(GLOBAL PROPERTY USE_FOLDERS ON) + set_target_properties(${name} PROPERTIES FOLDER "examples") + + if(WIN32) + # 1>C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\include\ostream(743,1): warning C4530: C++ exception handler used, but unwind semantics are not enabled. Specify /EHsc + # 1>D:\dev\AnyRtttl\master\third_parties\win32Arduino\install\include\win32arduino-2.4.0\SerialPrinter.h(202): message : see reference to function template instantiation 'std::basic_ostream> &std::operator <<>(std::basic_ostream> &,const char *)' being compiled + set_target_properties(${name} PROPERTIES COMPILE_FLAGS "/wd4530") + endif() + +endfunction() + +############################################################################################################################################## +# Dependencies +############################################################################################################################################## +find_package(GTest REQUIRED) +find_package(rapidassist 0.5.0 REQUIRED) +find_package(win32arduino 2.3.1 REQUIRED) +find_package(Git REQUIRED) + +# Arduino BitReader library dependency +file(MAKE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/external") +GIT_EXTERNAL("${CMAKE_CURRENT_SOURCE_DIR}/external" "http://github.com/end2endzone/BitReader.git" "1.3.0") +set(BITREADER_SOURCE_DIR "${PROJECT_SOURCE_DIR}/external/BitReader/src") + +############################################################################################################################################## +# Project settings +############################################################################################################################################## + +# Build options +option(ANYRTTTL_BUILD_EXAMPLES "Build all example projects" OFF) + +# Prevents annoying warnings on MSVC +if (WIN32) + add_definitions(-D_CRT_SECURE_NO_WARNINGS) +endif() + +# Find all library source and unit test files +file( GLOB ARDUINO_LIBRARY_SOURCE_FILES ${PROJECT_SOURCE_DIR}/src/*.cpp ${PROJECT_SOURCE_DIR}/src/*.h ${BITREADER_SOURCE_DIR}/*.cpp ${BITREADER_SOURCE_DIR}/*.h) +file( GLOB ARDUINO_LIBRARY_TEST_FILES ${PROJECT_SOURCE_DIR}/test/*.cpp ${PROJECT_SOURCE_DIR}/test/*.h ) + +# Create unit test executable +add_executable(anyrtttl_unittest + ${ARDUINO_LIBRARY_SOURCE_FILES} + ${ARDUINO_LIBRARY_TEST_FILES} +) + +#include directories +target_include_directories(anyrtttl_unittest + PRIVATE ${PROJECT_SOURCE_DIR}/src # Arduino Library folder + ${GTEST_INCLUDE_DIR} + ${BITREADER_SOURCE_DIR} + win32arduino +) + +# Unit test projects requires to link with pthread if also linking with gtest +if(NOT WIN32) + set(PTHREAD_LIBRARIES -pthread) +endif() + +#link libraries +target_link_libraries(anyrtttl_unittest PRIVATE win32arduino rapidassist ${PTHREAD_LIBRARIES} ${GTEST_LIBRARIES} ) + +if(WIN32) + # 1>C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\include\ostream(743,1): warning C4530: C++ exception handler used, but unwind semantics are not enabled. Specify /EHsc + # 1>D:\dev\AnyRtttl\master\third_parties\win32Arduino\install\include\win32arduino-2.4.0\SerialPrinter.h(202): message : see reference to function template instantiation 'std::basic_ostream> &std::operator <<>(std::basic_ostream> &,const char *)' being compiled + set_target_properties(anyrtttl_unittest PROPERTIES COMPILE_FLAGS "/wd4530") +endif() + +# Copy `expected_call_stack.log` to expected locations +configure_file(${PROJECT_SOURCE_DIR}/test/expected_call_stack.log ${PROJECT_BINARY_DIR}/expected_call_stack.log COPYONLY) +set(TEST_FILES_BINARY_DIR ${EXECUTABLE_OUTPUT_PATH}) +if(WIN32) + set(TEST_FILES_BINARY_DIR ${TEST_FILES_BINARY_DIR}/${CMAKE_CFG_INTDIR}) +endif() +add_custom_command( + TARGET anyrtttl_unittest POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${PROJECT_SOURCE_DIR}/test/expected_call_stack.log + ${TEST_FILES_BINARY_DIR}/expected_call_stack.log) + +############################################################################################################################################## +# Add all samples to the project unless the user has specified otherwise. +############################################################################################################################################## +if(ANYRTTTL_BUILD_EXAMPLES) + add_example("Basic") + add_example("BlockingProgramMemoryRtttl") + add_example("BlockingRtttl") + add_example("BlockingWithNonBlocking") + add_example("NonBlockingProgramMemoryRtttl") + add_example("NonBlockingRtttl") + add_example("NonBlockingStopBeforeEnd") + add_example("Play10Bits") + add_example("Play16Bits") + add_example("Rtttl2Code") +endif() + \ No newline at end of file diff --git a/lib/AnyRtttl/INSTALL.md b/lib/AnyRtttl/INSTALL.md new file mode 100644 index 0000000..0174011 --- /dev/null +++ b/lib/AnyRtttl/INSTALL.md @@ -0,0 +1,105 @@ +# Install # + +The library can be found, installed, or updated from the Arduino IDE using the official Arduino Library Manager (available from IDE version 1.6.2). + + +The library can be installed on the system by following the same steps as with other Arduino library. + +Refer to [Installing Additional Arduino Libraries](https://www.arduino.cc/en/Guide/Libraries) tutorial for details on how to install a third party library. + + + + +# Dependencies # + +The AnyRtttl library have no dependencies on other Arduino library. However, some examples of the library requires external dependencies. + +The following Arduino Library must be installed on the system to use the library examples: + +* [BitReader v1.3.0](https://github.com/end2endzone/BitReader/tree/1.3.0) + + + + +# Build # + +The library unit tests can be build on Windows/Linux platform to maintain the product stability and level of quality. + +This section explains how to compile and build the software and how to get a test environment ready. + + + +## Prerequisites ## + + + +### Software Requirements ### + +The following software must be installed on the system before compiling unit test source code: + +* [Google C++ Testing Framework v1.8.0](https://github.com/google/googletest/tree/release-1.8.0) +* [RapidAssist v0.5.0](https://github.com/end2endzone/RapidAssist/tree/0.5.0) +* [win32Arduino v2.3.1](https://github.com/end2endzone/win32Arduino/tree/2.3.1) +* [CMake](http://www.cmake.org/) v3.4.3 (or newer) + + + +### Linux Requirements ### + +These are the base requirements to build source code: + + * GNU-compatible Make or gmake + * POSIX-standard shell + * A C++98-standard-compliant compiler + + + +### Windows Requirements ### + +* Microsoft Visual C++ 2010 or newer + + + +## Build steps ## + +The AnyRtttl unit test uses the CMake build system to generate a platform-specific build environment. CMake reads the CMakeLists.txt files, checks for installed dependencies and then generates files for the selected build system. + +The following steps show how to build the library: + +1) Download the source code from an existing [tags](https://github.com/end2endzone/AnyRtttl/tags) and extract the content to a local directory (for example `c:\projects\AnyRtttl` or `~/dev/AnyRtttl`). + +2) Open a Command Prompt (Windows) or Terminal (Linux) and browse to the project directory. + +3) Enter the following commands to generate the project files for your build system: +``` +mkdir build +cd build +cmake .. +``` + +4) Build the source code. + +**Windows** +``` +cmake --build . --config Release +``` + +**Linux** +``` +make +``` + + + + +# Testing # + +AnyRtttl comes with unit tests which help maintaining the product stability and level of quality. + +Test are build using the Google Test v1.8.0 framework. For more information on how googletest is working, see the [google test documentation primer](https://github.com/google/googletest/blob/release-1.8.0/googletest/docs/V1_6_Primer.md). + +To run tests, open a shell prompt and browse to the `build/bin` folder and run `anyrtttl_unittest` executable. For Windows users, the executable is located in `build\bin\Release`. + +Test results are saved in junit format in file `anyrtttl_unittest.release.xml`. + +The latest test results are available at the beginning of the [README.md](README.md) file. diff --git a/lib/AnyRtttl/LICENSE b/lib/AnyRtttl/LICENSE new file mode 100644 index 0000000..dd4add2 --- /dev/null +++ b/lib/AnyRtttl/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Antoine Beauchamp + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/AnyRtttl/README.md b/lib/AnyRtttl/README.md new file mode 100644 index 0000000..d26d7aa --- /dev/null +++ b/lib/AnyRtttl/README.md @@ -0,0 +1,523 @@ +![AnyRtttl logo](https://github.com/end2endzone/AnyRtttl/raw/master/docs/AnyRtttl-splashscreen.png) + + +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![Github Releases](https://img.shields.io/github/release/end2endzone/AnyRtttl.svg)](https://github.com/end2endzone/AnyRtttl/releases) + + + +# AnyRtttl # + +AnyRtttl is a feature rich arduino library for playing [RTTTL](http://www.end2endzone.com/anyrtttl-a-feature-rich-arduino-library-for-playing-rtttl-melodies/#Quick_recall_of_the_RTTTL_format) melodies. The library offers much more interesting features than relying on the widely available `void play_rtttl(char *p)` function. The library supports all best RTTTL features. + +Library features: + +* Really small increase in memory & code footprint compared to the usual blocking algorithm. +* Blocking & Non-Blocking modes available. +* Support custom `tone()`, `noTone()`, `delay()` and `millis()` functions. +* Compatible with external Tone libraries. +* Supports highly compressed RTTTL binary format. +* Supports RTTTL melodies stored in Program Memory (`PROGMEM`). +* Play two monolithic melodies on two different pins using 2 piezo buzzer with the help of an external Tone library. + + + +## Status ## + +Build: + +| Service | Build | Tests | +|----|-------|-------| +| AppVeyor | [![Build status](https://img.shields.io/appveyor/ci/end2endzone/AnyRtttl/master.svg?logo=appveyor)](https://ci.appveyor.com/project/end2endzone/AnyRtttl) | [![Tests status](https://img.shields.io/appveyor/tests/end2endzone/AnyRtttl/master.svg?logo=appveyor)](https://ci.appveyor.com/project/end2endzone/AnyRtttl/branch/master/tests) | +| Windows Server 2019 | [![Build on Windows](https://github.com/end2endzone/AnyRtttl/actions/workflows/build_windows.yml/badge.svg)](https://github.com/end2endzone/AnyRtttl/actions/workflows/build_windows.yml) | [![Tests on Windows](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/end2endzone/58cf6c72c08e706335337d5ef9ca48e8/raw/AnyRtttl.master.Windows.json)](https://github.com/end2endzone/AnyRtttl/actions/workflows/build_windows.yml) | +| Ubuntu 22.04 | [![Build on Linux](https://github.com/end2endzone/AnyRtttl/actions/workflows/build_linux.yml/badge.svg)](https://github.com/end2endzone/AnyRtttl/actions/workflows/build_linux.yml) | [![Tests on Linux](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/end2endzone/58cf6c72c08e706335337d5ef9ca48e8/raw/AnyRtttl.master.Linux.json)](https://github.com/end2endzone/AnyRtttl/actions/workflows/build_linux.yml) | + +Statistics: + +| AppVeyor | GitHub | +|--------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------| +| [![Statistics](https://buildstats.info/appveyor/chart/end2endzone/AnyRtttl)](https://ci.appveyor.com/project/end2endzone/AnyRtttl/branch/master) | [![Statistics](https://buildstats.info/github/chart/end2endzone/AnyRtttl)](https://github.com/end2endzone/AnyRtttl/actions) | + + + + +# Purpose # + +After publishing [NonBlockingRtttl](https://github.com/end2endzone/NonBlockingRTTTL) arduino library, I started using the library in more complex projects which was requiring other libraries. I quickly ran into the hell of library dependencies and library conflicts. I realized that I needed more features that could help me prototype faster. + +Other libraries available which allows you to "play" a melody in [RTTTL](http://www.end2endzone.com/anyrtttl-a-feature-rich-arduino-library-for-playing-rtttl-melodies/#Quick_recall_of_the_RTTTL_format) format suffer the same issue: they are based on blocking APIs or the RTTTL data is not optimized for space. + +AnyRtttl is different since it packs multiple RTTTL related features in a single library. It supports [blocking](https://en.wikipedia.org/wiki/Blocking_(computing)) & [non-blocking](http://en.wikipedia.org/wiki/Non-blocking_algorithm) API which makes it suitable to be used by more advanced algorithm. For instance, when using the non-blocking API, the melody can be stopped when a button is pressed. The library is also compatible with external Tone libraries and it supports highly compressed RTTTL binary formats. + + + +## Non-Blocking ## + +Most of the code that can "play" a melody on internet are build the same way: sequential calls to `tone()` and `delay()` functions using hardcoded values. This type of implementation might be good for robots but not for realtime application or projects that needs to monitor pins while the song is playing. + +With AnyRtttl non-blocking mode, your program can read/write IOs pins while playing and react on changes. Implementing a "stop" or "next song" push button is easy! + + + +## External Tone or Timer #0 libraries ## + +The AnyRtttl library is also flexible by allowing you to use the build-in arduino `tone()` and `noTone()` functions or an implementation from any external library which makes it compatible with any Tone library in the market. + +The library also supports custom `delay()` and `millis()` functions. If a project requires modification to the microcontroller's build-in Timer #0, the `millis()` function may be impacted and behave incorrectly. To maximize compatibility, one can supply a custom function which behaves like the original to prevent altering playback. + + + +## Binary RTTTL ## + +The AnyRtttl library also supports playing RTTTL data which is stored as binary data instead of text. This is actually a custom implementation of the RTTTL format. Using this format, one can achieve storing an highly compressed RTTTL melody which saves memory. + +See below for details on RTTTL binary format. + + + + +# Usage # + +The following instructions show how to use the library. + +Define `ANY_RTTTL_INFO` to enable the debugging of the library state on the serial port. + +Use `ANY_RTTTL_VERSION` to get the current version of the library. + +Note, the specified macros must be defined before including `anyrtttl.h` in your sketches. + + + +## Non-blocking mode ## + + anyrtttl::nonblocking::begin(BUZZER_PIN, mario); +Call `anyrtttl::nonblocking::begin()` to setup AnyRtttl library in non-blocking mode. + +Then call `anyrtttl::nonblocking::play()` to update the library's state and play notes as required. + +Use `anyrtttl::done()` or `anyrtttl::nonblocking::isPlaying()` to know if the library is done playing the given song. + +Anytime, one can call `anyrtttl::nonblocking::stop()` to stop playing the current song. + +The following code shows how to use the library in non-blocking mode: + +```cpp +#include +#include +#include + +//project's constants +#define BUZZER_PIN 8 +const char * tetris = "tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a"; +const char * arkanoid = "Arkanoid:d=4,o=5,b=140:8g6,16p,16g.6,2a#6,32p,8a6,8g6,8f6,8a6,2g6"; +const char * mario = "mario:d=4,o=5,b=100:16e6,16e6,32p,8e6,16c6,8e6,8g6,8p,8g,8p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,16p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16c7,16p,16c7,16c7,p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16d#6,8p,16d6,8p,16c6"; +byte songIndex = 0; //which song to play when the previous one finishes + +void setup() { + pinMode(BUZZER_PIN, OUTPUT); + + Serial.begin(115200); + Serial.println(); +} + +void loop() { + // If we are not playing something + if ( !anyrtttl::nonblocking::isPlaying() ) + { + // Play a song based on songIndex. + if (songIndex == 0) + anyrtttl::nonblocking::begin(BUZZER_PIN, tetris); + else if (songIndex == 1) + anyrtttl::nonblocking::begin(BUZZER_PIN, arkanoid); + else if (songIndex == 2) + anyrtttl::nonblocking::begin(BUZZER_PIN, mario); + + //Set songIndex ready for next song + songIndex++; + } + else + { + anyrtttl::nonblocking::play(); + } +} +``` + + + +## Playing RTTTL data stored in flash (program) memory ## + +AnyRtttl also supports RTTTL melodies stored in flash or Program Memory (PROGMEM). + +The `anyrtttl::nonblocking::begin()` function supports _Program Memory_ macros such as `FPSTR()` or `F()`. + +The following code shows how to use the library with RTTTL data stored in flash (program) memory instead of SRAM: + +```cpp +#include +#include +#include + +//project's constants +#define BUZZER_PIN 8 +const char tetris[] PROGMEM = "tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a"; +const char arkanoid[] PROGMEM = "Arkanoid:d=4,o=5,b=140:8g6,16p,16g.6,2a#6,32p,8a6,8g6,8f6,8a6,2g6"; +const char mario[] PROGMEM = "mario:d=4,o=5,b=100:16e6,16e6,32p,8e6,16c6,8e6,8g6,8p,8g,8p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,16p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16c7,16p,16c7,16c7,p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16d#6,8p,16d6,8p,16c6"; +// James Bond theme defined in inline code below (also stored in flash memory) +byte songIndex = 0; //which song to play when the previous one finishes + +void setup() { + pinMode(BUZZER_PIN, OUTPUT); + + Serial.begin(115200); + Serial.println(); +} + +void loop() { + // If we are not playing something + if ( !anyrtttl::nonblocking::isPlaying() ) + { + // Play a song based on songIndex. + if (songIndex == 0) + anyrtttl::nonblocking::beginProgMem(BUZZER_PIN, tetris); + else if (songIndex == 1) + anyrtttl::nonblocking::begin_P(BUZZER_PIN, arkanoid); + else if (songIndex == 2) + anyrtttl::nonblocking::begin(BUZZER_PIN, FPSTR(mario)); + else if (songIndex == 3) + anyrtttl::nonblocking::begin(BUZZER_PIN, F("Bond:d=4,o=5,b=80:32p,16c#6,32d#6,32d#6,16d#6,8d#6,16c#6,16c#6,16c#6,16c#6,32e6,32e6,16e6,8e6,16d#6,16d#6,16d#6,16c#6,32d#6,32d#6,16d#6,8d#6,16c#6,16c#6,16c#6,16c#6,32e6,32e6,16e6,8e6,16d#6,16d6,16c#6,16c#7,c.7,16g#6,16f#6,g#.6")); + + //Set songIndex ready for next song + songIndex++; + } + else + { + anyrtttl::nonblocking::play(); + } +} +``` + + + +# Advanced Usage # + + + +## Custom Tone function (a.k.a. RTTTL 2 code) ## + +This example shows how custom functions can be used by the AnyRtttl library to convert an RTTTL melody to arduino code. + +First define replacement functions like the following: + +```cpp +void serialTone(byte pin, uint16_t frequency, uint32_t duration) { + Serial.print("tone("); + Serial.print(pin); + Serial.print(","); + Serial.print(frequency); + Serial.print(","); + Serial.print(duration); + Serial.println(");"); +} + +void serialNoTone(byte pin) { + Serial.print("noTone("); + Serial.print(pin); + Serial.println(");"); +} + +void serialDelay(uint32_t duration) { + Serial.print("delay("); + Serial.print(duration); + Serial.println(");"); +} +``` + +Each new functions prints the function call & arguments to the serial port. + +In the `setup()` function, setup the AnyRtttl library to use the new functions: + +```cpp +//Use custom functions +anyrtttl::setToneFunction(&serialTone); +anyrtttl::setNoToneFunction(&serialNoTone); +anyrtttl::setDelayFunction(&serialDelay); +``` + +Use the `anyrtttl::blocking::play()` API for "playing" an RTTTL melody and monitor the output of the serial port to see the actual arduino code generated by the library. + +The following code shows how to use the library with custom functions: + +```cpp +#include +#include +#include + +//project's constants +#define BUZZER_PIN 8 +const char * tetris = "tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a"; + +//******************************************************************************************************************* +// The following replacement functions prints the function call & parameters to the serial port. +//******************************************************************************************************************* +void serialTone(byte pin, uint16_t frequency, uint32_t duration) { + Serial.print("tone("); + Serial.print(pin); + Serial.print(","); + Serial.print(frequency); + Serial.print(","); + Serial.print(duration); + Serial.println(");"); +} + +void serialNoTone(byte pin) { + Serial.print("noTone("); + Serial.print(pin); + Serial.println(");"); +} + +void serialDelay(uint32_t duration) { + Serial.print("delay("); + Serial.print(duration); + Serial.println(");"); +} + +void setup() { + pinMode(BUZZER_PIN, OUTPUT); + + Serial.begin(115200); + Serial.println(); + + //Use custom functions + anyrtttl::setToneFunction(&serialTone); + anyrtttl::setNoToneFunction(&serialNoTone); + anyrtttl::setDelayFunction(&serialDelay); +} + +void loop() { + anyrtttl::blocking::play(BUZZER_PIN, tetris); + + while(true) + { + } +} +``` + + +## Play 16 bits per note RTTTL ## + +Note that this mode requires that an RTTTL melody be already converted to 16-bits per note binary format. + +Use the `anyrtttl::blocking::play16Bits()` API for playing an RTTTL melody encoded as 16 bits per note. + +The following code shows how to use the library with 16-bits per note binary RTTTL: + +```cpp +#include +#include +#include + +//project's constants +#define BUZZER_PIN 8 + +//RTTTL 16 bits binary format for the following: tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a +const unsigned char tetris16[] = {0x0A, 0x14, 0x12, 0x02, 0x33, 0x01, 0x03, 0x02, 0x0B, 0x02, 0x14, 0x02, 0x0C, 0x02, 0x03, 0x02, 0x33, 0x01, 0x2A, 0x01, 0x2B, 0x01, 0x03, 0x02, 0x12, 0x02, 0x0B, 0x02, 0x03, 0x02, 0x32, 0x01, 0x33, 0x01, 0x03, 0x02, 0x0A, 0x02, 0x12, 0x02, 0x02, 0x02, 0x2A, 0x01, 0x29, 0x01, 0x3B, 0x01, 0x0A, 0x02, 0x1B, 0x02, 0x2A, 0x02, 0x23, 0x02, 0x1B, 0x02, 0x12, 0x02, 0x13, 0x02, 0x03, 0x02, 0x12, 0x02, 0x0B, 0x02, 0x03, 0x02, 0x32, 0x01, 0x33, 0x01, 0x03, 0x02, 0x0A, 0x02, 0x12, 0x02, 0x02, 0x02, 0x2A, 0x01, 0x2A, 0x01}; +const int tetris16_length = 42; + +void setup() { + pinMode(BUZZER_PIN, OUTPUT); + + Serial.begin(115200); + Serial.println(); +} + +void loop() { + anyrtttl::blocking::play16Bits(BUZZER_PIN, tetris16, tetris16_length); + + while(true) + { + } +} +``` + + + +## Play 10 bits per note RTTTL ## + +Note that this mode requires that an RTTTL melody be already converted to 10-bits per note binary format. + +Create a function that will be used by AnyRtttl library to read bits as required. The signature of the library must look like this: `uint16_t function_name(uint8_t numBits)`. + +Note that this code requires the [BitReader](https://github.com/end2endzone/BitReader) library to extract bits from the RTTTL binary buffer. The implementation of `readNextBits()` function delegates the job to the BitReader's `read()` method. + +In the `setup()` function, setup the external library that is used for reading bits: `bitreader.setBuffer(tetris10);`. + +Use the `anyrtttl::blocking::play10Bits()` API for playing an RTTTL melody encoded as 10 bits per note. The 3rd argument of the function requires a pointer to the function extracting bits: `&function_name`. + +The following code shows how to use the library with 10-bits per note binary RTTTL: + +```cpp +#include +#include +#include + +//The BitReader library is required for extracting 10 bit blocks from the RTTTL buffer. +//It can be installed from Arduino Library Manager or from https://github.com/end2endzone/BitReader/releases +#include + +//project's constants +#define BUZZER_PIN 8 + +//RTTTL 10 bits binary format for the following: tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a +const unsigned char tetris10[] = {0x0A, 0x14, 0x12, 0xCE, 0x34, 0xE0, 0x82, 0x14, 0x32, 0x38, 0xE0, 0x4C, 0x2A, 0xAD, 0x34, 0xA0, 0x84, 0x0B, 0x0E, 0x28, 0xD3, 0x4C, 0x03, 0x2A, 0x28, 0xA1, 0x80, 0x2A, 0xA5, 0xB4, 0x93, 0x82, 0x1B, 0xAA, 0x38, 0xE2, 0x86, 0x12, 0x4E, 0x38, 0xA0, 0x84, 0x0B, 0x0E, 0x28, 0xD3, 0x4C, 0x03, 0x2A, 0x28, 0xA1, 0x80, 0x2A, 0xA9, 0x04}; +const int tetris10_length = 42; + +//bit reader support +#ifndef USE_BITADDRESS_READ_WRITE +BitReader bitreader; +#else +BitAddress bitreader; +#endif +uint16_t readNextBits(uint8_t numBits) +{ + uint16_t bits = 0; + bitreader.read(numBits, &bits); + return bits; +} + +void setup() { + pinMode(BUZZER_PIN, OUTPUT); + + bitreader.setBuffer(tetris10); + + Serial.begin(115200); + Serial.println(); +} + +void loop() { + anyrtttl::blocking::play10Bits(BUZZER_PIN, tetris10_length, &readNextBits); + + while(true) + { + } +} +``` + + + +## Other ## + +More AnyRtttl examples are also available: + +* [Basic](examples/Basic/Basic.ino) +* [BlockingProgramMemoryRtttl](examples/BlockingProgramMemoryRtttl/BlockingProgramMemoryRtttl.ino) +* [BlockingRtttl](examples/BlockingRtttl/BlockingRtttl.ino) +* [BlockingWithNonBlocking](examples/BlockingWithNonBlocking/BlockingWithNonBlocking.ino) +* [NonBlockingProgramMemoryRtttl](examples/NonBlockingProgramMemoryRtttl/NonBlockingProgramMemoryRtttl.ino) +* [NonBlockingRtttl](examples/NonBlockingRtttl/NonBlockingRtttl.ino) +* [NonBlockingStopBeforeEnd](examples/NonBlockingStopBeforeEnd/NonBlockingStopBeforeEnd.ino) +* [Play10Bits](examples/Play10Bits/Play10Bits.ino) +* [Play16Bits](examples/Play16Bits/Play16Bits.ino) +* [Rtttl2Code](examples/Rtttl2Code/Rtttl2Code.ino) + + + + +# Binary RTTTL format definition # + +The following section defines the field order and size (in bits) required for encoding / decoding of each melody as binary RTTTL. + +This is actually a custom implementation of the RTTTL format. Using this format, one can achieve storing an highly compressed RTTTL melody which saves memory. + +Note that all fields definition are defined in LSB to MSB order. + +## Header ## + +The first 16 bits stores the RTTTL default section (a.k.a header) which is defined as the following: + +| Field name | Size (bits) | Range | Description | +|-------------------------|:-----------:|----------|-------------------------------------------------------------| +| Default duration index | 3 | [0, 7] | Matches the index used for `getNoteDurationFromIndex()` API | +| Default octave index | 2 | [0, 3] | Matches the index used for `getNoteOctaveFromIndex()` API. | +| Beats per minutes (BPM) | 10 | [1, 900] | | +| Padding | 1 | | | + +## Notes ## + +Next is each note's of the melody. Each note is encoded as 10 bits (or 16 bits) per note. Notes are defined as the following: + +| Field name | Size (bits) | Range | Description | +|--------------------|:-----------:|---------|--------------------------------------------------------------| +| Duration index | 3 | [0, 7] | Matches the index used for `getNoteDurationFromIndex()` API. | +| Note letter index | 3 | [0, 7] | Matches the index used for `getNoteLetterFromIndex()` API. | +| Pound | 1 | boolean | Defines if the note is pounded or not. | +| Dotted | 1 | boolean | Defines if the note is dotted or not. | +| Octave index | 2 | [0, 3] | Matches the index used for `getNoteOctaveFromIndex()` API. | +| Padding (optional) | 6 | | See description below. | + +The last field of a note (defined as `Padding`) is an optional 6 bits field. The AnyRtttl library supports both 10 bits per note and 16 bits per note definitions. Use the appropriate API for playing both format. + + + +## 10 bits per note (no padding) ## + +Each RTTTL note is encoded into 10 bits which is the minimum size of a note. This storage method is the best compression method for storing RTTTL melodies and reduces the usage of the dynamic memory to the minimum. + +However, since all notes are not aligned on multiple of 8 bits, addressing each note by an offset is impossible which makes the playback harder. Each notes must be deserialized one after the other from a buffer using blocks of 10 bits which increases the program storage space footprint. + +An external arduino library (or custom code) is required to allow AnyRtttl library to consume bits as needed. The arduino [BitReader](https://github.com/end2endzone/BitReader) library may be used for handling bit deserialization but any library that can extract a given number of bits from a buffer would work. + + + +## 16 bits per note (with padding) ## + +Each RTTTL note is encoded into 16 bits which is much better than the average 3.28 bytes per note text format. This storage method is optimum for storing RTTTL melodies and reduces the usage of the dynamic memory without increasing to much program storage space. + +All notes are aligned on 16 bits. Addressing each note by an offset allows an easy playback. Only the first 10 bits of each 16 bits block is used. The value of the padding field is undefined. + + + +## Playback ## + +The following AnyRtttl blocking APIs are available for playing both binary formats: +* 10 bits per note: `anyrtttl::blocking::play10Bits()`. +* 16 bits per note: `anyrtttl::blocking::play16Bits()`. + + + + +# Building # + +Please refer to file [INSTALL.md](INSTALL.md) for details on how installing/building the application. + + + + +# Platforms # + +AnyRtttl has been tested with the following platform: + + * Linux x86/x64 + * Windows x86/x64 + + + + +# Versioning # + +We use [Semantic Versioning 2.0.0](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/end2endzone/AnyRtttl/tags). + + + + +# Authors # + +* **Antoine Beauchamp** - *Initial work* - [end2endzone](https://github.com/end2endzone) + +See also the list of [contributors](https://github.com/end2endzone/AnyRtttl/blob/master/AUTHORS) who participated in this project. + + + + +# License # + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details diff --git a/lib/AnyRtttl/appveyor.yml b/lib/AnyRtttl/appveyor.yml new file mode 100644 index 0000000..88c6ab9 --- /dev/null +++ b/lib/AnyRtttl/appveyor.yml @@ -0,0 +1,81 @@ +#---------------------------------# +# general configuration # +#---------------------------------# + +# version format +version: "{branch} (#{build})" + +# branches to build +branches: + only: + - master + - /feature-issue.*/ + +#---------------------------------# +# environment configuration # +#---------------------------------# + +# Build worker image (VM template) +image: Visual Studio 2019 + +# scripts that are called at very beginning, before repo cloning +init: + - cmd: git config --global core.autocrlf true + - ps: $env:GIT_HASH=$env:APPVEYOR_REPO_COMMIT.Substring(0, 10) + +# clone directory +clone_folder: c:\projects\AnyRtttl + +# scripts that run after cloning repository +install: +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\install_arduinocli.bat +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\arduino_install_libraries.bat +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\install_this.bat +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\install_googletest.bat +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\install_rapidassist.bat +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\install_win32arduino.bat + +#---------------------------------# +# build configuration # +#---------------------------------# + +environment: + PlatformToolset: v142 + +# build platform, i.e. x86, x64, Any CPU. This setting is optional. +platform: Win32 + +# build Configuration, i.e. Debug, Release, etc. +configuration: Release + +build_script: +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\build_library.bat +- cmd: call %APPVEYOR_BUILD_FOLDER%\external\BitReader\ci\appveyor\arduino_library_install.bat +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\arduino_build_sketch.bat Basic +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\arduino_build_sketch.bat BlockingProgramMemoryRtttl +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\arduino_build_sketch.bat BlockingRtttl +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\arduino_build_sketch.bat BlockingWithNonBlocking +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\arduino_build_sketch.bat NonBlockingProgramMemoryRtttl +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\arduino_build_sketch.bat NonBlockingRtttl +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\arduino_build_sketch.bat NonBlockingStopBeforeEnd +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\arduino_build_sketch.bat Play10Bits +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\arduino_build_sketch.bat Play16Bits +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\arduino_build_sketch.bat Rtttl2Code + + +#---------------------------------# +# tests configuration # +#---------------------------------# + +# to run your custom scripts instead of automatic tests +test_script: +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\test_script.bat +- ps: . "$env:APPVEYOR_BUILD_FOLDER\ci\appveyor\UploadJUnitFiles.ps1" -Path "$env:APPVEYOR_BUILD_FOLDER\build\bin" + +#---------------------------------# +# artifacts configuration # +#---------------------------------# + +artifacts: +- path: build\bin\Release\anyrtttl_unittest.release.xml + name: anyrtttl_unittest.release.xml diff --git a/lib/AnyRtttl/ci/appveyor/UploadJUnitFiles.bat b/lib/AnyRtttl/ci/appveyor/UploadJUnitFiles.bat new file mode 100644 index 0000000..5b6f14b --- /dev/null +++ b/lib/AnyRtttl/ci/appveyor/UploadJUnitFiles.bat @@ -0,0 +1,4 @@ +@echo off +cd c:\temp +powershell -nologo -executionpolicy bypass -File "%~dpn0.ps1" +pause diff --git a/lib/AnyRtttl/ci/appveyor/UploadJUnitFiles.ps1 b/lib/AnyRtttl/ci/appveyor/UploadJUnitFiles.ps1 new file mode 100644 index 0000000..5279e82 --- /dev/null +++ b/lib/AnyRtttl/ci/appveyor/UploadJUnitFiles.ps1 @@ -0,0 +1,39 @@ +Param( + [Parameter(Mandatory=$True)] + [string]$Path +) + +function IsJUnitFile([string]$Path) +{ + $JUnitSignature = "NUL 1>NUL +xcopy /s /y *.* %outdir% + +::Zip content +del %outdir%.zip >nul 2>nul +"C:\Program Files\7-Zip\7z" a %outdir%.zip %outdir% -xr!build -xr!third_parties + +::Open an explorer windows with the generated file selected +start "" explorer.exe /select,"%outdir%.zip" + +endlocal diff --git a/lib/AnyRtttl/ci/generic/patch.py b/lib/AnyRtttl/ci/generic/patch.py new file mode 100644 index 0000000..82ebcd6 --- /dev/null +++ b/lib/AnyRtttl/ci/generic/patch.py @@ -0,0 +1,70 @@ +#!/usr/bin/python + +import sys +import getopt +import argparse +import os +import os.path + +def check_file_exists(file_path) : + check_file = os.path.exists(file_path) + if not check_file: + print('File not found: ', file_path) + sys.exit(2) + +def read_file(file_path) : + content = '' + try: + with open(file_path) as f: + content=f.read() + except IOError: + print("Failed reading file: ", file_path) + sys.exit(3) + return content + +def main(argv): + argParser = argparse.ArgumentParser() + argParser.add_argument("-i", "--ifile", help="Input file") + argParser.add_argument("-o", "--ofile", help="Output file") + argParser.add_argument("-p", "--pfile", help="Pattern file") + argParser.add_argument("-r", "--rfile", help="Replace file") + #argParser.add_argument("-i", "--int", type=int, help="your numeric age ") + + args = argParser.parse_args() + #print("args=%s" % args) + + print("Patching file: ", args.ifile) + print("as output file: ", args.ofile) + + check_file_exists(args.ifile) + check_file_exists(args.ofile) + check_file_exists(args.pfile) + check_file_exists(args.rfile) + + # Read search and replace patterns + old_text = read_file(args.pfile) + new_text = read_file(args.rfile) + # print("Pattern: ", old_text) + # print("Replace: ", new_text) + + # Read input file + old_content = read_file(args.ifile) + + # Do the actual search and replace + new_content = old_content.replace(old_text, new_text) + + # Write output file + try: + with open(args.ofile, "w") as f: + f.write(new_content) + except IOError: + print("Failed writing file: ", args.ofile) + sys.exit(3) + + if old_content == new_content: + print("Warning! The given pattern was not found in input file.") + else: + print("success!") + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/lib/AnyRtttl/ci/generic/win32arduino.pattern.txt b/lib/AnyRtttl/ci/generic/win32arduino.pattern.txt new file mode 100644 index 0000000..c11ec6e --- /dev/null +++ b/lib/AnyRtttl/ci/generic/win32arduino.pattern.txt @@ -0,0 +1,2 @@ +#define F(expr) expr +#define PROGMEM \ No newline at end of file diff --git a/lib/AnyRtttl/ci/generic/win32arduino.replace.txt b/lib/AnyRtttl/ci/generic/win32arduino.replace.txt new file mode 100644 index 0000000..2822b49 --- /dev/null +++ b/lib/AnyRtttl/ci/generic/win32arduino.replace.txt @@ -0,0 +1,10 @@ +typedef struct __FlashStringHelper { + char c; +} __FlashStringHelper_t; +#ifndef F +# define F(expr) ((const __FlashStringHelper *)expr) +#endif +#ifndef FPSTR +# define FPSTR(expr) ((const __FlashStringHelper *)expr) +#endif +#define PROGMEM \ No newline at end of file diff --git a/lib/AnyRtttl/ci/github/arduino_build_sketch.bat b/lib/AnyRtttl/ci/github/arduino_build_sketch.bat new file mode 100644 index 0000000..46cb7a5 --- /dev/null +++ b/lib/AnyRtttl/ci/github/arduino_build_sketch.bat @@ -0,0 +1,11 @@ +@echo off + +:: Validate GitHub CI's environment +if "%GITHUB_WORKSPACE%"=="" ( + echo Please define 'GITHUB_WORKSPACE' environment variable. + exit /B 1 +) + +:: Call matching script for windows +call "%GITHUB_WORKSPACE%\ci\windows\%~n0.bat" "%~1" +if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/lib/AnyRtttl/ci/github/arduino_build_sketch.sh b/lib/AnyRtttl/ci/github/arduino_build_sketch.sh new file mode 100644 index 0000000..c9695af --- /dev/null +++ b/lib/AnyRtttl/ci/github/arduino_build_sketch.sh @@ -0,0 +1,12 @@ +# Any commands which fail will cause the shell script to exit immediately +set -e + +# Validate GitHub CI environment +if [ "$GITHUB_WORKSPACE" = "" ]; then + echo "Please define 'GITHUB_WORKSPACE' environment variable."; + exit 1; +fi + +# Call matching script for linux +this_filename=`basename "$0"` +$GITHUB_WORKSPACE/ci/linux/$this_filename "$1" diff --git a/lib/AnyRtttl/ci/github/arduino_install_libraries.bat b/lib/AnyRtttl/ci/github/arduino_install_libraries.bat new file mode 100644 index 0000000..46cb7a5 --- /dev/null +++ b/lib/AnyRtttl/ci/github/arduino_install_libraries.bat @@ -0,0 +1,11 @@ +@echo off + +:: Validate GitHub CI's environment +if "%GITHUB_WORKSPACE%"=="" ( + echo Please define 'GITHUB_WORKSPACE' environment variable. + exit /B 1 +) + +:: Call matching script for windows +call "%GITHUB_WORKSPACE%\ci\windows\%~n0.bat" "%~1" +if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/lib/AnyRtttl/ci/github/arduino_install_libraries.sh b/lib/AnyRtttl/ci/github/arduino_install_libraries.sh new file mode 100644 index 0000000..c9695af --- /dev/null +++ b/lib/AnyRtttl/ci/github/arduino_install_libraries.sh @@ -0,0 +1,12 @@ +# Any commands which fail will cause the shell script to exit immediately +set -e + +# Validate GitHub CI environment +if [ "$GITHUB_WORKSPACE" = "" ]; then + echo "Please define 'GITHUB_WORKSPACE' environment variable."; + exit 1; +fi + +# Call matching script for linux +this_filename=`basename "$0"` +$GITHUB_WORKSPACE/ci/linux/$this_filename "$1" diff --git a/lib/AnyRtttl/ci/github/build_library.bat b/lib/AnyRtttl/ci/github/build_library.bat new file mode 100644 index 0000000..3879bc5 --- /dev/null +++ b/lib/AnyRtttl/ci/github/build_library.bat @@ -0,0 +1,11 @@ +@echo off + +:: Validate GitHub CI's environment +if "%GITHUB_WORKSPACE%"=="" ( + echo Please define 'GITHUB_WORKSPACE' environment variable. + exit /B 1 +) + +:: Call matching script for windows +call "%GITHUB_WORKSPACE%\ci\windows\%~n0.bat" +if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/lib/AnyRtttl/ci/github/build_library.sh b/lib/AnyRtttl/ci/github/build_library.sh new file mode 100644 index 0000000..23af3c6 --- /dev/null +++ b/lib/AnyRtttl/ci/github/build_library.sh @@ -0,0 +1,12 @@ +# Any commands which fail will cause the shell script to exit immediately +set -e + +# Validate GitHub CI environment +if [ "$GITHUB_WORKSPACE" = "" ]; then + echo "Please define 'GITHUB_WORKSPACE' environment variable."; + exit 1; +fi + +# Call matching script for linux +this_filename=`basename "$0"` +$GITHUB_WORKSPACE/ci/linux/$this_filename diff --git a/lib/AnyRtttl/ci/github/install_arduinocli.bat b/lib/AnyRtttl/ci/github/install_arduinocli.bat new file mode 100644 index 0000000..539218c --- /dev/null +++ b/lib/AnyRtttl/ci/github/install_arduinocli.bat @@ -0,0 +1,15 @@ +@echo off + +:: Validate GitHub CI's environment +if "%GITHUB_WORKSPACE%"=="" ( + echo Please define 'GITHUB_WORKSPACE' environment variable. + exit /B 1 +) + +:: Call matching script for windows +call "%GITHUB_WORKSPACE%\ci\windows\%~n0.bat" +if %errorlevel% neq 0 exit /b %errorlevel% + +:: Remember installation directory +echo Remember ARDUINO_CLI_INSTALL_DIR as %ARDUINO_CLI_INSTALL_DIR% in %GITHUB_ENV% +echo ARDUINO_CLI_INSTALL_DIR=%ARDUINO_CLI_INSTALL_DIR%>> %GITHUB_ENV% diff --git a/lib/AnyRtttl/ci/github/install_arduinocli.sh b/lib/AnyRtttl/ci/github/install_arduinocli.sh new file mode 100644 index 0000000..690c37c --- /dev/null +++ b/lib/AnyRtttl/ci/github/install_arduinocli.sh @@ -0,0 +1,17 @@ +# Any commands which fail will cause the shell script to exit immediately +set -e + +# Validate GitHub CI environment +if [ "$GITHUB_WORKSPACE" = "" ]; then + echo "Please define 'GITHUB_WORKSPACE' environment variable."; + exit 1; +fi + +# Call matching script for linux +# and execute the script under the current shell instead of loading another one +this_filename=`basename "$0"` +. $GITHUB_WORKSPACE/ci/linux/$this_filename + +# Remember installation directory +echo Remember ARDUINO_CLI_INSTALL_DIR as $ARDUINO_CLI_INSTALL_DIR in $GITHUB_ENV +echo ARDUINO_CLI_INSTALL_DIR=$ARDUINO_CLI_INSTALL_DIR>> $GITHUB_ENV diff --git a/lib/AnyRtttl/ci/github/install_googletest.bat b/lib/AnyRtttl/ci/github/install_googletest.bat new file mode 100644 index 0000000..3879bc5 --- /dev/null +++ b/lib/AnyRtttl/ci/github/install_googletest.bat @@ -0,0 +1,11 @@ +@echo off + +:: Validate GitHub CI's environment +if "%GITHUB_WORKSPACE%"=="" ( + echo Please define 'GITHUB_WORKSPACE' environment variable. + exit /B 1 +) + +:: Call matching script for windows +call "%GITHUB_WORKSPACE%\ci\windows\%~n0.bat" +if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/lib/AnyRtttl/ci/github/install_googletest.sh b/lib/AnyRtttl/ci/github/install_googletest.sh new file mode 100644 index 0000000..23af3c6 --- /dev/null +++ b/lib/AnyRtttl/ci/github/install_googletest.sh @@ -0,0 +1,12 @@ +# Any commands which fail will cause the shell script to exit immediately +set -e + +# Validate GitHub CI environment +if [ "$GITHUB_WORKSPACE" = "" ]; then + echo "Please define 'GITHUB_WORKSPACE' environment variable."; + exit 1; +fi + +# Call matching script for linux +this_filename=`basename "$0"` +$GITHUB_WORKSPACE/ci/linux/$this_filename diff --git a/lib/AnyRtttl/ci/github/install_rapidassist.bat b/lib/AnyRtttl/ci/github/install_rapidassist.bat new file mode 100644 index 0000000..3879bc5 --- /dev/null +++ b/lib/AnyRtttl/ci/github/install_rapidassist.bat @@ -0,0 +1,11 @@ +@echo off + +:: Validate GitHub CI's environment +if "%GITHUB_WORKSPACE%"=="" ( + echo Please define 'GITHUB_WORKSPACE' environment variable. + exit /B 1 +) + +:: Call matching script for windows +call "%GITHUB_WORKSPACE%\ci\windows\%~n0.bat" +if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/lib/AnyRtttl/ci/github/install_rapidassist.sh b/lib/AnyRtttl/ci/github/install_rapidassist.sh new file mode 100644 index 0000000..23af3c6 --- /dev/null +++ b/lib/AnyRtttl/ci/github/install_rapidassist.sh @@ -0,0 +1,12 @@ +# Any commands which fail will cause the shell script to exit immediately +set -e + +# Validate GitHub CI environment +if [ "$GITHUB_WORKSPACE" = "" ]; then + echo "Please define 'GITHUB_WORKSPACE' environment variable."; + exit 1; +fi + +# Call matching script for linux +this_filename=`basename "$0"` +$GITHUB_WORKSPACE/ci/linux/$this_filename diff --git a/lib/AnyRtttl/ci/github/install_this.bat b/lib/AnyRtttl/ci/github/install_this.bat new file mode 100644 index 0000000..3879bc5 --- /dev/null +++ b/lib/AnyRtttl/ci/github/install_this.bat @@ -0,0 +1,11 @@ +@echo off + +:: Validate GitHub CI's environment +if "%GITHUB_WORKSPACE%"=="" ( + echo Please define 'GITHUB_WORKSPACE' environment variable. + exit /B 1 +) + +:: Call matching script for windows +call "%GITHUB_WORKSPACE%\ci\windows\%~n0.bat" +if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/lib/AnyRtttl/ci/github/install_this.sh b/lib/AnyRtttl/ci/github/install_this.sh new file mode 100644 index 0000000..23af3c6 --- /dev/null +++ b/lib/AnyRtttl/ci/github/install_this.sh @@ -0,0 +1,12 @@ +# Any commands which fail will cause the shell script to exit immediately +set -e + +# Validate GitHub CI environment +if [ "$GITHUB_WORKSPACE" = "" ]; then + echo "Please define 'GITHUB_WORKSPACE' environment variable."; + exit 1; +fi + +# Call matching script for linux +this_filename=`basename "$0"` +$GITHUB_WORKSPACE/ci/linux/$this_filename diff --git a/lib/AnyRtttl/ci/github/install_win32arduino.bat b/lib/AnyRtttl/ci/github/install_win32arduino.bat new file mode 100644 index 0000000..3879bc5 --- /dev/null +++ b/lib/AnyRtttl/ci/github/install_win32arduino.bat @@ -0,0 +1,11 @@ +@echo off + +:: Validate GitHub CI's environment +if "%GITHUB_WORKSPACE%"=="" ( + echo Please define 'GITHUB_WORKSPACE' environment variable. + exit /B 1 +) + +:: Call matching script for windows +call "%GITHUB_WORKSPACE%\ci\windows\%~n0.bat" +if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/lib/AnyRtttl/ci/github/install_win32arduino.sh b/lib/AnyRtttl/ci/github/install_win32arduino.sh new file mode 100644 index 0000000..23af3c6 --- /dev/null +++ b/lib/AnyRtttl/ci/github/install_win32arduino.sh @@ -0,0 +1,12 @@ +# Any commands which fail will cause the shell script to exit immediately +set -e + +# Validate GitHub CI environment +if [ "$GITHUB_WORKSPACE" = "" ]; then + echo "Please define 'GITHUB_WORKSPACE' environment variable."; + exit 1; +fi + +# Call matching script for linux +this_filename=`basename "$0"` +$GITHUB_WORKSPACE/ci/linux/$this_filename diff --git a/lib/AnyRtttl/ci/github/maketestbadge.py b/lib/AnyRtttl/ci/github/maketestbadge.py new file mode 100644 index 0000000..968bef4 --- /dev/null +++ b/lib/AnyRtttl/ci/github/maketestbadge.py @@ -0,0 +1,165 @@ +#!/usr/bin/python + +import sys + +import os +from os import path + +import io +import itertools as IT +import xml.etree.ElementTree as ET + +import platform + +LEVEL_SUCCESS = 0 +LEVEL_WARNING = 1 +LEVEL_ERROR = 2 + +# Queries examples: +# https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fend2endzone%2Ffa6f6ec2f8315e357405164b4d618ef4%2Fraw%2Fafe1c3be748e7a78bc905c322afbd9a24f1e328e%2Fmybadge.json +# https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fend2endzone%2Ffa6f6ec2f8315e357405164b4d618ef4%2Fraw%2F5df297ffc360897097974338d4d43ef69271831b%2Fmybadge.json +# https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fend2endzone%2Ffa6f6ec2f8315e357405164b4d618ef4%2Fraw%2Fmybadge.json + +def getNamedLogo(): + # Search the running CI/CD service from environment variables + namedLogo = "" + if os.getenv('APPVEYOR', "") != "": + namedLogo = "AppVeyor" + elif os.getenv('TRAVIS', "") != "": + namedLogo = "Travis CI" + elif os.getenv('JENKINS_URL', "") != "": + namedLogo = "Jenkins" + elif os.getenv('GITHUB_ACTIONS', "") != "": + if os.getenv('RUNNER_OS', "") == "macOS": + namedLogo = "Apple" + elif os.getenv('RUNNER_OS', "") == "Linux": + namedLogo = "Linux" + elif os.getenv('RUNNER_OS', "") == "Windows": + namedLogo = "Windows" + else: + namedLogo = "GitHub" + elif platform.system() == "Darwin": + namedLogo = "Apple" + elif platform.system() == "Linux": + namedLogo = "Linux" + elif platform.system() == "Windows": + namedLogo = "Windows" + return namedLogo + +def getColorFromLevel(level): + color = "" + if level == LEVEL_SUCCESS: + color = "Green" + elif level == LEVEL_WARNING: + color = "Orange" + else: + color = "Red" + return color + +def main(): + print("maketestbadge v1.1") + + # Validate input file + if len(sys.argv) == 2: + file_path = sys.argv[1] + else: + print("Create endpoint badge json files from junit report. See https://shields.io/endpoint for details.") + print("Missing input file. Please specify a path to a junit report.") + sys.exit(1); + if not path.isfile(file_path) or not path.exists(file_path): + print("File not found: " + file_path) + sys.exit(1); + + print("Creating badge from junit report '" + file_path + "' for https://shields.io/endpoint") + + # Parse the content of the file + try: + tree = ET.parse(file_path) + except ET.ParseError as err: + lineno, column = err.position + err.msg = "Failed parsing file at line=" + str(lineno) + ", column=" + str(column) + "." + #raise + print(err.msg) + sys.exit(1); + root = tree.getroot() + + # Find the important attributes + #testsuites = root.find('./testsuite') + try: + tests_count = root.attrib['tests'] + failures_count = root.attrib['failures'] + disabled_count = root.attrib['disabled'] + except KeyError as err: + err.msg = "Failed to find count of tests, failures or disabled values." + #raise + print(err.msg) + sys.exit(1); + tests_count = int(tests_count ) + failures_count = int(failures_count) + disabled_count = int(disabled_count) + success_count = tests_count - failures_count - disabled_count + print("Found " + str(tests_count) + " tests: " + str(success_count) + " success, " + str(failures_count) + " failures and " + str(disabled_count) + " disabled tests.") + + # Evaluate badge properties + # badge_level + if failures_count == 0: + badge_level = LEVEL_SUCCESS + elif failures_count == 1: + badge_level = LEVEL_WARNING + else: + badge_level = LEVEL_ERROR + + # badge_color, a.k.a message color + if badge_level == LEVEL_SUCCESS: + badge_color = "#4c1" #brightgreen + elif badge_level == LEVEL_WARNING: + badge_color = "#fe7d37" #orange + else: + badge_color = "#e05d44" #red + + # badge_message + if badge_level == LEVEL_SUCCESS: + badge_message = str(success_count) + " passed" + elif badge_level == LEVEL_WARNING: + badge_message = str(failures_count) + " failed" + else: + badge_message = str(failures_count) + " failed" + #if disabled_count > 0: + # badge_message = badge_message + ", " + str(disabled_count) + " disabled" + + # other + badge_schemaVersion = 1 + badge_label = "tests" + #badge_labelColor = "#5c5c5c" + badge_namedLogo = getNamedLogo() + badge_logoColor = "white" + + print("Creating badge: " + badge_namedLogo + ", " + getColorFromLevel(badge_level).lower() + ", " + badge_message) + + relative_path = "badge.json" + full_path = os.path.realpath(relative_path) + + # Save as badge.json + try: + text_file = open(full_path, "w") + text_file.write("{\n") + text_file.write(" \"schemaVersion\": {0},\n".format(badge_schemaVersion)) + text_file.write(" \"namedLogo\": \"{0}\",\n".format(badge_namedLogo)) + text_file.write(" \"logoColor\": \"{0}\",\n".format(badge_logoColor)) + text_file.write(" \"label\": \"{0}\",\n".format(badge_label)) + #text_file.write(" \"labelColor\": \"{0}\",\n".format(badge_labelColor)) + text_file.write(" \"color\": \"{0}\",\n".format(badge_color)) + text_file.write(" \"message\": \"{0}\"\n".format(badge_message)) + text_file.write("}\n") + text_file.close() + except OSError as err: + err.msg = "Failed to save badge.json." + #raise + print(err.msg) + sys.exit(1); + + print("Saved badge as " + full_path) + +if __name__ == "__main__": + main() + \ No newline at end of file diff --git a/lib/AnyRtttl/ci/github/test_script.bat b/lib/AnyRtttl/ci/github/test_script.bat new file mode 100644 index 0000000..3879bc5 --- /dev/null +++ b/lib/AnyRtttl/ci/github/test_script.bat @@ -0,0 +1,11 @@ +@echo off + +:: Validate GitHub CI's environment +if "%GITHUB_WORKSPACE%"=="" ( + echo Please define 'GITHUB_WORKSPACE' environment variable. + exit /B 1 +) + +:: Call matching script for windows +call "%GITHUB_WORKSPACE%\ci\windows\%~n0.bat" +if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/lib/AnyRtttl/ci/github/test_script.sh b/lib/AnyRtttl/ci/github/test_script.sh new file mode 100644 index 0000000..23af3c6 --- /dev/null +++ b/lib/AnyRtttl/ci/github/test_script.sh @@ -0,0 +1,12 @@ +# Any commands which fail will cause the shell script to exit immediately +set -e + +# Validate GitHub CI environment +if [ "$GITHUB_WORKSPACE" = "" ]; then + echo "Please define 'GITHUB_WORKSPACE' environment variable."; + exit 1; +fi + +# Call matching script for linux +this_filename=`basename "$0"` +$GITHUB_WORKSPACE/ci/linux/$this_filename diff --git a/lib/AnyRtttl/ci/github/tests_not_available.badge.json b/lib/AnyRtttl/ci/github/tests_not_available.badge.json new file mode 100644 index 0000000..d316982 --- /dev/null +++ b/lib/AnyRtttl/ci/github/tests_not_available.badge.json @@ -0,0 +1,8 @@ +{ + "schemaVersion": 1, + "namedLogo": "GitHub", + "logoColor": "white", + "label": "tests", + "color": "inactive", + "message": "not available" +} diff --git a/lib/AnyRtttl/ci/linux/arduino_build_sketch.sh b/lib/AnyRtttl/ci/linux/arduino_build_sketch.sh new file mode 100644 index 0000000..9316b21 --- /dev/null +++ b/lib/AnyRtttl/ci/linux/arduino_build_sketch.sh @@ -0,0 +1,30 @@ +# Any commands which fail will cause the shell script to exit immediately +set -e + +# Set PRODUCT_SOURCE_DIR root directory +if [ "$PRODUCT_SOURCE_DIR" = "" ]; then + RESTORE_DIRECTORY="$PWD" + cd "$(dirname "$0")" + cd ../.. + export PRODUCT_SOURCE_DIR="$PWD" + echo "PRODUCT_SOURCE_DIR set to '$PRODUCT_SOURCE_DIR'." + cd "$RESTORE_DIRECTORY" + unset RESTORE_DIRECTORY +fi + +# Check Arduino CLI installation +echo Expecting Arduino IDE installed in directory: $ARDUINO_CLI_INSTALL_DIR +echo Searching for arduino cli executable... +export PATH=$PATH:$ARDUINO_CLI_INSTALL_DIR +which arduino-cli +echo + +export ARDUINO_INO_FILE=$PRODUCT_SOURCE_DIR/examples/$1/$1.ino + +echo ========================================================================================================== +echo Compiling $ARDUINO_INO_FILE +echo ========================================================================================================== +cd $PRODUCT_SOURCE_DIR/examples/$1 +arduino-cli compile -b arduino:avr:nano:cpu=atmega328 $1.ino + +cd "$(dirname "$0")" diff --git a/lib/AnyRtttl/ci/linux/arduino_install_libraries.sh b/lib/AnyRtttl/ci/linux/arduino_install_libraries.sh new file mode 100644 index 0000000..f8d6d05 --- /dev/null +++ b/lib/AnyRtttl/ci/linux/arduino_install_libraries.sh @@ -0,0 +1,30 @@ +# Any commands which fail will cause the shell script to exit immediately +set -e + +# Set PRODUCT_SOURCE_DIR root directory +if [ "$PRODUCT_SOURCE_DIR" = "" ]; then + RESTORE_DIRECTORY="$PWD" + cd "$(dirname "$0")" + cd ../.. + export PRODUCT_SOURCE_DIR="$PWD" + echo "PRODUCT_SOURCE_DIR set to '$PRODUCT_SOURCE_DIR'." + cd "$RESTORE_DIRECTORY" + unset RESTORE_DIRECTORY +fi + +# Check Arduino CLI installation +echo Expecting Arduino IDE installed in directory: $ARDUINO_CLI_INSTALL_DIR +echo Searching for arduino cli executable... +export PATH=$PATH:$ARDUINO_CLI_INSTALL_DIR +which arduino-cli +echo + +echo ========================================================================================================== +echo Installing arduino library dependencies +echo ========================================================================================================== + +echo BitReader... +arduino-cli lib install BitReader +echo + +cd "$(dirname "$0")" diff --git a/lib/AnyRtttl/ci/linux/build_library.sh b/lib/AnyRtttl/ci/linux/build_library.sh new file mode 100644 index 0000000..b33a024 --- /dev/null +++ b/lib/AnyRtttl/ci/linux/build_library.sh @@ -0,0 +1,38 @@ +# Any commands which fail will cause the shell script to exit immediately +set -e + +# Validate mandatory environment variables +if [ "$PRODUCT_BUILD_TYPE" = "" ]; then + echo "Please define 'PRODUCT_BUILD_TYPE' environment variable."; + exit 1; +fi + +# Set PRODUCT_SOURCE_DIR root directory +if [ "$PRODUCT_SOURCE_DIR" = "" ]; then + RESTORE_DIRECTORY="$PWD" + cd "$(dirname "$0")" + cd ../.. + export PRODUCT_SOURCE_DIR="$PWD" + echo "PRODUCT_SOURCE_DIR set to '$PRODUCT_SOURCE_DIR'." + cd "$RESTORE_DIRECTORY" + unset RESTORE_DIRECTORY +fi + +unset CMAKE_PREFIX_PATH +export CMAKE_PREFIX_PATH="$CMAKE_PREFIX_PATH;$PRODUCT_SOURCE_DIR/third_parties/googletest/install" +export CMAKE_PREFIX_PATH="$CMAKE_PREFIX_PATH;$PRODUCT_SOURCE_DIR/third_parties/RapidAssist/install" +export CMAKE_PREFIX_PATH="$CMAKE_PREFIX_PATH;$PRODUCT_SOURCE_DIR/third_parties/win32Arduino/install" + +echo ============================================================================ +echo Generating AnyRtttl... +echo ============================================================================ +cd $PRODUCT_SOURCE_DIR +mkdir -p build +cd build +cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$CMAKE_PREFIX_PATH" -DANYRTTTL_BUILD_EXAMPLES=ON .. + +echo ============================================================================ +echo Compiling AnyRtttl... +echo ============================================================================ +cmake --build . +echo diff --git a/lib/AnyRtttl/ci/linux/install_arduinocli.sh b/lib/AnyRtttl/ci/linux/install_arduinocli.sh new file mode 100644 index 0000000..eea4d15 --- /dev/null +++ b/lib/AnyRtttl/ci/linux/install_arduinocli.sh @@ -0,0 +1,33 @@ +# Any commands which fail will cause the shell script to exit immediately +set -e + +# Set download filename +export ARDUINO_CLI_FILENAME="" +if [ "$RUNNER_OS" = "Linux" ]; then + export ARDUINO_CLI_FILENAME=arduino-cli_latest_Linux_64bit.tar.gz +elif [ "$RUNNER_OS" = "macOS" ]; then + export ARDUINO_CLI_FILENAME=arduino-cli_latest_macOS_64bit.tar.gz +fi + +# Download +echo Downloading file https://downloads.arduino.cc/arduino-cli/$ARDUINO_CLI_FILENAME +wget --no-verbose https://downloads.arduino.cc/arduino-cli/$ARDUINO_CLI_FILENAME +echo + +# Installing +export ARDUINO_CLI_INSTALL_DIR=$HOME/arduino-cli +echo Installing Arduino CLI to directory: $ARDUINO_CLI_INSTALL_DIR +mkdir -p $ARDUINO_CLI_INSTALL_DIR +tar xf $ARDUINO_CLI_FILENAME --directory $ARDUINO_CLI_INSTALL_DIR +echo + +# Verify +echo Searching for arduino cli executable... +export PATH=$PATH:$ARDUINO_CLI_INSTALL_DIR +which arduino-cli +arduino-cli version +echo + +echo Installing arduino:avr core... +arduino-cli core install arduino:avr +echo diff --git a/lib/AnyRtttl/ci/linux/install_googletest.sh b/lib/AnyRtttl/ci/linux/install_googletest.sh new file mode 100644 index 0000000..317c0b2 --- /dev/null +++ b/lib/AnyRtttl/ci/linux/install_googletest.sh @@ -0,0 +1,56 @@ +# Any commands which fail will cause the shell script to exit immediately +set -e + +# Validate mandatory environment variables +if [ "$PRODUCT_BUILD_TYPE" = "" ]; then + echo "Please define 'PRODUCT_BUILD_TYPE' environment variable."; + exit 1; +fi + +# Set PRODUCT_SOURCE_DIR root directory +if [ "$PRODUCT_SOURCE_DIR" = "" ]; then + RESTORE_DIRECTORY="$PWD" + cd "$(dirname "$0")" + cd ../.. + export PRODUCT_SOURCE_DIR="$PWD" + echo "PRODUCT_SOURCE_DIR set to '$PRODUCT_SOURCE_DIR'." + cd "$RESTORE_DIRECTORY" + unset RESTORE_DIRECTORY +fi + +# Prepare CMAKE parameters +export CMAKE_INSTALL_PREFIX="$PRODUCT_SOURCE_DIR/third_parties/googletest/install" +unset CMAKE_PREFIX_PATH +export CMAKE_PREFIX_PATH="$CMAKE_PREFIX_PATH;" + +echo ============================================================================ +echo Cloning googletest into $PRODUCT_SOURCE_DIR/third_parties/googletest +echo ============================================================================ +mkdir -p "$PRODUCT_SOURCE_DIR/third_parties" +cd "$PRODUCT_SOURCE_DIR/third_parties" +git clone "https://github.com/google/googletest.git" +cd googletest +echo + +echo Checking out version 1.8.0... +git -c advice.detachedHead=false checkout release-1.8.0 +echo + +echo ============================================================================ +echo Generating googletest... +echo ============================================================================ +mkdir -p build +cd build +cmake -Wno-dev -DCMAKE_BUILD_TYPE=$PRODUCT_BUILD_TYPE -DBUILD_SHARED_LIBS=OFF -DBUILD_GMOCK=OFF -DBUILD_GTEST=ON -DCMAKE_INSTALL_PREFIX="$CMAKE_INSTALL_PREFIX" -DCMAKE_PREFIX_PATH="$CMAKE_PREFIX_PATH" .. + +echo ============================================================================ +echo Compiling googletest... +echo ============================================================================ +cmake --build . -- -j4 +echo + +echo ============================================================================ +echo Installing googletest into $CMAKE_INSTALL_PREFIX +echo ============================================================================ +make install +echo diff --git a/lib/AnyRtttl/ci/linux/install_rapidassist.sh b/lib/AnyRtttl/ci/linux/install_rapidassist.sh new file mode 100644 index 0000000..3221bc5 --- /dev/null +++ b/lib/AnyRtttl/ci/linux/install_rapidassist.sh @@ -0,0 +1,56 @@ +# Any commands which fail will cause the shell script to exit immediately +set -e + +# Validate mandatory environment variables +if [ "$PRODUCT_BUILD_TYPE" = "" ]; then + echo "Please define 'PRODUCT_BUILD_TYPE' environment variable."; + exit 1; +fi + +# Set PRODUCT_SOURCE_DIR root directory +if [ "$PRODUCT_SOURCE_DIR" = "" ]; then + RESTORE_DIRECTORY="$PWD" + cd "$(dirname "$0")" + cd ../.. + export PRODUCT_SOURCE_DIR="$PWD" + echo "PRODUCT_SOURCE_DIR set to '$PRODUCT_SOURCE_DIR'." + cd "$RESTORE_DIRECTORY" + unset RESTORE_DIRECTORY +fi + +# Prepare CMAKE parameters +export CMAKE_INSTALL_PREFIX="$PRODUCT_SOURCE_DIR/third_parties/RapidAssist/install" +unset CMAKE_PREFIX_PATH +export CMAKE_PREFIX_PATH="$CMAKE_PREFIX_PATH;$PRODUCT_SOURCE_DIR/third_parties/googletest/install" + +echo ============================================================================ +echo Cloning RapidAssist into $PRODUCT_SOURCE_DIR/third_parties/RapidAssist +echo ============================================================================ +mkdir -p "$PRODUCT_SOURCE_DIR/third_parties" +cd "$PRODUCT_SOURCE_DIR/third_parties" +git clone "https://github.com/end2endzone/RapidAssist.git" +cd RapidAssist +echo + +echo Checking out version v0.10.2... +git -c advice.detachedHead=false checkout 0.10.2 +echo + +echo ============================================================================ +echo Generating RapidAssist... +echo ============================================================================ +mkdir -p build +cd build +cmake -Wno-dev -DCMAKE_BUILD_TYPE=$PRODUCT_BUILD_TYPE -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX="$CMAKE_INSTALL_PREFIX" -DCMAKE_PREFIX_PATH="$CMAKE_PREFIX_PATH" .. + +echo ============================================================================ +echo Compiling RapidAssist... +echo ============================================================================ +cmake --build . -- -j4 +echo + +echo ============================================================================ +echo Installing RapidAssist into $CMAKE_INSTALL_PREFIX +echo ============================================================================ +make install +echo diff --git a/lib/AnyRtttl/ci/linux/install_this.sh b/lib/AnyRtttl/ci/linux/install_this.sh new file mode 100644 index 0000000..95e8b91 --- /dev/null +++ b/lib/AnyRtttl/ci/linux/install_this.sh @@ -0,0 +1,19 @@ +# Any commands which fail will cause the shell script to exit immediately +set -e + +# Set PRODUCT_SOURCE_DIR root directory +if [ "$PRODUCT_SOURCE_DIR" = "" ]; then + RESTORE_DIRECTORY="$PWD" + cd "$(dirname "$0")" + cd ../.. + export PRODUCT_SOURCE_DIR="$PWD" + echo "PRODUCT_SOURCE_DIR set to '$PRODUCT_SOURCE_DIR'." + cd "$RESTORE_DIRECTORY" + unset RESTORE_DIRECTORY +fi + +# Create libraries folder for current user +mkdir -p $HOME/Arduino/libraries + +# Install current library to Arduino Library repository +ln -s $PRODUCT_SOURCE_DIR $HOME/Arduino/libraries/. diff --git a/lib/AnyRtttl/ci/linux/install_win32arduino.sh b/lib/AnyRtttl/ci/linux/install_win32arduino.sh new file mode 100644 index 0000000..51cd34e --- /dev/null +++ b/lib/AnyRtttl/ci/linux/install_win32arduino.sh @@ -0,0 +1,64 @@ +# Any commands which fail will cause the shell script to exit immediately +set -e + +# Validate mandatory environment variables +if [ "$PRODUCT_BUILD_TYPE" = "" ]; then + echo "Please define 'PRODUCT_BUILD_TYPE' environment variable."; + exit 1; +fi + +# Set PRODUCT_SOURCE_DIR root directory +if [ "$PRODUCT_SOURCE_DIR" = "" ]; then + RESTORE_DIRECTORY="$PWD" + cd "$(dirname "$0")" + cd ../.. + export PRODUCT_SOURCE_DIR="$PWD" + echo "PRODUCT_SOURCE_DIR set to '$PRODUCT_SOURCE_DIR'." + cd "$RESTORE_DIRECTORY" + unset RESTORE_DIRECTORY +fi + +# Prepare CMAKE parameters +export CMAKE_INSTALL_PREFIX="$PRODUCT_SOURCE_DIR/third_parties/win32Arduino/install" +unset CMAKE_PREFIX_PATH +export CMAKE_PREFIX_PATH="$CMAKE_PREFIX_PATH;$PRODUCT_SOURCE_DIR/third_parties/googletest/install" +export CMAKE_PREFIX_PATH="$CMAKE_PREFIX_PATH;$PRODUCT_SOURCE_DIR/third_parties/RapidAssist/install" + +echo ============================================================================ +echo Cloning win32Arduino into $PRODUCT_SOURCE_DIR/third_parties/win32Arduino +echo ============================================================================ +mkdir -p "$PRODUCT_SOURCE_DIR/third_parties" +cd "$PRODUCT_SOURCE_DIR/third_parties" +git clone "https://github.com/end2endzone/win32Arduino.git" +cd win32Arduino +echo + +echo Checking out version v2.4.0... +git -c advice.detachedHead=false checkout 2.4.0 +echo + +echo ============================================================================ +echo Patching win32Arduino for type __FlashStringHelper... +echo ============================================================================ +python "$PRODUCT_SOURCE_DIR/ci/generic/patch.py" -i "$PRODUCT_SOURCE_DIR/third_parties/win32Arduino/include/avr/pgmspace.h" -o "$PRODUCT_SOURCE_DIR/third_parties/win32Arduino/include/avr/pgmspace.h" -p "$PRODUCT_SOURCE_DIR/ci/generic/win32arduino.pattern.txt" -r "$PRODUCT_SOURCE_DIR/ci/generic/win32arduino.replace.txt" +echo + +echo ============================================================================ +echo Generating win32Arduino... +echo ============================================================================ +mkdir -p build +cd build +cmake -Wno-dev -DCMAKE_BUILD_TYPE=$PRODUCT_BUILD_TYPE -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX="$CMAKE_INSTALL_PREFIX" -DCMAKE_PREFIX_PATH="$CMAKE_PREFIX_PATH" .. +echo + +echo ============================================================================ +echo Compiling win32Arduino... +echo ============================================================================ +cmake --build . -- -j4 +echo + +echo ============================================================================ +echo Installing win32Arduino into $CMAKE_INSTALL_PREFIX +echo ============================================================================ +make install +echo diff --git a/lib/AnyRtttl/ci/linux/test_script.sh b/lib/AnyRtttl/ci/linux/test_script.sh new file mode 100644 index 0000000..efc793a --- /dev/null +++ b/lib/AnyRtttl/ci/linux/test_script.sh @@ -0,0 +1,33 @@ +# Any commands which fail will cause the shell script to exit immediately +set -e + +# Validate mandatory environment variables +if [ "$PRODUCT_BUILD_TYPE" = "" ]; then + echo "Please define 'PRODUCT_BUILD_TYPE' environment variable."; + exit 1; +fi + +# Set PRODUCT_SOURCE_DIR root directory +if [ "$PRODUCT_SOURCE_DIR" = "" ]; then + RESTORE_DIRECTORY="$PWD" + cd "$(dirname "$0")" + cd ../.. + export PRODUCT_SOURCE_DIR="$PWD" + echo "PRODUCT_SOURCE_DIR set to '$PRODUCT_SOURCE_DIR'." + cd "$RESTORE_DIRECTORY" + unset RESTORE_DIRECTORY +fi + +echo ============================================================================ +echo Running unit tests... +echo ============================================================================ +cd "$PRODUCT_SOURCE_DIR/build/bin" +if [ "$PRODUCT_BUILD_TYPE" = "Debug" ]; then + ./anyrtttl_unittest-d || true; #do not fail build even if a test fails. +else + ./anyrtttl_unittest || true; #do not fail build even if a test fails. +fi + +# Note: +# GitHub Action do not support uploading test results in a nice GUI. There is no build-in way to detect a failed test. +# Do not reset the error returned by unit test execution. This will actually fail the build and will indicate in GitHub that a test has failed. diff --git a/lib/AnyRtttl/ci/windows/arduino_build_sketch.bat b/lib/AnyRtttl/ci/windows/arduino_build_sketch.bat new file mode 100644 index 0000000..bcbc2fd --- /dev/null +++ b/lib/AnyRtttl/ci/windows/arduino_build_sketch.bat @@ -0,0 +1,33 @@ +@echo off + +:: Set PRODUCT_SOURCE_DIR root directory +setlocal enabledelayedexpansion +if "%PRODUCT_SOURCE_DIR%"=="" ( + :: Delayed expansion is required within parentheses https://superuser.com/questions/78496/variables-in-batch-file-not-being-set-when-inside-if + cd /d "%~dp0" + cd ..\.. + set PRODUCT_SOURCE_DIR=!CD! + cd ..\.. + echo PRODUCT_SOURCE_DIR set to '!PRODUCT_SOURCE_DIR!'. +) +endlocal & set PRODUCT_SOURCE_DIR=%PRODUCT_SOURCE_DIR% +echo. + +:: Check Arduino CLI installation +echo Expecting Arduino CLI installed in directory: %ARDUINO_CLI_INSTALL_DIR% +echo Searching for arduino cli executable... +set PATH=%PATH%;%ARDUINO_CLI_INSTALL_DIR% +where arduino-cli.exe +if %errorlevel% neq 0 exit /b %errorlevel% +echo. + +set ARDUINO_INO_FILE=%PRODUCT_SOURCE_DIR%\examples\%~1\%~1.ino + +echo ========================================================================================================== +echo Compiling %ARDUINO_INO_FILE% +echo ========================================================================================================== +cd /d "%PRODUCT_SOURCE_DIR%\examples\%~1" +arduino-cli compile -b arduino:avr:nano:cpu=atmega328 %~1.ino +if %errorlevel% neq 0 exit /b %errorlevel% + +cd /d "%~dp0" diff --git a/lib/AnyRtttl/ci/windows/arduino_install_libraries.bat b/lib/AnyRtttl/ci/windows/arduino_install_libraries.bat new file mode 100644 index 0000000..232efc9 --- /dev/null +++ b/lib/AnyRtttl/ci/windows/arduino_install_libraries.bat @@ -0,0 +1,33 @@ +@echo off + +:: Set PRODUCT_SOURCE_DIR root directory +setlocal enabledelayedexpansion +if "%PRODUCT_SOURCE_DIR%"=="" ( + :: Delayed expansion is required within parentheses https://superuser.com/questions/78496/variables-in-batch-file-not-being-set-when-inside-if + cd /d "%~dp0" + cd ..\.. + set PRODUCT_SOURCE_DIR=!CD! + cd ..\.. + echo PRODUCT_SOURCE_DIR set to '!PRODUCT_SOURCE_DIR!'. +) +endlocal & set PRODUCT_SOURCE_DIR=%PRODUCT_SOURCE_DIR% +echo. + +:: Check Arduino CLI installation +echo Expecting Arduino CLI installed in directory: %ARDUINO_CLI_INSTALL_DIR% +echo Searching for arduino cli executable... +set PATH=%PATH%;%ARDUINO_CLI_INSTALL_DIR% +where arduino-cli.exe +if %errorlevel% neq 0 exit /b %errorlevel% +echo. + +echo ========================================================================================================== +echo Installing arduino library dependencies +echo ========================================================================================================== + +echo BitReader +arduino-cli lib install BitReader +if %errorlevel% neq 0 exit /b %errorlevel% +echo. + +cd /d "%~dp0" diff --git a/lib/AnyRtttl/ci/windows/build_library.bat b/lib/AnyRtttl/ci/windows/build_library.bat new file mode 100644 index 0000000..a3cda5d --- /dev/null +++ b/lib/AnyRtttl/ci/windows/build_library.bat @@ -0,0 +1,48 @@ +@echo off + +:: Validate mandatory environment variables +if "%CONFIGURATION%"=="" ( + echo Please define 'Configuration' environment variable. + exit /B 1 +) +if "%PLATFORM%"=="" ( + echo Please define 'Platform' environment variable. + exit /B 1 +) + +:: Set PRODUCT_SOURCE_DIR root directory +setlocal enabledelayedexpansion +if "%PRODUCT_SOURCE_DIR%"=="" ( + :: Delayed expansion is required within parentheses https://superuser.com/questions/78496/variables-in-batch-file-not-being-set-when-inside-if + cd /d "%~dp0" + cd ..\.. + set PRODUCT_SOURCE_DIR=!CD! + cd ..\.. + echo PRODUCT_SOURCE_DIR set to '!PRODUCT_SOURCE_DIR!'. +) +endlocal & set PRODUCT_SOURCE_DIR=%PRODUCT_SOURCE_DIR% +echo. + +set CMAKE_PREFIX_PATH= +set CMAKE_PREFIX_PATH=%CMAKE_PREFIX_PATH%;%PRODUCT_SOURCE_DIR%\third_parties\googletest\install +set CMAKE_PREFIX_PATH=%CMAKE_PREFIX_PATH%;%PRODUCT_SOURCE_DIR%\third_parties\RapidAssist\install +set CMAKE_PREFIX_PATH=%CMAKE_PREFIX_PATH%;%PRODUCT_SOURCE_DIR%\third_parties\win32Arduino\install + +echo ============================================================================ +echo Generating AnyRtttl... +echo ============================================================================ +cd /d %PRODUCT_SOURCE_DIR% +mkdir build >NUL 2>NUL +cd build +cmake -DCMAKE_GENERATOR_PLATFORM=%Platform% -T %PlatformToolset% -DCMAKE_CXX_FLAGS=/D_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING -DCMAKE_PREFIX_PATH="%CMAKE_PREFIX_PATH%" -DANYRTTTL_BUILD_EXAMPLES=ON .. +if %errorlevel% neq 0 exit /b %errorlevel% + +echo ============================================================================ +echo Compiling AnyRtttl... +echo ============================================================================ +cmake --build . --config %Configuration% +if %errorlevel% neq 0 exit /b %errorlevel% +echo. + +::Return to launch folder +cd /d %~dp0 diff --git a/lib/AnyRtttl/ci/windows/install_arduinocli.bat b/lib/AnyRtttl/ci/windows/install_arduinocli.bat new file mode 100644 index 0000000..3e1e370 --- /dev/null +++ b/lib/AnyRtttl/ci/windows/install_arduinocli.bat @@ -0,0 +1,31 @@ +@echo off +cd /d %~dp0 + +:: Set download filename +set ARDUINO_CLI_FILENAME=arduino-cli_latest_Windows_64bit.zip + +:: Download +echo Downloading file https://downloads.arduino.cc/arduino-cli/%ARDUINO_CLI_FILENAME% +curl -fsSL -o "%TEMP%\%ARDUINO_CLI_FILENAME%" "https://downloads.arduino.cc/arduino-cli/%ARDUINO_CLI_FILENAME%" +if %errorlevel% neq 0 exit /b %errorlevel% +echo. + +:: Installing +set ARDUINO_CLI_INSTALL_DIR=%USERPROFILE%\Desktop\arduino-cli +echo Installing Arduino CLI to directory: %ARDUINO_CLI_INSTALL_DIR% +7z x "%TEMP%\%ARDUINO_CLI_FILENAME%" "-o%ARDUINO_CLI_INSTALL_DIR%" +echo. + +:: Verify +echo Searching for arduino cli executable... +set PATH=%ARDUINO_CLI_INSTALL_DIR%;%PATH% +where arduino-cli.exe +if %errorlevel% neq 0 exit /b %errorlevel% +arduino-cli version +echo. + +echo Installing arduino:avr core... +REM Use `--skip-post-install` on AppVeyor to skip UAC prompt which is blocking the build. +arduino-cli core install arduino:avr --skip-post-install +if %errorlevel% neq 0 exit /b %errorlevel% +echo. diff --git a/lib/AnyRtttl/ci/windows/install_googletest.bat b/lib/AnyRtttl/ci/windows/install_googletest.bat new file mode 100644 index 0000000..8942532 --- /dev/null +++ b/lib/AnyRtttl/ci/windows/install_googletest.bat @@ -0,0 +1,81 @@ +@echo off + +:: Validate mandatory environment variables +if "%CONFIGURATION%"=="" ( + echo Please define 'Configuration' environment variable. + exit /B 1 +) +if "%PLATFORM%"=="" ( + echo Please define 'Platform' environment variable. + exit /B 1 +) + +:: Set PRODUCT_SOURCE_DIR root directory +setlocal enabledelayedexpansion +if "%PRODUCT_SOURCE_DIR%"=="" ( + :: Delayed expansion is required within parentheses https://superuser.com/questions/78496/variables-in-batch-file-not-being-set-when-inside-if + cd /d "%~dp0" + cd ..\.. + set PRODUCT_SOURCE_DIR=!CD! + cd ..\.. + echo PRODUCT_SOURCE_DIR set to '!PRODUCT_SOURCE_DIR!'. +) +endlocal & set PRODUCT_SOURCE_DIR=%PRODUCT_SOURCE_DIR% +echo. + +:: Prepare CMAKE parameters +set CMAKE_INSTALL_PREFIX=%PRODUCT_SOURCE_DIR%\third_parties\googletest\install +set CMAKE_PREFIX_PATH= +set CMAKE_PREFIX_PATH=%CMAKE_PREFIX_PATH%; + +echo ============================================================================ +echo Cloning googletest into %PRODUCT_SOURCE_DIR%\third_parties\googletest +echo ============================================================================ +mkdir "%PRODUCT_SOURCE_DIR%\third_parties" >NUL 2>NUL +cd "%PRODUCT_SOURCE_DIR%\third_parties" +git clone "https://github.com/google/googletest.git" +cd googletest +echo. + +echo Checking out version 1.8.0... +git -c advice.detachedHead=false checkout release-1.8.0 +echo. + +echo ============================================================================ +echo Generating googletest... +echo ============================================================================ +mkdir build >NUL 2>NUL +cd build +cmake -Wno-dev -DCMAKE_GENERATOR_PLATFORM=%PLATFORM% -T %PLATFORMTOOLSET% -Dgtest_force_shared_crt=ON -DBUILD_GMOCK=OFF -DBUILD_GTEST=ON -DCMAKE_INSTALL_PREFIX="%CMAKE_INSTALL_PREFIX%" -DCMAKE_PREFIX_PATH="%CMAKE_PREFIX_PATH%" .. +if %errorlevel% neq 0 exit /b %errorlevel% +echo. + +echo. +echo. +echo ================================================================================== +echo Patching googletest to silence MSVC warning C4996 about deprecated 'std::tr1' namespace +echo ================================================================================== +echo add_definitions(-D_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING)>>..\googletest\CMakeLists.txt +echo add_definitions(-D_CRT_SECURE_NO_WARNINGS)>>..\googletest\CMakeLists.txt +cmake .. 1>NUL 2>NUL +echo Done patching. +echo. +echo. + +:: Continue with compilation +echo ============================================================================ +echo Compiling googletest... +echo ============================================================================ +cmake --build . --config %CONFIGURATION% -- -maxcpucount /m +if %errorlevel% neq 0 exit /b %errorlevel% +echo. + +echo ============================================================================ +echo Installing googletest into %CMAKE_INSTALL_PREFIX% +echo ============================================================================ +cmake --build . --config %CONFIGURATION% --target INSTALL +if %errorlevel% neq 0 exit /b %errorlevel% +echo. + +::Return to launch folder +cd /d "%~dp0" diff --git a/lib/AnyRtttl/ci/windows/install_rapidassist.bat b/lib/AnyRtttl/ci/windows/install_rapidassist.bat new file mode 100644 index 0000000..dccf3b4 --- /dev/null +++ b/lib/AnyRtttl/ci/windows/install_rapidassist.bat @@ -0,0 +1,68 @@ +@echo off + +:: Validate mandatory environment variables +if "%CONFIGURATION%"=="" ( + echo Please define 'Configuration' environment variable. + exit /B 1 +) +if "%PLATFORM%"=="" ( + echo Please define 'Platform' environment variable. + exit /B 1 +) + +:: Set PRODUCT_SOURCE_DIR root directory +setlocal enabledelayedexpansion +if "%PRODUCT_SOURCE_DIR%"=="" ( + :: Delayed expansion is required within parentheses https://superuser.com/questions/78496/variables-in-batch-file-not-being-set-when-inside-if + cd /d "%~dp0" + cd ..\.. + set PRODUCT_SOURCE_DIR=!CD! + cd ..\.. + echo PRODUCT_SOURCE_DIR set to '!PRODUCT_SOURCE_DIR!'. +) +endlocal & set PRODUCT_SOURCE_DIR=%PRODUCT_SOURCE_DIR% +echo. + +:: Prepare CMAKE parameters +set CMAKE_INSTALL_PREFIX=%PRODUCT_SOURCE_DIR%\third_parties\RapidAssist\install +set CMAKE_PREFIX_PATH= +set CMAKE_PREFIX_PATH=%CMAKE_PREFIX_PATH%;%PRODUCT_SOURCE_DIR%\third_parties\googletest\install; + +echo ============================================================================ +echo Cloning RapidAssist into %PRODUCT_SOURCE_DIR%\third_parties\RapidAssist +echo ============================================================================ +mkdir "%PRODUCT_SOURCE_DIR%\third_parties" >NUL 2>NUL +cd "%PRODUCT_SOURCE_DIR%\third_parties" +git clone "https://github.com/end2endzone/RapidAssist.git" +cd RapidAssist +echo. + +echo Checking out version v0.10.2... +git -c advice.detachedHead=false checkout 0.10.2 +echo. + +echo ============================================================================ +echo Generating RapidAssist... +echo ============================================================================ +mkdir build >NUL 2>NUL +cd build +cmake -Wno-dev -DCMAKE_GENERATOR_PLATFORM=%PLATFORM% -T %PLATFORMTOOLSET% -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX="%CMAKE_INSTALL_PREFIX%" -DCMAKE_PREFIX_PATH="%CMAKE_PREFIX_PATH%" .. +if %errorlevel% neq 0 exit /b %errorlevel% +echo. + +echo ============================================================================ +echo Compiling RapidAssist... +echo ============================================================================ +cmake --build . --config %CONFIGURATION% -- -maxcpucount /m +if %errorlevel% neq 0 exit /b %errorlevel% +echo. + +echo ============================================================================ +echo Installing RapidAssist into %CMAKE_INSTALL_PREFIX% +echo ============================================================================ +cmake --build . --config %CONFIGURATION% --target INSTALL +if %errorlevel% neq 0 exit /b %errorlevel% +echo. + +::Return to launch folder +cd /d "%~dp0" diff --git a/lib/AnyRtttl/ci/windows/install_this.bat b/lib/AnyRtttl/ci/windows/install_this.bat new file mode 100644 index 0000000..cebebe3 --- /dev/null +++ b/lib/AnyRtttl/ci/windows/install_this.bat @@ -0,0 +1,46 @@ +@echo off + +:: Set PRODUCT_SOURCE_DIR root directory +setlocal enabledelayedexpansion +if "%PRODUCT_SOURCE_DIR%"=="" ( + :: Delayed expansion is required within parentheses https://superuser.com/questions/78496/variables-in-batch-file-not-being-set-when-inside-if + cd /d "%~dp0" + cd ..\.. + set PRODUCT_SOURCE_DIR=!CD! + cd ..\.. + echo PRODUCT_SOURCE_DIR set to '!PRODUCT_SOURCE_DIR!'. +) +endlocal & set PRODUCT_SOURCE_DIR=%PRODUCT_SOURCE_DIR% +echo. + +:: Create libraries folder for current user +mkdir %USERPROFILE%\Documents\Arduino\libraries >NUL 2>NUL + +:: Navigate to root directory of repository +cd /d %~dp0 +cd ..\.. + +setlocal + +:: Copy properties as environment variables +FOR /F "tokens=1,2 delims==" %%G IN (library.properties) DO (set %%G=%%H) +echo Installing %name%-%version% for current user + +:: Cleanup +set installdir=%USERPROFILE%\Documents\Arduino\libraries\%name%-%version% +IF EXIST %installdir% ( + rmdir /S /Q %installdir% +) + +:: Copy +xcopy /S /Y %cd% %installdir%\ + +::Cleanup +IF EXIST %installdir%\build ( + rmdir /S /Q %installdir%\build +) +IF EXIST %installdir%\third_parties ( + rmdir /S /Q %installdir%\third_parties +) + +endlocal diff --git a/lib/AnyRtttl/ci/windows/install_win32arduino.bat b/lib/AnyRtttl/ci/windows/install_win32arduino.bat new file mode 100644 index 0000000..4870f7d --- /dev/null +++ b/lib/AnyRtttl/ci/windows/install_win32arduino.bat @@ -0,0 +1,76 @@ +@echo off + +:: Validate mandatory environment variables +if "%CONFIGURATION%"=="" ( + echo Please define 'Configuration' environment variable. + exit /B 1 +) +if "%PLATFORM%"=="" ( + echo Please define 'Platform' environment variable. + exit /B 1 +) + +:: Set PRODUCT_SOURCE_DIR root directory +setlocal enabledelayedexpansion +if "%PRODUCT_SOURCE_DIR%"=="" ( + :: Delayed expansion is required within parentheses https://superuser.com/questions/78496/variables-in-batch-file-not-being-set-when-inside-if + cd /d "%~dp0" + cd ..\.. + set PRODUCT_SOURCE_DIR=!CD! + cd ..\.. + echo PRODUCT_SOURCE_DIR set to '!PRODUCT_SOURCE_DIR!'. +) +endlocal & set PRODUCT_SOURCE_DIR=%PRODUCT_SOURCE_DIR% +echo. + +:: Prepare CMAKE parameters +set CMAKE_INSTALL_PREFIX=%PRODUCT_SOURCE_DIR%\third_parties\win32Arduino\install +set CMAKE_PREFIX_PATH= +set CMAKE_PREFIX_PATH=%CMAKE_PREFIX_PATH%;%PRODUCT_SOURCE_DIR%\third_parties\googletest\install; +set CMAKE_PREFIX_PATH=%CMAKE_PREFIX_PATH%;%PRODUCT_SOURCE_DIR%\third_parties\RapidAssist\install; + +echo ============================================================================ +echo Cloning win32Arduino into %PRODUCT_SOURCE_DIR%\third_parties\win32Arduino +echo ============================================================================ +mkdir "%PRODUCT_SOURCE_DIR%\third_parties" >NUL 2>NUL +cd "%PRODUCT_SOURCE_DIR%\third_parties" +git clone "https://github.com/end2endzone/win32Arduino.git" +cd win32Arduino +echo. + +echo Checking out version 2.4.0... +git -c advice.detachedHead=false checkout 2.4.0 +echo. + +echo ============================================================================ +echo Patching win32Arduino for type __FlashStringHelper... +echo ============================================================================ +python "%PRODUCT_SOURCE_DIR%\ci\generic\patch.py" -i "%PRODUCT_SOURCE_DIR%\third_parties\win32Arduino\include\avr\pgmspace.h" -o "%PRODUCT_SOURCE_DIR%\third_parties\win32Arduino\include\avr\pgmspace.h" -p "%PRODUCT_SOURCE_DIR%\ci\generic\win32arduino.pattern.txt" -r "%PRODUCT_SOURCE_DIR%\ci\generic\win32arduino.replace.txt" +if %errorlevel% neq 0 exit /b %errorlevel% +echo. + +echo ============================================================================ +echo Generating win32Arduino... +echo ============================================================================ +mkdir build >NUL 2>NUL +cd build +cmake -Wno-dev -DCMAKE_GENERATOR_PLATFORM=%PLATFORM% -T %PLATFORMTOOLSET% -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX="%CMAKE_INSTALL_PREFIX%" -DCMAKE_PREFIX_PATH="%CMAKE_PREFIX_PATH%" .. +if %errorlevel% neq 0 exit /b %errorlevel% +echo. + +echo ============================================================================ +echo Compiling win32Arduino... +echo ============================================================================ +cmake --build . --config %CONFIGURATION% -- -maxcpucount /m +if %errorlevel% neq 0 exit /b %errorlevel% +echo. + +echo ============================================================================ +echo Installing win32Arduino into %PRODUCT_SOURCE_DIR%\third_parties\win32Arduino\install +echo ============================================================================ +cmake --build . --config %CONFIGURATION% --target INSTALL +if %errorlevel% neq 0 exit /b %errorlevel% +echo. + +::Return to launch folder +cd /d "%~dp0" diff --git a/lib/AnyRtttl/ci/windows/test_script.bat b/lib/AnyRtttl/ci/windows/test_script.bat new file mode 100644 index 0000000..d04ff5b --- /dev/null +++ b/lib/AnyRtttl/ci/windows/test_script.bat @@ -0,0 +1,41 @@ +@echo off + +:: Validate mandatory environment variables +if "%CONFIGURATION%"=="" ( + echo Please define 'Configuration' environment variable. + exit /B 1 +) +if "%PLATFORM%"=="" ( + echo Please define 'Platform' environment variable. + exit /B 1 +) + +:: Set PRODUCT_SOURCE_DIR root directory +setlocal enabledelayedexpansion +if "%PRODUCT_SOURCE_DIR%"=="" ( + :: Delayed expansion is required within parentheses https://superuser.com/questions/78496/variables-in-batch-file-not-being-set-when-inside-if + cd /d "%~dp0" + cd ..\.. + set PRODUCT_SOURCE_DIR=!CD! + cd ..\.. + echo PRODUCT_SOURCE_DIR set to '!PRODUCT_SOURCE_DIR!'. +) +endlocal & set PRODUCT_SOURCE_DIR=%PRODUCT_SOURCE_DIR% +echo. + +echo ======================================================================= +echo Running unit tests... +echo ======================================================================= +cd /d "%PRODUCT_SOURCE_DIR%\build\bin\%CONFIGURATION%" +if "%CONFIGURATION%"=="Debug" ( + anyrtttl_unittest-d.exe +) else ( + anyrtttl_unittest.exe +) + +:: Note: +:: GitHub Action do not support uploading test results in a nice GUI. There is no build-in way to detect a failed test. +:: Do not reset the error returned by unit test execution. This will actually fail the build and will indicate in GitHub that a test has failed. +:: +:: Reset error in case of test case fail, this prevents a test failure to actually fail the build +exit /b 0 diff --git a/lib/AnyRtttl/examples.cpp.in b/lib/AnyRtttl/examples.cpp.in new file mode 100644 index 0000000..adbafa7 --- /dev/null +++ b/lib/AnyRtttl/examples.cpp.in @@ -0,0 +1,14 @@ +#include +#include "Arduino.h" +#include "@SOURCE_INO_FILE@" + +using namespace testarduino; + +int main(int argc, char* argv[]) +{ + printf("Calling setup()...\n"); + setup(); + + printf("Calling loop()...\n"); + loop(); +} diff --git a/lib/AnyRtttl/examples/Basic/Basic.ino b/lib/AnyRtttl/examples/Basic/Basic.ino new file mode 100644 index 0000000..4c7ba90 --- /dev/null +++ b/lib/AnyRtttl/examples/Basic/Basic.ino @@ -0,0 +1,22 @@ +#include +#include +#include + +//project's constants +#define BUZZER_PIN 8 +const char * tetris = "tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a"; + +void setup() { + pinMode(BUZZER_PIN, OUTPUT); + + Serial.begin(115200); + Serial.println(); +} + +void loop() { + anyrtttl::blocking::play(BUZZER_PIN, tetris); + + while(true) + { + } +} diff --git a/lib/AnyRtttl/examples/BlockingProgramMemoryRtttl/BlockingProgramMemoryRtttl.ino b/lib/AnyRtttl/examples/BlockingProgramMemoryRtttl/BlockingProgramMemoryRtttl.ino new file mode 100644 index 0000000..bf1da79 --- /dev/null +++ b/lib/AnyRtttl/examples/BlockingProgramMemoryRtttl/BlockingProgramMemoryRtttl.ino @@ -0,0 +1,35 @@ +#include +#include +#include + +//project's constants +#define BUZZER_PIN 8 +const char tetris[] PROGMEM = "tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a"; +const char arkanoid[] PROGMEM = "Arkanoid:d=4,o=5,b=140:8g6,16p,16g.6,2a#6,32p,8a6,8g6,8f6,8a6,2g6"; +const char mario[] PROGMEM = "mario:d=4,o=5,b=140:16e6,16e6,32p,8e6,16c6,8e6,8g6,8p,8g,8p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,16p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16c7,16p,16c7,16c7,p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16d#6,8p,16d6,8p,16c6"; +// James Bond theme defined in inline code below (also stored in flash memory) + +void setup() { + pinMode(BUZZER_PIN, OUTPUT); + + Serial.begin(115200); + Serial.println("ready"); +} + +void loop() { + anyrtttl::blocking::playProgMem(BUZZER_PIN, tetris); + delay(1000); + + anyrtttl::blocking::play_P(BUZZER_PIN, arkanoid); + delay(1000); + + anyrtttl::blocking::play(BUZZER_PIN, FPSTR(mario)); + delay(1000); + + anyrtttl::blocking::play(BUZZER_PIN, F("Bond:d=4,o=5,b=80:32p,16c#6,32d#6,32d#6,16d#6,8d#6,16c#6,16c#6,16c#6,16c#6,32e6,32e6,16e6,8e6,16d#6,16d#6,16d#6,16c#6,32d#6,32d#6,16d#6,8d#6,16c#6,16c#6,16c#6,16c#6,32e6,32e6,16e6,8e6,16d#6,16d6,16c#6,16c#7,c.7,16g#6,16f#6,g#.6")); + delay(1000); + + while(true) + { + } +} diff --git a/lib/AnyRtttl/examples/BlockingRtttl/BlockingRtttl.ino b/lib/AnyRtttl/examples/BlockingRtttl/BlockingRtttl.ino new file mode 100644 index 0000000..6156ecd --- /dev/null +++ b/lib/AnyRtttl/examples/BlockingRtttl/BlockingRtttl.ino @@ -0,0 +1,31 @@ +#include +#include +#include + +//project's constants +#define BUZZER_PIN 8 +const char * tetris = "tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a"; +const char * arkanoid = "Arkanoid:d=4,o=5,b=140:8g6,16p,16g.6,2a#6,32p,8a6,8g6,8f6,8a6,2g6"; +const char * mario = "mario:d=4,o=5,b=140:16e6,16e6,32p,8e6,16c6,8e6,8g6,8p,8g,8p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,16p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16c7,16p,16c7,16c7,p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16d#6,8p,16d6,8p,16c6"; + +void setup() { + pinMode(BUZZER_PIN, OUTPUT); + + Serial.begin(115200); + Serial.println("ready"); +} + +void loop() { + anyrtttl::blocking::play(BUZZER_PIN, tetris); + delay(1000); + + anyrtttl::blocking::play(BUZZER_PIN, arkanoid); + delay(1000); + + anyrtttl::blocking::play(BUZZER_PIN, mario); + delay(1000); + + while(true) + { + } +} diff --git a/lib/AnyRtttl/examples/BlockingWithNonBlocking/BlockingWithNonBlocking.ino b/lib/AnyRtttl/examples/BlockingWithNonBlocking/BlockingWithNonBlocking.ino new file mode 100644 index 0000000..9f6e4d0 --- /dev/null +++ b/lib/AnyRtttl/examples/BlockingWithNonBlocking/BlockingWithNonBlocking.ino @@ -0,0 +1,44 @@ +#include +#include +#include + +//project's constants +#define BUZZER_PIN 8 +const char * tetris = "tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a"; +const char * arkanoid = "Arkanoid:d=4,o=5,b=140:8g6,16p,16g.6,2a#6,32p,8a6,8g6,8f6,8a6,2g6"; +const char * mario = "mario:d=4,o=5,b=100:16e6,16e6,32p,8e6,16c6,8e6,8g6,8p,8g,8p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,16p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16c7,16p,16c7,16c7,p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16d#6,8p,16d6,8p,16c6"; + +void setup() { + pinMode(BUZZER_PIN, OUTPUT); + + Serial.begin(115200); + Serial.println(); +} + +void loop() { + + // Play tetris and wait until done playing before jumping to the next song. + anyrtttl::nonblocking::begin(BUZZER_PIN, tetris); + while( !anyrtttl::nonblocking::done() ) + { + anyrtttl::nonblocking::play(); + } + + // Play arkanoid and loop until done playing before jumping to the next song. + anyrtttl::nonblocking::begin(BUZZER_PIN, arkanoid); + while( !anyrtttl::nonblocking::done() ) + { + anyrtttl::nonblocking::play(); + } + + // Play mario and loop until done playing before looping again. + anyrtttl::nonblocking::begin(BUZZER_PIN, mario); + while( !anyrtttl::nonblocking::done() ) + { + anyrtttl::nonblocking::play(); + } + + while(true) + { + } +} diff --git a/lib/AnyRtttl/examples/NonBlockingProgramMemoryRtttl/NonBlockingProgramMemoryRtttl.ino b/lib/AnyRtttl/examples/NonBlockingProgramMemoryRtttl/NonBlockingProgramMemoryRtttl.ino new file mode 100644 index 0000000..e12f033 --- /dev/null +++ b/lib/AnyRtttl/examples/NonBlockingProgramMemoryRtttl/NonBlockingProgramMemoryRtttl.ino @@ -0,0 +1,41 @@ +#include +#include +#include + +//project's constants +#define BUZZER_PIN 8 +const char tetris[] PROGMEM = "tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a"; +const char arkanoid[] PROGMEM = "Arkanoid:d=4,o=5,b=140:8g6,16p,16g.6,2a#6,32p,8a6,8g6,8f6,8a6,2g6"; +const char mario[] PROGMEM = "mario:d=4,o=5,b=100:16e6,16e6,32p,8e6,16c6,8e6,8g6,8p,8g,8p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,16p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16c7,16p,16c7,16c7,p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16d#6,8p,16d6,8p,16c6"; +// James Bond theme defined in inline code below (also stored in flash memory) +byte songIndex = 0; //which song to play when the previous one finishes + +void setup() { + pinMode(BUZZER_PIN, OUTPUT); + + Serial.begin(115200); + Serial.println(); +} + +void loop() { + // If we are not playing something + if ( !anyrtttl::nonblocking::isPlaying() ) + { + // Play a song based on songIndex. + if (songIndex == 0) + anyrtttl::nonblocking::beginProgMem(BUZZER_PIN, tetris); + else if (songIndex == 1) + anyrtttl::nonblocking::begin_P(BUZZER_PIN, arkanoid); + else if (songIndex == 2) + anyrtttl::nonblocking::begin(BUZZER_PIN, FPSTR(mario)); + else if (songIndex == 3) + anyrtttl::nonblocking::begin(BUZZER_PIN, F("Bond:d=4,o=5,b=80:32p,16c#6,32d#6,32d#6,16d#6,8d#6,16c#6,16c#6,16c#6,16c#6,32e6,32e6,16e6,8e6,16d#6,16d#6,16d#6,16c#6,32d#6,32d#6,16d#6,8d#6,16c#6,16c#6,16c#6,16c#6,32e6,32e6,16e6,8e6,16d#6,16d6,16c#6,16c#7,c.7,16g#6,16f#6,g#.6")); + + //Set songIndex ready for next song + songIndex++; + } + else + { + anyrtttl::nonblocking::play(); + } +} diff --git a/lib/AnyRtttl/examples/NonBlockingRtttl/NonBlockingRtttl.ino b/lib/AnyRtttl/examples/NonBlockingRtttl/NonBlockingRtttl.ino new file mode 100644 index 0000000..fac679b --- /dev/null +++ b/lib/AnyRtttl/examples/NonBlockingRtttl/NonBlockingRtttl.ino @@ -0,0 +1,38 @@ +#include +#include +#include + +//project's constants +#define BUZZER_PIN 8 +const char * tetris = "tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a"; +const char * arkanoid = "Arkanoid:d=4,o=5,b=140:8g6,16p,16g.6,2a#6,32p,8a6,8g6,8f6,8a6,2g6"; +const char * mario = "mario:d=4,o=5,b=100:16e6,16e6,32p,8e6,16c6,8e6,8g6,8p,8g,8p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,16p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16c7,16p,16c7,16c7,p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16d#6,8p,16d6,8p,16c6"; +byte songIndex = 0; //which song to play when the previous one finishes + +void setup() { + pinMode(BUZZER_PIN, OUTPUT); + + Serial.begin(115200); + Serial.println(); +} + +void loop() { + // If we are not playing something + if ( !anyrtttl::nonblocking::isPlaying() ) + { + // Play a song based on songIndex. + if (songIndex == 0) + anyrtttl::nonblocking::begin(BUZZER_PIN, tetris); + else if (songIndex == 1) + anyrtttl::nonblocking::begin(BUZZER_PIN, arkanoid); + else if (songIndex == 2) + anyrtttl::nonblocking::begin(BUZZER_PIN, mario); + + //Set songIndex ready for next song + songIndex++; + } + else + { + anyrtttl::nonblocking::play(); + } +} diff --git a/lib/AnyRtttl/examples/NonBlockingStopBeforeEnd/NonBlockingStopBeforeEnd.ino b/lib/AnyRtttl/examples/NonBlockingStopBeforeEnd/NonBlockingStopBeforeEnd.ino new file mode 100644 index 0000000..7cffd2c --- /dev/null +++ b/lib/AnyRtttl/examples/NonBlockingStopBeforeEnd/NonBlockingStopBeforeEnd.ino @@ -0,0 +1,42 @@ +#include +#include +#include + +//project's constants +#define BUZZER_PIN 8 +const char * tetris = "tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a"; + +unsigned long playStart = 0; +bool firstPass = true; + +void setup() { + pinMode(BUZZER_PIN, OUTPUT); + + Serial.begin(115200); + Serial.println(); +} + +void loop() { + if (firstPass) { + anyrtttl::nonblocking::begin(BUZZER_PIN, tetris); + + //remember when we started playing the song + playStart = millis(); + + firstPass = false; + } + + //if we are playing something + if ( anyrtttl::nonblocking::isPlaying() ) { + + //does the melody been playing for more than 5 seconds ? + if ( millis() - playStart > 5000 ) + { + anyrtttl::nonblocking::stop(); + } + } + + //if anything available for playing, play it + anyrtttl::nonblocking::play(); + +} diff --git a/lib/AnyRtttl/examples/Play10Bits/Play10Bits.ino b/lib/AnyRtttl/examples/Play10Bits/Play10Bits.ino new file mode 100644 index 0000000..841ee7c --- /dev/null +++ b/lib/AnyRtttl/examples/Play10Bits/Play10Bits.ino @@ -0,0 +1,44 @@ +#include +#include +#include + +//The BitReader library is required for extracting 10 bit blocks from the RTTTL buffer. +//It can be installed from Arduino Library Manager or from https://github.com/end2endzone/BitReader/releases +#include + +//project's constants +#define BUZZER_PIN 8 + +//RTTTL 10 bits binary format for the following: tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a +const unsigned char tetris10[] = {0x0A, 0x14, 0x12, 0xCE, 0x34, 0xE0, 0x82, 0x14, 0x32, 0x38, 0xE0, 0x4C, 0x2A, 0xAD, 0x34, 0xA0, 0x84, 0x0B, 0x0E, 0x28, 0xD3, 0x4C, 0x03, 0x2A, 0x28, 0xA1, 0x80, 0x2A, 0xA5, 0xB4, 0x93, 0x82, 0x1B, 0xAA, 0x38, 0xE2, 0x86, 0x12, 0x4E, 0x38, 0xA0, 0x84, 0x0B, 0x0E, 0x28, 0xD3, 0x4C, 0x03, 0x2A, 0x28, 0xA1, 0x80, 0x2A, 0xA9, 0x04}; +const int tetris10_length = 42; + +//bit reader support +#ifndef USE_BITADDRESS_READ_WRITE +BitReader bitreader; +#else +BitAddress bitreader; +#endif +uint16_t readNextBits(uint8_t numBits) +{ + uint16_t bits = 0; + bitreader.read(numBits, &bits); + return bits; +} + +void setup() { + pinMode(BUZZER_PIN, OUTPUT); + + bitreader.setBuffer(tetris10); + + Serial.begin(115200); + Serial.println(); +} + +void loop() { + anyrtttl::blocking::play10Bits(BUZZER_PIN, tetris10_length, &readNextBits); + + while(true) + { + } +} diff --git a/lib/AnyRtttl/examples/Play16Bits/Play16Bits.ino b/lib/AnyRtttl/examples/Play16Bits/Play16Bits.ino new file mode 100644 index 0000000..ad18800 --- /dev/null +++ b/lib/AnyRtttl/examples/Play16Bits/Play16Bits.ino @@ -0,0 +1,25 @@ +#include +#include +#include + +//project's constants +#define BUZZER_PIN 8 + +//RTTTL 16 bits binary format for the following: tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a +const unsigned char tetris16[] = {0x0A, 0x14, 0x12, 0x02, 0x33, 0x01, 0x03, 0x02, 0x0B, 0x02, 0x14, 0x02, 0x0C, 0x02, 0x03, 0x02, 0x33, 0x01, 0x2A, 0x01, 0x2B, 0x01, 0x03, 0x02, 0x12, 0x02, 0x0B, 0x02, 0x03, 0x02, 0x32, 0x01, 0x33, 0x01, 0x03, 0x02, 0x0A, 0x02, 0x12, 0x02, 0x02, 0x02, 0x2A, 0x01, 0x29, 0x01, 0x3B, 0x01, 0x0A, 0x02, 0x1B, 0x02, 0x2A, 0x02, 0x23, 0x02, 0x1B, 0x02, 0x12, 0x02, 0x13, 0x02, 0x03, 0x02, 0x12, 0x02, 0x0B, 0x02, 0x03, 0x02, 0x32, 0x01, 0x33, 0x01, 0x03, 0x02, 0x0A, 0x02, 0x12, 0x02, 0x02, 0x02, 0x2A, 0x01, 0x2A, 0x01}; +const int tetris16_length = 42; + +void setup() { + pinMode(BUZZER_PIN, OUTPUT); + + Serial.begin(115200); + Serial.println(); +} + +void loop() { + anyrtttl::blocking::play16Bits(BUZZER_PIN, tetris16, tetris16_length); + + while(true) + { + } +} diff --git a/lib/AnyRtttl/examples/Rtttl2Code/Rtttl2Code.ino b/lib/AnyRtttl/examples/Rtttl2Code/Rtttl2Code.ino new file mode 100644 index 0000000..afb3c30 --- /dev/null +++ b/lib/AnyRtttl/examples/Rtttl2Code/Rtttl2Code.ino @@ -0,0 +1,52 @@ +#include +#include +#include + +//project's constants +#define BUZZER_PIN 8 +const char * tetris = "tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a"; + +//******************************************************************************************************************* +// The following replacement functions prints the function call & parameters to the serial port. +//******************************************************************************************************************* +void serialTone(uint8_t pin, unsigned int frequency, unsigned long duration) { + Serial.print("tone("); + Serial.print(pin); + Serial.print(","); + Serial.print(frequency); + Serial.print(","); + Serial.print(duration); + Serial.println(");"); +} + +void serialNoTone(uint8_t pin) { + Serial.print("noTone("); + Serial.print(pin); + Serial.println(");"); +} + +void serialDelay(unsigned long duration) { + Serial.print("delay("); + Serial.print(duration); + Serial.println(");"); +} + +void setup() { + pinMode(BUZZER_PIN, OUTPUT); + + Serial.begin(115200); + Serial.println(); + + //Use custom functions + anyrtttl::setToneFunction(&serialTone); + anyrtttl::setNoToneFunction(&serialNoTone); + anyrtttl::setDelayFunction(&serialDelay); +} + +void loop() { + anyrtttl::blocking::play(BUZZER_PIN, tetris); + + while(true) + { + } +} diff --git a/lib/AnyRtttl/keywords.txt b/lib/AnyRtttl/keywords.txt new file mode 100644 index 0000000..d327e0e --- /dev/null +++ b/lib/AnyRtttl/keywords.txt @@ -0,0 +1,16 @@ +anyrtttl KEYWORD1 +play16Bits KEYWORD2 +play10Bits KEYWORD2 +begin KEYWORD2 +beginProgMem KEYWORD2 +begin_P KEYWORD2 +play KEYWORD2 +playProgMem KEYWORD2 +play_P KEYWORD2 +stop KEYWORD2 +isPlaying KEYWORD2 +done KEYWORD2 +setToneFunction KEYWORD2 +setNoToneFunction KEYWORD2 +setDelayFunction KEYWORD2 +setMillisFunction KEYWORD2 diff --git a/lib/AnyRtttl/library.properties b/lib/AnyRtttl/library.properties new file mode 100644 index 0000000..f8c77a3 --- /dev/null +++ b/lib/AnyRtttl/library.properties @@ -0,0 +1,9 @@ +name=AnyRtttl +version=2.3 +author=Antoine Beauchamp +maintainer=Antoine Beauchamp +sentence=A feature rich arduino library for playing rtttl melodies. +paragraph=The AnyRtttl is a feature rich library which supports all best RTTTL features: Blocking & Non-Blocking modes, custom tone(), delay() and millis() functions, PROGMEM support, and much more. +category=Other +url=https://github.com/end2endzone/AnyRtttl +architectures=* diff --git a/lib/AnyRtttl/src/anyrtttl.cpp b/lib/AnyRtttl/src/anyrtttl.cpp new file mode 100644 index 0000000..df8ea12 --- /dev/null +++ b/lib/AnyRtttl/src/anyrtttl.cpp @@ -0,0 +1,672 @@ +// --------------------------------------------------------------------------- +// AUTHOR/LICENSE: +// The following code was written by Antoine Beauchamp. For other authors, see AUTHORS file. +// The code & updates for the library can be found at https://github.com/end2endzone/AnyRtttl +// MIT License: http://www.opensource.org/licenses/mit-license.php +// --------------------------------------------------------------------------- +#include "Arduino.h" +#include "anyrtttl.h" +#include "binrtttl.h" + +/********************************************************* + * RTTTL Library data + *********************************************************/ + +namespace anyrtttl +{ + +const uint16_t notes[] = { NOTE_SILENT, +NOTE_C4, NOTE_CS4, NOTE_D4, NOTE_DS4, NOTE_E4, NOTE_F4, NOTE_FS4, NOTE_G4, NOTE_GS4, NOTE_A4, NOTE_AS4, NOTE_B4, +NOTE_C5, NOTE_CS5, NOTE_D5, NOTE_DS5, NOTE_E5, NOTE_F5, NOTE_FS5, NOTE_G5, NOTE_GS5, NOTE_A5, NOTE_AS5, NOTE_B5, +NOTE_C6, NOTE_CS6, NOTE_D6, NOTE_DS6, NOTE_E6, NOTE_F6, NOTE_FS6, NOTE_G6, NOTE_GS6, NOTE_A6, NOTE_AS6, NOTE_B6, +NOTE_C7, NOTE_CS7, NOTE_D7, NOTE_DS7, NOTE_E7, NOTE_F7, NOTE_FS7, NOTE_G7, NOTE_GS7, NOTE_A7, NOTE_AS7, NOTE_B7 +}; + +#define isdigit(n) (n >= '0' && n <= '9') +typedef uint16_t TONE_DURATION; +static const byte NOTES_PER_OCTAVE = 12; + +const char * buffer = ""; +ReadCharFuncPtr readCharFunc = &readChar; +int bufferIndex = -32760; +byte default_dur = 4; +byte default_oct = 5; +RTTTL_BPM bpm = 63; +RTTTL_DURATION wholenote; +byte pin = -1; +unsigned long delayToNextNote = 0; //milliseconds before playing the next note +bool playing = false; +TONE_DURATION duration; +byte noteOffset; +RTTTL_OCTAVE_VALUE scale; +int tmpNumber; + +const char * readNumber(const char * iBuffer, int & oValue, ReadCharFuncPtr iReadCharFunc) +{ + oValue = 0; + while(isdigit(iReadCharFunc(iBuffer))) + { + oValue = (oValue * 10) + (readCharFunc(buffer) - '0'); + iBuffer++; + } + return iBuffer; +} + +void serialPrint(const char * iBuffer, ReadCharFuncPtr iReadCharFunc) +{ + char c = readCharFunc(iBuffer); + while(c) { + Serial.print(c); + iBuffer++; + c = readCharFunc(iBuffer); + } +} + +/**************************************************************************** + * Custom functions + ****************************************************************************/ + +ToneFuncPtr _tone = &tone; +NoToneFuncPtr _noTone = &noTone; +DelayFuncPtr _delay = &delay; +MillisFuncPtr _millis = &millis; + +void setToneFunction(ToneFuncPtr iFunc) { + _tone = iFunc; +} + +void setNoToneFunction(NoToneFuncPtr iFunc) { + _noTone = iFunc; +} + +void setDelayFunction(DelayFuncPtr iFunc) { + _delay = iFunc; +} + +void setMillisFunction(MillisFuncPtr iFunc) { + _millis = iFunc; +} + +char readChar(const char * iBuffer) { + return *iBuffer; +} + +char readChar_P(const char * iBuffer) { + return pgm_read_byte_near(iBuffer); +} + + + +/**************************************************************************** + * Blocking API + ****************************************************************************/ +namespace blocking +{ + +void play(byte iPin, const char * iBuffer, ReadCharFuncPtr iReadCharFunc) { + // Absolutely no error checking in here + + default_dur = 4; + default_oct = 6; + bpm = 63; + buffer = iBuffer; + readCharFunc = iReadCharFunc; + + #ifdef ANY_RTTTL_DEBUG + Serial.print("playing: "); + serialPrint(buffer, readCharFunc); + Serial.println(); + #endif + + // format: d=N,o=N,b=NNN: + // find the start (skip name, etc) + + while(readCharFunc(buffer) != ':') buffer++; // ignore name + buffer++; // skip ':' + + // get default duration + if(readCharFunc(buffer) == 'd') + { + buffer++; buffer++; // skip "d=" + buffer = readNumber(buffer, tmpNumber, readCharFunc); + if(tmpNumber > 0) + default_dur = tmpNumber; + buffer++; // skip comma + } + + #ifdef ANY_RTTTL_INFO + Serial.print("ddur: "); Serial.println(default_dur, 10); + #endif + + // get default octave + if(readCharFunc(buffer) == 'o') + { + buffer++; buffer++; // skip "o=" + buffer = readNumber(buffer, tmpNumber, readCharFunc); + if(tmpNumber >= 3 && tmpNumber <= 7) + default_oct = tmpNumber; + buffer++; // skip comma + } + + #ifdef ANY_RTTTL_INFO + Serial.print("doct: "); Serial.println(default_oct, 10); + #endif + + // get BPM + if(readCharFunc(buffer) == 'b') + { + buffer++; buffer++; // skip "b=" + buffer = readNumber(buffer, tmpNumber, readCharFunc); + bpm = tmpNumber; + buffer++; // skip colon + } + + #ifdef ANY_RTTTL_INFO + Serial.print("bpm: "); Serial.println(bpm, 10); + #endif + + // BPM usually expresses the number of quarter notes per minute + wholenote = (60 * 1000L / bpm) * 4; // this is the time for whole noteOffset (in milliseconds) + + #ifdef ANY_RTTTL_INFO + Serial.print("wn: "); Serial.println(wholenote, 10); + #endif + + // now begin note loop + while(readCharFunc(buffer)) + { + // first, get note duration, if available + buffer = readNumber(buffer, tmpNumber, readCharFunc); + + if(tmpNumber) + duration = wholenote / tmpNumber; + else + duration = wholenote / default_dur; // we will need to check if we are a dotted noteOffset after + + // now get the note + noteOffset = getNoteOffsetFromLetter(readCharFunc(buffer)); + buffer++; + + // now, get optional '#' sharp + if(readCharFunc(buffer) == '#') + { + noteOffset++; + buffer++; + } + + // now, get optional '.' dotted note + if(readCharFunc(buffer) == '.') + { + duration += duration/2; + buffer++; + } + + // now, get scale + if(isdigit(readCharFunc(buffer))) + { + scale = readCharFunc(buffer) - '0'; + buffer++; + } + else + { + scale = default_oct; + } + + if(readCharFunc(buffer) == ',') + buffer++; // skip comma for next note (or we may be at the end) + + // now play the note + if(noteOffset) + { + uint16_t frequency = notes[(scale - 4) * NOTES_PER_OCTAVE + noteOffset]; + + #ifdef ANY_RTTTL_INFO + Serial.print("Playing: "); + Serial.print(scale, 10); Serial.print(' '); + Serial.print(noteOffset, 10); Serial.print(" ("); + Serial.print(frequency, 10); + Serial.print(") "); + Serial.println(duration, 10); + #endif + + _tone(iPin, frequency, duration); + _delay(duration+1); + _noTone(iPin); + } + else + { + #ifdef ANY_RTTTL_INFO + Serial.print("Pausing: "); + Serial.println(duration, 10); + #endif + _delay(duration); + } + } +} + +void play(byte iPin, const char * iBuffer) { play(iPin, iBuffer, &readChar); } + +void play(byte iPin, const __FlashStringHelper* str) { play(iPin, (const char *)str, &readChar_P); } +void playProgMem(byte iPin, const char * iBuffer) { play(iPin, iBuffer, &readChar_P); } +void play_P(byte iPin, const char * iBuffer) { play(iPin, iBuffer, &readChar_P); } +void play_P(byte iPin, const __FlashStringHelper* str) { play(iPin, (const char *)str, &readChar_P); } + +void play16Bits(int iPin, const unsigned char * iBuffer, int iNumNotes) { + // Absolutely no error checking in here + + RTTTL_DEFAULT_VALUE_SECTION * defaultSection = (RTTTL_DEFAULT_VALUE_SECTION *)iBuffer; + RTTTL_NOTE * notesBuffer = (RTTTL_NOTE *)iBuffer; + + bpm = defaultSection->bpm; + + #ifdef ANY_RTTTL_DEBUG + Serial.print("numNotes="); + Serial.println(iNumNotes); + // format: d=N,o=N,b=NNN: + Serial.print("d="); + Serial.print(getNoteDurationFromIndex(defaultSection->durationIdx)); + Serial.print(",o="); + Serial.print(getNoteOctaveFromIndex(defaultSection->octaveIdx)); + Serial.print(",b="); + Serial.println(bpm); + #endif + + // BPM usually expresses the number of quarter notes per minute + wholenote = (60 * 1000L / bpm) * 4; // this is the time for whole noteOffset (in milliseconds) + + // now begin note loop + for(int i=0; i= 0 && iIndex < gNoteLettersCount) + return gNoteLetters[iIndex]; + return -1; +} + +uint16_t getNoteLettersCount() +{ + return gNoteLettersCount; +} + +NOTE_LETTER_INDEX findNoteLetterIndex(RTTTL_NOTE_LETTER n) +{ + for(NOTE_LETTER_INDEX i=0; i= 0 && iIndex < gNoteLettersCount) + return gNoteOffsets[iIndex]; + return 0; +} + +int getNoteOffsetFromLetter(RTTTL_NOTE_LETTER n) +{ + NOTE_LETTER_INDEX index = findNoteLetterIndex(n); + return getNoteOffsetFromLetterIndex(index); +} + +RTTTL_DURATION getNoteDurationFromIndex(DURATION_INDEX iIndex) +{ + if (iIndex >= 0 && iIndex < gNoteDurationsCount) + return gNoteDurations[iIndex]; + return -1; +} + +uint16_t getNoteDurationsCount() +{ + return gNoteDurationsCount; +} + +DURATION_INDEX findNoteDurationIndex(RTTTL_DURATION n) +{ + for(DURATION_INDEX i=0; i= 0 && iIndex < gNoteOctavesCount) + return gNoteOctaves[iIndex]; + return -1; +} + +uint16_t getNoteOctavesCount() +{ + return gNoteOctavesCount; +} + +OCTAVE_INDEX findNoteOctaveIndex(RTTTL_OCTAVE_VALUE n) +{ + for(OCTAVE_INDEX i=0; i= 0 && iIndex < gNoteBpmsCount) + return gNoteBpms[iIndex]; + return -1; +} + +uint16_t getBpmsCount() +{ + return gNoteBpmsCount; +} + +BPM_INDEX findBpmIndex(RTTTL_BPM n) +{ + for(BPM_INDEX i=0; i THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/partitions_8mb_ota_2mb_ee.cvs b/partitions_8mb_ota_2mb_ee.cvs new file mode 100644 index 0000000..acb6073 --- /dev/null +++ b/partitions_8mb_ota_2mb_ee.cvs @@ -0,0 +1,9 @@ +#apps(2.75MB), spiffs(2.5MB) +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x2C0000, +app1, app, ota_1, 0x2D0000, 0x2C0000, +eeprom, data, 0x99, 0x590000, 0x1000, +coredump, data, coredump,0x591000, 0x10000, +spiffs, data, spiffs, 0x5A1000, 0x25F000, \ No newline at end of file diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..968b6db --- /dev/null +++ b/platformio.ini @@ -0,0 +1,39 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:esp32s3dev] +platform = espressif32 +board = wroom-32S3-N8R2 +framework = arduino +board_build.filesystem = littlefs +board_build.partitions = partitions_8mb_ota_2mb_ee.cvs +monitor_speed = 115200 +monitor_filters = esp32_exception_decoder +board_build.sdkconfig_defaults = sdkconfig.defaults +lib_deps = + bblanchon/ArduinoJson + makuna/NeoPixelBus @ ^2.8.3 + https://github.com/me-no-dev/ESPAsyncWebServer + jeremycole/I2C Temperature Sensors derived from the LM75 @ ^1.0.3 + mathertel/OneButton @ ^2.6.1 + h2zero/NimBLE-Arduino @ ^1.4.1 + adafruit/Adafruit SSD1306 @ ^2.5.7 + fastled/FastLED @ ^3.9.4 + marian-craciunescu/ESP32Ping@^1.7 + + +build_flags = + -mfix-esp32-psram-cache-issue + -D CONFIG_LOG_DYNAMIC_LEVEL_CONTROL=1 + -D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE + -D CONFIG_ARDUHAL_LOG_COLORS=1 +upload_port = COM11 +debug_init_break = tbreak setup +monitor_port = COM11 diff --git a/sdkconfig.defaults b/sdkconfig.defaults new file mode 100644 index 0000000..fbca3b6 --- /dev/null +++ b/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_LOG_DYNAMIC_LEVEL_CONTROL=y \ No newline at end of file diff --git a/src/ATALights.cpp b/src/ATALights.cpp new file mode 100644 index 0000000..a0276f8 --- /dev/null +++ b/src/ATALights.cpp @@ -0,0 +1,478 @@ +#include "ATALights.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include +#include +#include "Animations.h" +#include +#include "system.h" +#include "ColorPalettes.h" +//#include + +#define FASTLED_CORE 0 +static const char* tag = "strips"; + +TaskHandle_t Animation_Task_Handle; +LEDSTRIP_SETTINGS ledSettings[2]; +volatile bool AnimationLooping = false; +ANIM_EVENT prevAnimEvent = {0}; +QueueHandle_t animationQueue = xQueueCreate( 1, sizeof( ANIM_EVENT ) ); + +void Lights_Set_Animation(int animIndex, uint8_t red, uint8_t grn, uint8_t blu){ + + // Trigger to exit curring looping animation + if(AnimationLooping){ + AnimationLooping = false; + xTaskNotifyGive( Animation_Task_Handle ); + } + + ANIM_EVENT event; + event.AnimationIndex = animIndex; + event.data.red = red; + event.data.grn = grn; + event.data.blu = blu; + event.data.wht = 0; + xQueueSend( animationQueue, &event, 25 ); +} + +void Lights_Set_ON(){ + Lights_Set_Animation(prevAnimEvent.AnimationIndex, prevAnimEvent.data.red, prevAnimEvent.data.grn, prevAnimEvent.data.blu); +} + +void Lights_Set_OFF(){ + Lights_Set_Animation(OFF_INDEX, 0, 0, 0); +} + +void Lights_Set_Brightness(uint8_t scale){ + FastLED.setBrightness(scale); +} + +void Lights_Set_White(uint8_t val){ + //pwmOut[0]->setOutput(led_status.white / 2.5f); +} + +void Init_Lights_Task(void){ + + ledSettings[0].leds = new CRGB[ledSettings[0].size]; + Init_Strip(ledSettings[0].leds, ledSettings[0].pin, ledSettings[0].size, ledSettings[0].rgbOrder, ledSettings[0].chip, ledSettings[0].bright); + ESP_LOGD(tag, "Initializing Strip1: Pin: %d, size: %d, order: %s, chip: %s", ledSettings[0].pin, ledSettings[0].size, ledSettings[0].rgbOrder, ledSettings[0].chip); + + + ledSettings[1].leds = new CRGB[ledSettings[1].size];; + Init_Strip(ledSettings[1].leds, ledSettings[1].pin, ledSettings[1].size, ledSettings[1].rgbOrder, ledSettings[1].chip, ledSettings[1].bright); + ESP_LOGD(tag, "Initializing Strip2: Pin: %d, size: %d, order: %s, chip: %s", ledSettings[1].pin, ledSettings[1].size, ledSettings[1].rgbOrder, ledSettings[1].chip); + + xTaskCreatePinnedToCore(Lights_Control_Task, "Lights_Task", 1024*8, NULL, 1, &Animation_Task_Handle, FASTLED_CORE); + ESP_LOGI(tag, "Lights Task Created..."); +} + + +void Animation_Loop_Exit(void){ + if( Animation_Task_Handle ){ + xTaskNotifyGive( Animation_Task_Handle ); + } +} + + +EOrder GetEOrderByString(const String& rgbStr){ + ESP_LOGI(tag, "EOrder is %s", rgbStr.c_str()); + if (rgbStr.equalsIgnoreCase("RGB")) return RGB; + else if (rgbStr.equalsIgnoreCase("RBG")) return RBG; + else if (rgbStr.equalsIgnoreCase("GRB")) return GRB; + else if (rgbStr.equalsIgnoreCase("GBR")) return GBR; + else if (rgbStr.equalsIgnoreCase("BRG")) return BRG; + else if (rgbStr.equalsIgnoreCase("BGR")) return BGR; + else return RGB; // Default case +} + + +void LightsON(void){ + FastLED.show(); +} + + +void LightsOff(void){ + FastLED.clear(); + FastLED.show(); +} + + +/* +void setPixel(LEDSTRIP_SETTINGS& strip, int pixelIndex, CRGB col){ + int x = calcPixelIndex(strip, pixelIndex); + leds1[x] = col; +} + +inline int calcPixelIndex(LEDSTRIP_SETTINGS& strip, int index) { + int x = (index + strip.shift) % strip.effSize; + x = (x < 0) ? (x + strip.effSize) : x; + return (x +strip.offset) % strip.effSize; +} +*/ + +inline void setPixel1(LEDSTRIP_SETTINGS& leds, int pixelIndex, const CRGB col) { + register uint16_t x = pixelIndex + ledSettings[0].shift; + // If strip.effSize is power of 2, use faster bit masking + if ((leds.effSize & (leds.effSize - 1)) == 0) { + x = (x < 0) ? ((x + leds.effSize) & (leds.effSize - 1)) : (x & (leds.effSize - 1)); + leds.leds[(x + leds.offset) & (leds.effSize - 1)] = col; + } else { + // For non-power-of-2 sizes, still need modulo + x = (x < 0) ? ((x + ledSettings[0].effSize) % ledSettings[0].effSize) : (x % ledSettings[0].effSize); + ledSettings[0].leds[(x + ledSettings[0].offset) % ledSettings[0].effSize] = col; + } +} + + +inline void setPixel2(int pixelIndex, const CRGB col) { + register uint16_t x = pixelIndex + ledSettings[1].shift; + // If strip.effSize is power of 2, use faster bit masking + if ((ledSettings[1].effSize & (ledSettings[1].effSize - 1)) == 0) { + x = (x < 0) ? ((x + ledSettings[1].effSize) & (ledSettings[1].effSize - 1)) : (x & (ledSettings[1].effSize - 1)); + ledSettings[1].leds[(x + ledSettings[1].offset) & (ledSettings[1].effSize - 1)] = col; + } else { + // For non-power-of-2 sizes, still need modulo + x = (x < 0) ? ((x + ledSettings[1].effSize) % ledSettings[1].effSize) : (x % ledSettings[1].effSize); + ledSettings[1].leds[(x + ledSettings[1].offset) % ledSettings[1].effSize] = col; + } +} + + +void Init_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder, const String& chipType, uint8_t bright) { + // Validate inputs + if (!leds || size <= 0) { + ESP_LOGE(tag, "Invalid LED array or size"); + return; + } + + // Convert strings to uppercase once + String chipUpper = chipType; + String orderUpper = colorOrder; + chipUpper.toUpperCase(); + orderUpper.toUpperCase(); + + // Validate pin (using AND for correct logic) + if (pin != 3 && pin != 9 && pin != 46) { + ESP_LOGW(tag, "Invalid pin %d, defaulting to 3", pin); + pin = 3; + } + + EOrder rgbOrder = GetEOrderByString(orderUpper); + + if(pin == 3){ + // First level: Chip type selection + if (chipUpper == "WS2812B" || chipUpper == "SK6812") { + switch(rgbOrder) { + case RGB: FastLED.addLeds(leds, size); break; + case RBG: FastLED.addLeds(leds, size); break; + case GRB: FastLED.addLeds(leds, size); break; + case GBR: FastLED.addLeds(leds, size); break; + case BRG: FastLED.addLeds(leds, size); break; + case BGR: FastLED.addLeds(leds, size); break; + default: FastLED.addLeds(leds, size); break; + } + } + else if (chipUpper == "SK6812") { + switch(rgbOrder) { + case RGB: FastLED.addLeds(leds, size); break; + case RBG: FastLED.addLeds(leds, size); break; + case GRB: FastLED.addLeds(leds, size); break; + case GBR: FastLED.addLeds(leds, size); break; + case BRG: FastLED.addLeds(leds, size); break; + case BGR: FastLED.addLeds(leds, size); break; + default: FastLED.addLeds(leds, size); break; + } + } + else if (chipUpper == "WS2811_400") { + switch(rgbOrder) { + case RGB: FastLED.addLeds(leds, size); break; + case RBG: FastLED.addLeds(leds, size); break; + case GRB: FastLED.addLeds(leds, size); break; + case GBR: FastLED.addLeds(leds, size); break; + case BRG: FastLED.addLeds(leds, size); break; + case BGR: FastLED.addLeds(leds, size); break; + default: FastLED.addLeds(leds, size); break; + } + } + else if (chipUpper == "WS2815") { + switch(rgbOrder) { + case RGB: FastLED.addLeds(leds, size); break; + case RBG: FastLED.addLeds(leds, size); break; + case GRB: FastLED.addLeds(leds, size); break; + case GBR: FastLED.addLeds(leds, size); break; + case BRG: FastLED.addLeds(leds, size); break; + case BGR: FastLED.addLeds(leds, size); break; + default: FastLED.addLeds(leds, size); break; + } + } + else { + // Default to WS2812B if unknown chip type + ESP_LOGW(tag, "Unknown LED chip type: %s, defaulting to WS2812B", chipType.c_str()); + FastLED.addLeds(leds, size); + } + ESP_LOGI(tag, "Initialized %s LED strip with %d LEDs on pin %d", chipType.c_str(), size, pin); + } + else if(pin == 9){ + // First level: Chip type selection + if (chipUpper == "WS2812B") { + switch(rgbOrder) { + case RGB: FastLED.addLeds(leds, size); break; + case RBG: FastLED.addLeds(leds, size); break; + case GRB: FastLED.addLeds(leds, size); break; + case GBR: FastLED.addLeds(leds, size); break; + case BRG: FastLED.addLeds(leds, size); break; + case BGR: FastLED.addLeds(leds, size); break; + default: FastLED.addLeds(leds, size); break; + } + } + else if (chipUpper == "SK6812") { + switch(rgbOrder) { + case RGB: FastLED.addLeds(leds, size); break; + case RBG: FastLED.addLeds(leds, size); break; + case GRB: FastLED.addLeds(leds, size); break; + case GBR: FastLED.addLeds(leds, size); break; + case BRG: FastLED.addLeds(leds, size); break; + case BGR: FastLED.addLeds(leds, size); break; + default: FastLED.addLeds(leds, size); break; + } + } + else if (chipUpper == "WS2811_400") { + switch(rgbOrder) { + case RGB: FastLED.addLeds(leds, size); break; + case RBG: FastLED.addLeds(leds, size); break; + case GRB: FastLED.addLeds(leds, size); break; + case GBR: FastLED.addLeds(leds, size); break; + case BRG: FastLED.addLeds(leds, size); break; + case BGR: FastLED.addLeds(leds, size); break; + default: FastLED.addLeds(leds, size); break; + } + } + else if (chipUpper == "WS2815") { + switch(rgbOrder) { + case RGB: FastLED.addLeds(leds, size); break; + case RBG: FastLED.addLeds(leds, size); break; + case GRB: FastLED.addLeds(leds, size); break; + case GBR: FastLED.addLeds(leds, size); break; + case BRG: FastLED.addLeds(leds, size); break; + case BGR: FastLED.addLeds(leds, size); break; + default: FastLED.addLeds(leds, size); break; + } + } + else { + // Default to WS2812B if unknown chip type + ESP_LOGW(tag, "Unknown LED chip type: %s, defaulting to WS2812B", chipType.c_str()); + FastLED.addLeds(leds, size); + } + ESP_LOGI(tag, "Initialized %s LED strip with %d LEDs on pin %d", chipType.c_str(), size, pin); + } + else if(pin == 46){ + // First level: Chip type selection + if (chipUpper == "WS2812B" || chipUpper == "SK6812") { + switch(rgbOrder) { + case RGB: FastLED.addLeds(leds, size); break; + case RBG: FastLED.addLeds(leds, size); break; + case GRB: FastLED.addLeds(leds, size); break; + case GBR: FastLED.addLeds(leds, size); break; + case BRG: FastLED.addLeds(leds, size); break; + case BGR: FastLED.addLeds(leds, size); break; + default: FastLED.addLeds(leds, size); break; + } + } + else if (chipUpper == "SK6812") { + switch(rgbOrder) { + case RGB: FastLED.addLeds(leds, size); break; + case RBG: FastLED.addLeds(leds, size); break; + case GRB: FastLED.addLeds(leds, size); break; + case GBR: FastLED.addLeds(leds, size); break; + case BRG: FastLED.addLeds(leds, size); break; + case BGR: FastLED.addLeds(leds, size); break; + default: FastLED.addLeds(leds, size); break; + } + } + else if (chipUpper == "WS2811_400") { + switch(rgbOrder) { + case RGB: FastLED.addLeds(leds, size); break; + case RBG: FastLED.addLeds(leds, size); break; + case GRB: FastLED.addLeds(leds, size); break; + case GBR: FastLED.addLeds(leds, size); break; + case BRG: FastLED.addLeds(leds, size); break; + case BGR: FastLED.addLeds(leds, size); break; + default: FastLED.addLeds(leds, size); break; + } + } + else if (chipUpper == "WS2815") { + switch(rgbOrder) { + case RGB: FastLED.addLeds(leds, size); break; + case RBG: FastLED.addLeds(leds, size); break; + case GRB: FastLED.addLeds(leds, size); break; + case GBR: FastLED.addLeds(leds, size); break; + case BRG: FastLED.addLeds(leds, size); break; + case BGR: FastLED.addLeds(leds, size); break; + default: FastLED.addLeds(leds, size); break; + } + } + else { + // Default to WS2812B if unknown chip type + ESP_LOGW(tag, "Unknown LED chip type: %s, defaulting to WS2812B", chipType.c_str()); + FastLED.addLeds(leds, size); + } + ESP_LOGI(tag, "Initialized %s LED strip with %d LEDs on pin %d", chipType.c_str(), size, pin); + } + + FastLED.setBrightness(bright); +} + + +void Lights_Control_Task_Resume(void){ + vTaskResume(Animation_Task_Handle); +} + + +void Lights_Control_Task(void *parameters){ + + ANIM_EVENT AnimEvent; + CRGB col; + COLOR_PACK colorPack; + CRGBPalette16 firePalette; + + ESP_LOGD(tag, "Lights Control Task Entered..."); + vTaskSuspend(NULL); + ESP_LOGD(tag, "Lights Control Task Resumed..."); + vTaskDelay(1000); + + Lights_Set_Brightness(48); + Lights_Set_Animation(1, 0, 0, 0); // set rainbow animation + + + while (true) { + if (xQueueReceive(animationQueue, &AnimEvent, portMAX_DELAY) == pdTRUE) { + ESP_LOGD(tag, "New Animation Event: Index: %d", AnimEvent.AnimationIndex); + switch (AnimEvent.AnimationIndex) { + case -3: // Set Pixel by index + ledSettings[0].leds[AnimEvent.data.data[7]] = CRGB(AnimEvent.data.red, AnimEvent.data.grn, AnimEvent.data.blu); + FastLED.show(); + break; + case -2: // Fill Static Color + col = CRGB(AnimEvent.data.red, AnimEvent.data.grn, AnimEvent.data.blu); + fill_solid(ledSettings[0].leds, ledSettings[0].size, col); + FastLED.show(); + ESP_LOGD(tag, "Static Color"); + break; + case -1: + FastLED.clear(); + FastLED.show(); + ESP_LOGD(tag, "LEDs Off"); + break; + case 0: + break; + case 1: + Anim_Rainbow(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, 30); + break; + case 2: + Anim_TimedFill(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, CRGB::Black, CRGB::White, 1000, 0); + break; + case 3: + Anim_TimedFill(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, CRGB::Black, CRGB::White, 2000, 0); + break; + case 4: + Anim_TimedFill(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, CRGB::Black, CRGB::White, 3000, 0); + break; + case 5: + createFirePalette(firePalette, CRGB::Red, CRGB::OrangeRed, CRGB::Orange); + Anim_Fire(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, 60, firePalette, 0); + break; + case 6: + createFirePalette(firePalette, CRGB::DarkGreen, CRGB::Green, CRGB::LightGreen); + Anim_Fire(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, 60, firePalette, 0); + break; + case 7: + createFirePalette(firePalette, CRGB::DarkBlue, CRGB::Blue, CRGB::LightBlue); + Anim_Fire(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, 60, firePalette, 0); + break; + case 8: + createFirePalette(firePalette, CRGB::Purple, CRGB::Blue, CRGB::Violet); + Anim_Fire(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, 60, firePalette, 0); + break; + case 9: + loadColorPack(colorPack, colorPack_USA); + Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack, 1, 80); + break; + case 10: + loadColorPack(colorPack, colorPack_MEXICO); + Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack, 1, 80); + break; + case 11: + loadColorPack(colorPack, colorPack_RedBlack); + Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack, 4, 80); + break; + case 12: + loadColorPack(colorPack, colorPack_YellowBlack); + Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack, 4, 80); + break; + case 13: + loadColorPack(colorPack, colorPack_GreenBlack); + Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack, 4, 80); + break; + case 14: + loadColorPack(colorPack, colorPack_BlueBlack); + Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack, 4, 80); + break; + case 15: + loadColorPack(colorPack, colorPack_VioletBlack); + Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack, 4, 80); + break; + case 16: + loadColorPack(colorPack, colorPack_RAINBOW); + Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack, 80, RANDOM_DECAY, true, 1); + break; + case 17: + loadColorPack(colorPack, colorPack_USA); + Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack, 80, LINEAR_DECAY, true, 1); + break; + case 18: + loadColorPack(colorPack, colorPack_USA); + Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack, 80, RANDOM_DECAY, false, 1); + break; + case 19: + loadColorPack(colorPack, colorPack_USA); + Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack, 80, RANDOM_DECAY, true, 2); + break; + case 20: + loadColorPack(colorPack, colorPack_RAINBOW); + Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack, 80, RANDOM_DECAY, true, 1); + break; + case 21: + loadColorPack(colorPack, colorPack_RAINBOW); + Anim_ColorBreath(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack, 7000, 90); + break; + case 22: + loadColorPack(colorPack, colorPack_USA); + Anim_GradientRotate(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack, 50); + break; + default: + ESP_LOGD(tag, "Loop default"); + break; + } + + AnimationLooping = false; + prevAnimEvent = AnimEvent; + ESP_LOGD(tag, "Going to Queue to Wait"); + } + } +} + + +void createFirePalette(CRGBPalette16& palette, CRGB color1, CRGB color2, CRGB color3) { + for (uint8_t i = 0; i < 16; i++) { + if (i < 3) palette[i] = CRGB::Black; + else if (i < 7) palette[i] = color1; + else if (i < 10) palette[i] = color2; + else if (i < 15) palette[i] = color3; + else palette[i] = CRGB::White; + } +} + +void loadColorPack(COLOR_PACK& dest, const COLOR_PACK& src) { + memcpy_P(&dest, &src, sizeof(COLOR_PACK)); +} diff --git a/src/Animations.cpp b/src/Animations.cpp new file mode 100644 index 0000000..0b36c2d --- /dev/null +++ b/src/Animations.cpp @@ -0,0 +1,571 @@ +#include "Animations.h" +#include +#include +#include +#include "ColorPalettes.h" +#include "esp_system.h" + + +typedef struct{ + float minSpeed; + float maxSpeed; + int rampCycles; +}SPEED_PROPERTIES; + + +void Animation_Init(void){ + // Create Palettes + +} + +// Animation Loop Template +void Animation_Loop(bool volatile& loop_active_flag, int speed, std::function callback) { + if (!callback) { + ESP_LOGE("Animation_Loop", "Invalid callback function"); + return; + } + + loop_active_flag = true; + speed = constrain(speed, 0, MaxSpeed); + int loopDelay = max(MaxSpeed - speed, MinLoopDelay); + + ulTaskNotifyTake(pdTRUE, 0); // Clear any pending notifications + + TickType_t xLastWakeTime; + TickType_t elapsedTicks; + TickType_t delayTicks; + int retVal = 0; + while(!retVal && loop_active_flag) { + xLastWakeTime = xTaskGetTickCount(); + + try { + retVal = callback(); // Call animation function + } catch (const std::exception& e) { + ESP_LOGE("Animation_Loop", "Callback exception: %s", e.what()); + break; + } + + if(!loop_active_flag) return; + + // Calculate remaining time with overflow protection + elapsedTicks = xTaskGetTickCount() - xLastWakeTime; + delayTicks = (elapsedTicks < loopDelay) ? (loopDelay - elapsedTicks) : 0; + + // Check for termination request + if (ulTaskNotifyTake(pdTRUE, delayTicks)) { break; } + } + + loop_active_flag = false; +} + +// Animation Loop Template +void Animation_Loop_Duration(bool volatile& loop_active_flag, int speed, TickType_t durationMs, std::function callback) { + loop_active_flag = true; + speed = constrain(speed, 0, MaxSpeed); + if (durationMs < 0) durationMs = 0; + + ulTaskNotifyTake(pdTRUE, 0); // Clear any pending notifications + TickType_t startTicks = xTaskGetTickCount(); + TickType_t xLastWakeTime; + for(;;) { + xLastWakeTime = xTaskGetTickCount(); + + // Call animation function + int speedIncrease = callback(); // Call animation function + + if(!loop_active_flag) return; + + // Calculate combined speed with bounds protection + int totalSpeed = constrain(speed + speedIncrease, 0, MaxSpeed); + + // Calculate delay with minimum protection + int loopDelay = MaxSpeed - totalSpeed; + loopDelay = max(loopDelay, MinLoopDelay); + + // Calculate remaining time with overflow protection + TickType_t elapsedTicks = xTaskGetTickCount() - xLastWakeTime; + TickType_t delayTicks = (elapsedTicks < loopDelay) ? (loopDelay - elapsedTicks) : 0; + + // Delay and Check for termination request + if (ulTaskNotifyTake(pdTRUE, delayTicks)) { break; } + + // Check if duration reached and wait for loop_active_flag + if (durationMs >=0) { + TickType_t totalElapsed = xTaskGetTickCount() - startTicks; + + if (totalElapsed >= durationMs) { + while(loop_active_flag){ + if (ulTaskNotifyTake(pdTRUE, 50)) { + return; + } + } + break; + } + } + } + + loop_active_flag = false; +} + +// Animation Loop Template +void Animation_Loop_Cycles(bool volatile& loop_active_flag, int speed, uint32_t loop_cycles, std::function callback) { + loop_active_flag = true; + uint32_t loop_cycle_count = 0; + speed = constrain(speed, 0, MaxSpeed); + + ulTaskNotifyTake(pdTRUE, 0); // Clear any pending notifications + TickType_t startTicks = xTaskGetTickCount(); + TickType_t xLastWakeTime; + TickType_t elapsedTicks; + TickType_t delayTicks; + for(;;) { + xLastWakeTime = xTaskGetTickCount(); + + int speedIncrease = callback(); // Call animation function + + if(!loop_active_flag) return; + + // Calculate combined speed with bounds protection + int totalSpeed = constrain(speed + speedIncrease, 0, MaxSpeed); + + // Calculate delay with minimum protection + int loopDelay = MaxSpeed - totalSpeed; + loopDelay = max(loopDelay, MinLoopDelay); + + // Calculate remaining time with overflow protection + elapsedTicks = xTaskGetTickCount() - xLastWakeTime; + delayTicks = (elapsedTicks < loopDelay) ? (loopDelay - elapsedTicks) : 0; + + // Delay and Check for termination request + if (ulTaskNotifyTake(pdTRUE, delayTicks)) { break; } + + // Check if cycles reached and wait for loop_active_flag + if (loop_cycle_count >= loop_cycles) { + while(loop_active_flag){ + if (ulTaskNotifyTake(pdTRUE, 50)) { + //loop_active_flag = false; + break; + } + } + break; + } + + loop_cycle_count++; + } + + loop_active_flag = false; +} + + +/******************************************************************************** + * + * Animations + * + *******************************************************************************/ + +void Anim_Rainbow(bool volatile& activeFlag, CRGB* leds, int size, int speed){ + // Initialize rainbow pattern once + fill_rainbow_circular(leds, size, 0); + + CRGB temp; + Animation_Loop(activeFlag, speed, [&]() -> int { + // Rotate pixels by 1 position + temp = leds[0]; // Save first pixel + memmove(&leds[0], &leds[1], (size-1) * sizeof(CRGB)); + leds[size-1] = temp; // Move first pixel to end + + FastLED.show(); + return 0; + }); +} + +// Fire parameters (adjustable) +const uint8_t FIRE_COOLING = 66; +const uint8_t FIRE_SPARKING = 62; +const uint8_t FIRE_brightness = 255; + +void Anim_Fire(bool volatile& activeFlag, CRGB* leds, int size, int speed, const CRGBPalette16& firePalette, int shift = 0) { + if (!leds || size <= 0) return; + + // Calculate half size for mirroring + const int halfSize = size / 2; + + // Create heat array for half the size + uint8_t* heat = new (std::nothrow) uint8_t[halfSize]; + if (!heat) return; + memset(heat, 0, halfSize * sizeof(uint8_t)); + + CRGB color; + uint8_t colorindex; + int pos1, pos2, y; + Animation_Loop(activeFlag, speed, [&]() -> int { + // Random cooling + for(int i = 0; i < halfSize; i++) { + heat[i] = qsub8(heat[i], random8(0, ((FIRE_COOLING * 10) / halfSize) + 2)); + } + + // Heat rises and diffuses + for(int k = halfSize - 1; k >= 2; k--) { + heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2]) / 3; + } + + // Randomly ignite new sparks at bottom + if(random8() < FIRE_SPARKING) { + y = random8(7); + heat[y] = qadd8(heat[y], random8(160, 240)); + } + + // Map heat to colors with mirroring and shifting + for(int j = 0; j < halfSize; j++) { + colorindex = scale8(heat[j], 240); + color = ColorFromPalette(firePalette, colorindex, FIRE_brightness, LINEARBLEND); + + // Apply shift and wrap around + pos1 = (j + shift + size) % size; + pos2 = (size - 1 - j + shift + size) % size; + + leds[pos1] = color; + leds[pos2] = color; // Mirror + } + + FastLED.show(); + return 0; + }); + + delete[] heat; +} + + +void Anim_Color_Sectors(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colorPack, uint8_t numRepeats, int speed) { + // Validate inputs + if (size <= 0 || numRepeats <= 0 || colorPack.size <= 0) return; + + // Calculate sector size + int sectorSize = size / (colorPack.size * numRepeats); + if (sectorSize < 1) sectorSize = 1; + + // Initialize pattern + for (int repeat = 0; repeat < numRepeats; repeat++) { + for (int color = 0; color < colorPack.size; color++) { + int startIdx = (repeat * colorPack.size + color) * sectorSize; + int endIdx = startIdx + sectorSize; + if (endIdx > size) endIdx = size; + + // Fill sector with the specified color + fill_solid(&leds[startIdx], endIdx - startIdx, colorPack.col[color]); + } + } + + // Animate rotation + bool direction = true; // true = forward, false = backward + int loopCounter = 0; + CRGB temp; + Animation_Loop(activeFlag, speed, [&]() -> int { + + if (direction) { + temp = leds[0]; + memmove(&leds[0], &leds[1], (size-1) * sizeof(CRGB)); + leds[size-1] = temp; + } else { + temp = leds[size-1]; + memmove(&leds[1], &leds[0], (size-1) * sizeof(CRGB)); + leds[0] = temp; + } + + // Direction Switching Logic + loopCounter++; + if(loopCounter >= (size * CYCLES_PER_DIRECTION)){ + direction = !direction; + loopCounter = 0; + } + + FastLED.show(); + return 0; + }); +} + + + +/******************************************************************************** + * Comets Animation + *******************************************************************************/ +#define COMET_SIZE_FACTOR 0.2 +#define COMET_FADE_FACTOR1 64 /* longer tail */ +#define COMET_FADE_FACTOR2 192 /* shorter tail */ +#define COMET_FADE_FACTOR COMET_FADE_FACTOR2 +#define MAX_COMETS 16 // Maximum number of comets supported +/* +void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colorPack, int speed, bool randomDecay, bool shorterTail, int cometMultiplier = 1) { + // Validate inputs + int numComets = colorPack.size; + if (size <= 0 || numComets <= 0 || colorPack.size <= 0 || cometMultiplier <= 0) return; + + // Calculate comet size + int cometSize = (size / (numComets * cometMultiplier)) * COMET_SIZE_FACTOR; + if (cometSize < 1) cometSize = 1; + + // Set fade factor + uint8_t fadeFactor = shorterTail ? COMET_FADE_FACTOR2 : COMET_FADE_FACTOR1; + + // Initialize comet positions to be equally spaced + std::unique_ptr cometPositions(new (std::nothrow) int[numComets * cometMultiplier]); + if (!cometPositions) return; + int totalComets = numComets * cometMultiplier; + int spacing = size / totalComets; + for (int i = 0; i < totalComets; i++) { + cometPositions[i] = i * spacing; + } + + // Animation loop + bool direction = true; // true = forward, false = backward + int loopCounter = 0; + try { + Animation_Loop(activeFlag, speed, [&]() -> int { + // Fade all LEDs + for (int i = 0; i < size; i++) { + if(!randomDecay) { + leds[i].fadeToBlackBy(fadeFactor); + } else if(random(10) > 5){ + leds[i].fadeToBlackBy(fadeFactor); + } + } + + // Move and draw comets + for (int i = 0; i < totalComets; i++) { + if (direction) { + cometPositions[i] = (cometPositions[i] + 1) % size; + } else { + cometPositions[i] = (cometPositions[i] - 1 + size) % size; + } + + // Draw comet with solid color + CRGB color = colorPack.col[i % colorPack.size]; + for (int j = 0; j < cometSize; j++) { + int pos = (cometPositions[i] - j + size) % size; + leds[pos] += color; + } + } + + loopCounter++; + if(loopCounter >= (size * CYCLES_PER_DIRECTION)){ + direction = !direction; + loopCounter = 0; + } + + FastLED.show(); + return 0; + }); + } catch (const std::exception& e) { + ESP_LOGE("Anim_Comets", "Exception in Animation_Loop: %s", e.what()); + } catch (...) { + ESP_LOGE("Anim_Comets", "Unknown exception in Animation_Loop"); + } + // No need to delete cometPositions as it is managed by std::unique_ptr +} +*/ + + +void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colorPack, int speed, bool randomDecay, bool shorterTail, int cometMultiplier = 1) { + // Validate inputs + int numComets = colorPack.size; + int totalComets = numComets * cometMultiplier; + + if (size <= 0 || numComets <= 0 || colorPack.size <= 0 || cometMultiplier <= 0 || totalComets > MAX_COMETS) { + ESP_LOGE("Anim_Comets", "Invalid input parameters or too many comets (max: %d, requested: %d)", MAX_COMETS, totalComets); + return; + } + + // Calculate comet size + int cometSize = (size / totalComets) * COMET_SIZE_FACTOR; + if (cometSize < 1) cometSize = 1; + + // Set fade factor + uint8_t fadeFactor = shorterTail ? COMET_FADE_FACTOR2 : COMET_FADE_FACTOR1; + + // Initialize comet positions with fixed array + int cometPositions[MAX_COMETS] = {0}; + int spacing = size / totalComets; + for (int i = 0; i < totalComets; i++) { + cometPositions[i] = i * spacing; + } + + // Animation loop + bool direction = true; // true = forward, false = backward + int loopCounter = 0; + int pos; + CRGB color; + try { + Animation_Loop(activeFlag, speed, [&]() -> int { + // Fade all LEDs + for (int i = 0; i < size; i++) { + if (!randomDecay) { + leds[i].fadeToBlackBy(fadeFactor); + } else if (getRandomValue(10) > 5) { + leds[i].fadeToBlackBy(fadeFactor); + } + } + + // Move and draw comets + for (int i = 0; i < totalComets; i++) { + if (direction) { + cometPositions[i] = (cometPositions[i] + 1) % size; + } else { + cometPositions[i] = (cometPositions[i] - 1 + size) % size; + } + + // Draw comet with solid color + colorPack.col[i % colorPack.size]; + for (int j = 0; j < cometSize; j++) { + pos = (cometPositions[i] - j + size) % size; + leds[pos] += color; + } + } + + // Direction change + if (++loopCounter >= (size * CYCLES_PER_DIRECTION)) { + direction = !direction; + loopCounter = 0; + } + + FastLED.show(); + return 0; + }); + } catch (const std::exception& e) { + ESP_LOGE("Anim_Comets", "Exception in Animation_Loop: %s", e.what()); + } catch (...) { + ESP_LOGE("Anim_Comets", "Unknown exception in Animation_Loop"); + } +} + + +void Anim_TimedFill(bool volatile& activeFlag, CRGB* leds, int size, CRGB baseCol, CRGB fillCol, int totalDurationMs, int shift = 0) { + if (!leds || size <= 0 || totalDurationMs <= 0) return; + + const int halfSize = size / 2; + const float msPerLed = totalDurationMs / (float)halfSize; + unsigned long startTime = millis(); + + fill_solid(leds, size, baseCol); + + int prevLedsToLight = 0; + unsigned long currentTime; + unsigned long elapsedTime; + int ledsToLight, pos; + Animation_Loop(activeFlag, 90, [&]() -> int { + currentTime = millis(); + elapsedTime = currentTime - startTime; + + // Calculate how many LEDs should be lit based on elapsed time + ledsToLight = (elapsedTime / msPerLed); + if (ledsToLight > halfSize) ledsToLight = halfSize; + + // Fill LEDs up to current position + for (int i = 0; i < ledsToLight; i++) { + pos = (i + shift + size) % size; + leds[pos] = fillCol; + leds[size - 1 - pos] = fillCol; // Mirror + } + + // Update LEDs only when necessary + if(prevLedsToLight < ledsToLight){ + FastLED.show(); + } + prevLedsToLight = ledsToLight; + + // Return 1 when complete + return (ledsToLight >= halfSize) ? 1 : 0; + }); +} + + +#define MIN_BRIGHTNESS 2 +void Anim_ColorBreath(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colors, uint32_t timeMs, int speed) { + if (!leds || size <= 0 || colors.size <= 0 || timeMs <= 0) return; + + uint8_t colorIndex = 0; + unsigned long startTime = millis(); + const uint32_t halfTime = timeMs / 2; + + uint8_t origBright = FastLED.getBrightness(); + FastLED.setBrightness(255); + + uint8_t brightness = MIN_BRIGHTNESS; + uint32_t elapsedTime; + CRGB scaledColor, correctedColor; + unsigned long currentTime; + Animation_Loop(activeFlag, speed, [&]() -> int { + // Calculate elapsed time in current breath cycle + currentTime = millis(); + elapsedTime = currentTime - startTime; + + // Calculate brightness using a linear approach + if (elapsedTime < halfTime) { + brightness = map(elapsedTime, 0, halfTime, MIN_BRIGHTNESS, 255); // Brighten + } else { + brightness = map(elapsedTime, halfTime, timeMs, 255, MIN_BRIGHTNESS); // Dim + } + + // Scale the color directly + scaledColor = colors.col[colorIndex]; + scaledColor.nscale8(brightness * origBright / 255); + + // Correct the color scale for vision + correctedColor = scaledColor; + correctedColor.nscale8_video(brightness); + + // Fill all LEDs with scaled color + fill_solid(leds, size, correctedColor); + FastLED.show(); + + if (elapsedTime >= timeMs) { + colorIndex = (colorIndex + 1) % colors.size; + startTime = currentTime; + } + + return 0; + }); + + FastLED.setBrightness(origBright); +} + + +void Anim_GradientRotate(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colors, int speed) { + if (!leds || size <= 0 || colors.size < 2) return; + + CRGB color1, color2; + + // Create initial gradient + int segmentLength = size / colors.size; + for(int i = 0; i < size; i++) { + // Determine which color segment we're in + int segment = i / segmentLength; + int nextSegment = (segment + 1) % colors.size; + + // Calculate blend amount within segment + uint8_t blendPos = map(i % segmentLength, 0, segmentLength - 1, 0, 255); + + // Create local copies for blending + color1 = colors.col[segment]; + color2 = colors.col[nextSegment]; + + // Set initial gradient + leds[i] = blend(color1, color2, blendPos); + } + + // Rotation animation loop + CRGB temp; + Animation_Loop(activeFlag, speed, [&]() -> int { + // Rotate one position + temp = leds[0]; + memmove(&leds[0], &leds[1], (size-1) * sizeof(CRGB)); + leds[size-1] = temp; + + FastLED.show(); + return 0; + }); +} + + +uint32_t getRandomValue(uint32_t maxValue) { + return esp_random() % maxValue; +} \ No newline at end of file diff --git a/src/AppUpgrade.cpp b/src/AppUpgrade.cpp new file mode 100644 index 0000000..433886d --- /dev/null +++ b/src/AppUpgrade.cpp @@ -0,0 +1,618 @@ +#include "AppUpgrade.h" +#include "esp_log.h" +#include +#include +#include +#include "global.h" +#include "jsonConstrain.h" +#include "BLE_UpdateService.h" + +static const char* TAG = "AppUpdater"; +TaskHandle_t Update_Task_Handle = NULL; +TaskHandle_t versionCheckTask_Handle = NULL; + +// Queue handle for firmware update messages +//QueueHandle_t updateMsgQueue = NULL; + +String updateUrl = ""; + +Version otaVersion; + + +AppUpdater::AppUpdater(fs::FS& fs, Version localVersion, const char* bucket, const char* manifestName, const char* appBin) + : localVersion(localVersion), bucketUrl(bucket), manifestName(manifestName), appName(appBin), fileSystem(fs), downloadBuffer(new uint8_t[BUFFER_SIZE]) +{ + ESP_LOGI(TAG, "AppUpdater initialized with version %s", localVersion.toString().c_str()); +} + +void AppUpdater::setProgressCallback(void (*callback)( UpdateStatus status, int percentage, const char* message)) { + progressCb = callback; +} + +void AppUpdater::updateProgress(UpdateStatus newStatus, int percentage, const char* message) { + status = newStatus; + if (progressCb) { + progressCb(status, percentage, message); + } +} + +bool AppUpdater::checkManifest() { + String url = String(bucketUrl) + manifestName; + ESP_LOGD(TAG, "Fetching manifest from: %s", url.c_str()); + + // Start the HTTP client and Send GET request for manifest + HTTPClient http; + http.begin(url); + int httpCode = http.GET(); + if (httpCode != HTTP_CODE_OK) { + ESP_LOGE(TAG, "HTTP GET failed, error: %d", httpCode); + http.end(); + return false; + } + + // Read the response + String payload = http.getString(); + http.end(); + + // Parse JSON + DeserializationError error = deserializeJson(jsonManifest, payload); + ESP_LOGD(TAG, "Manifest deserialized"); + if (error) { + ESP_LOGE(TAG, "Failed to parse manifest: %s", error.c_str()); + return false; + } + + // Check for files section + jsonFilesArray = jsonManifest["files"]; + if (jsonFilesArray.isNull()) { + ESP_LOGE(TAG, "No files section in manifest"); + return false; + }else{ + ESP_LOGD(TAG, "%d Files found", jsonFilesArray.size()); + } + + // Check for version section + JsonObject jsonVersion = jsonManifest["version"]; + ESP_LOGD(TAG, "Version section found"); + if (jsonVersion.isNull()) { + ESP_LOGE(TAG, "No version section in manifest"); + return false; + } + + // Get the remote version + byte major = jsonVersion["major"] | 0; + byte minor = jsonVersion["minor"] | 0; + byte patch = jsonVersion["patch"] | 0; + otaVersion = {major, minor, patch}; + + //Version localVersion; + //::sscanf(localVersion, "%d.%d.%d", &localVersion.major, &localVersion.minor, &localVersion.patch); + + // Check if an update is available + updateAvailable = false; + if (otaVersion < localVersion) { + ESP_LOGI(TAG, "No updates available"); + return false; + }else{ + updateAvailable = true; + ESP_LOGD(TAG, "Update available"); + } + + //ESP_LOGD(TAG, "Manifest content: %s", payload.c_str()); + + return true; +} + +bool AppUpdater::updateFile(const char* remotePath, const char* localPath, const char* expectedMd5) { + //updateProgress(UpdateStatus::DOWNLOADING, 0, localPath); + + // Construct full URL + String url = String(bucketUrl) + remotePath; + ESP_LOGD(TAG, "Downloading: %s -> %s", url.c_str(), localPath); + + String localMd5 = getLocalMD5(localPath); + + if (localMd5.equals(expectedMd5)) { + ESP_LOGI(TAG, "File already up to date: %s", localPath); + updateProgress(UpdateStatus::FILE_SKIPPED, 100, localPath); + return true; + } + + // Start the download + HTTPClient http; + http.begin(url); + int httpCode = http.GET(); + if (httpCode != HTTP_CODE_OK) { + ESP_LOGE(TAG, "Download failed: %d", httpCode); + updateProgress(UpdateStatus::ERROR, 0, "Download failed"); + http.end(); + return false; + } + + // Get the stream and content length + WiFiClient* stream = http.getStreamPtr(); + size_t contentLength = http.getSize(); + + // Verify and save the file + bool success = verifyAndSaveFile(stream, contentLength, localPath, expectedMd5); + http.end(); + if(!success){ + updateProgress( UpdateStatus::ERROR, 0, "MD5 verification failed"); + }else{ + updateProgress( UpdateStatus::FILE_SAVED, 100, localPath); + } + + return success; +} + +bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, const char* localPath, const char* expectedMd5) +{ + MD5Builder md5; + md5.begin(); + size_t totalRead = 0; + + // Create temporary filename + String tempPath = String(localPath) + ".tmp"; + + // Open temporary file for writing + File file = fileSystem.open(tempPath.c_str(), FILE_WRITE); + if (!file) { + ESP_LOGE(TAG, "Failed to open temporary file for writing"); + return false; + } + + //updateProgress(UpdateStatus::DOWNLOADING, 0, localPath); + + // Single pass: Save file and calculate MD5 + while (totalRead < contentLength) { + size_t available = stream->available(); + if (available) { + size_t readLen = stream->readBytes(downloadBuffer.get(), std::min(available, size_t(BUFFER_SIZE))); + + // Write to temp file and update MD5 + if (file.write(downloadBuffer.get(), readLen) != readLen) { + ESP_LOGE(TAG, "Failed to write to temporary file"); + + file.close(); + fileSystem.remove(tempPath.c_str()); + return false; + } + + md5.add(downloadBuffer.get(), readLen); + totalRead += readLen; + updateProgress(UpdateStatus::DOWNLOADING, (totalRead * 90) / contentLength , localPath); + } + yield(); + } + + file.close(); + md5.calculate(); + String calculatedMd5 = md5.toString(); + + // Verify MD5 hash + if (!calculatedMd5.equals(expectedMd5)) { + //ESP_LOGE(TAG, "MD5 mismatch for %s", localPath); + fileSystem.remove(tempPath.c_str()); + return false; + } + + // Replace original file with verified temp file + if (fileSystem.exists(localPath)) { + fileSystem.remove(localPath); + } + if (!fileSystem.rename(tempPath.c_str(), localPath)) { + ESP_LOGE(TAG, "Failed to rename temporary file"); + fileSystem.remove(tempPath.c_str()); + return false; + } + + return true; +} + +String AppUpdater::getLocalMD5(const char* filePath){ + File file = fileSystem.open(filePath, "r"); + if(!file){ + ESP_LOGE(TAG, "Error opening %s...", filePath); + return String(); + } + + MD5Builder md5Builder; + md5Builder.begin(); + size_t fileSize = file.size(); + size_t totalRead = 0; + size_t readLen = 0; + while (totalRead < fileSize) { + readLen = file.readBytes(reinterpret_cast(downloadBuffer.get()), std::min(fileSize - totalRead, size_t(BUFFER_SIZE))); + md5Builder.add(downloadBuffer.get(), readLen); + totalRead += readLen; + } + + md5Builder.calculate(); + file.close(); + return md5Builder.toString(); +} + +bool AppUpdater::updateFilesArray() { + int successCount = 0; + int totalFiles = jsonFilesArray.size(); + ESP_LOGI(TAG, "Found %d files in manifest", totalFiles); + + // Iterate over each file entry in the manifest + for (JsonObject file : jsonFilesArray) { + const char* remotePath = file["remote"]; + const char* localPath = file["local"]; + const char* expectedMd5 = file["md5"]; + + // Skip invalid entries + if (!remotePath || !localPath || !expectedMd5) { + ESP_LOGE(TAG, "Invalid file entry in manifest"); + continue; + } + + // Attempt to update the file + if (updateFile(remotePath, localPath, expectedMd5)) { + successCount++; + } + } + + ESP_LOGI(TAG, "Manifest update complete: %d/%d files updated", successCount, totalFiles); + return successCount == totalFiles; +} + +bool AppUpdater::updateApp() { + updateProgress(UpdateStatus::MESSAGE, 0, "Starting firmware update"); + + // Check for firmware section in manifest + if (!jsonManifest["firmware"].is() || !jsonManifest["firmware"]["md5"].is()) { + ESP_LOGE(TAG, "Invalid firmware section in manifest"); + updateProgress(UpdateStatus::ERROR, 0, "Firmware: Invalid firmware section in manifest"); + return false; + } + + // Get the firmware MD5 hash and URL + const char* expectedMd5 = jsonManifest["firmware"]["md5"]; + String firmwareUrl = String(bucketUrl) + appName; + + // Download the firmware + HTTPClient http; + http.begin(firmwareUrl); + int httpCode = http.GET(); + if (httpCode != HTTP_CODE_OK) { + ESP_LOGE(TAG, "Firmware download failed: %d", httpCode); + updateProgress(UpdateStatus::ERROR, 0, "Firmware: Firmware download failed"); + http.end(); + return false; + } + + // Check available space + size_t firmwareSize = http.getSize(); + if (!Update.begin(firmwareSize)) { + ESP_LOGE(TAG, "Firmware: Not enough space for update"); + updateProgress(UpdateStatus::ERROR, 0, "Firmware: Not enough space for update"); + http.end(); + return false; + } + + // Set up MD5 checking + MD5Builder md5; + md5.begin(); + + // Download and verify firmware + WiFiClient* stream = http.getStreamPtr(); + size_t remaining = firmwareSize; + while (remaining > 0) { + size_t chunk = std::min(remaining, size_t(BUFFER_SIZE)); + size_t read = stream->readBytes(downloadBuffer.get(), chunk); + + // Check for timeout + if (read == 0) { + ESP_LOGE(TAG, "Read timeout"); + + Update.abort(); + http.end(); + return false; + } + + // Update MD5 and write firmware + md5.add(downloadBuffer.get(), read); + if (Update.write(downloadBuffer.get(), read) != read) { + ESP_LOGE(TAG, "Write failed"); + Update.abort(); + http.end(); + return false; + } + + remaining -= read; + updateProgress(UpdateStatus::DOWNLOADING, (firmwareSize - remaining) * 100 / firmwareSize, "firmware"); + } + + // Verify MD5 + md5.calculate(); + String calculatedMd5 = md5.toString(); + if (!calculatedMd5.equals(expectedMd5)) { + ESP_LOGE(TAG, "MD5 mismatch. Expected: %s, Got: %s", expectedMd5, calculatedMd5.c_str()); + updateProgress(UpdateStatus::MD5_FAILED, 0, "Firmware: MD5 mismatch"); + Update.abort(); + http.end(); + return false; + } + + // Finish update + if (!Update.end()) { + ESP_LOGE(TAG, "Update end failed"); + updateProgress(UpdateStatus::ERROR, 0, "Firmware: Update failed"); + http.end(); + return false; + } + + http.end(); + updateProgress(UpdateStatus::COMPLETE, 0, "Firmware: Complete"); + return true; +} + +bool AppUpdater::IsUpdateAvailable(){ + return updateAvailable; +} + + +AsyncEventSource* eventProgress = nullptr; +void startFirmwareUpdateTask(AsyncEventSource* evProg) { + eventProgress = evProg; + if(Update_Task_Handle) { + ESP_LOGW(TAG, "Firmware update task already running"); + return; + } + xTaskCreate(firmwareUpdateTask, "FirmwareUpdate", 1024*8, NULL, 1, &Update_Task_Handle); +} + +void firmwareUpdateTask(void* parameter) { + static const char* TAG = "UpdateTask"; + AppUpdater* updater = nullptr; + + try { + loadUpdateJson(); + + // Initialize updater + updater = new AppUpdater(LittleFS, localVersion, updateUrl.c_str(), "update.json", "firmware.bin"); + updater->setProgressCallback(updateProgress); + + ESP_LOGI(TAG, "Starting update check from: %s", updateUrl.c_str()); + + // Check and perform updates + if (!updater->checkManifest()) { throw std::runtime_error("Failed to check manifest"); } + + if (updater->IsUpdateAvailable()) { + ESP_LOGI(TAG, "Update available, updating files..."); + + if (!updater->updateFilesArray()) { + throw std::runtime_error("Failed to update files"); + } + + ESP_LOGI(TAG, "Updating firmware..."); + if (!updater->updateApp()) { + throw std::runtime_error("Failed to update firmware"); + } + ESP_LOGI(TAG, "Update successful, restarting..."); + + sendUpdateMessage("Restarting ", true, 100); + vTaskDelay(2000); + + ESP.restart(); + + } + + } catch (const std::exception& e) { + ESP_LOGE(TAG, "Update failed: %s", e.what()); + } + end: + delete updater; + Update_Task_Handle = NULL; + vTaskDelete(NULL); +} + + +void startVersionCheckTask() { + if(versionCheckTask_Handle != NULL) { + ESP_LOGW(TAG, "Version Check Tak already running"); + return; + } + xTaskCreate(versionCheckTask, "VersionCheckTask", 1024*8, NULL, 1, &versionCheckTask_Handle); +} + +void versionCheckTask(void* parameter){ + + if(updateUrl == ""){ + loadUpdateJson(); + } + + if(checkManifest(otaVersion) == false){ + ESP_LOGE(TAG, "Error checking manifest"); + } + + versionCheckTask_Handle = NULL; + vTaskDelete(NULL); +} + +void loadUpdateJson(void) { + try { + ESP_LOGD(TAG, "loadUpdateJaon function..."); + if(updateUrl == "") { + String updateJsonPath = "/system/update.json"; + + // Read and parse update.json + File file = LittleFS.open(updateJsonPath); + if (!file) { + throw std::runtime_error("Failed to open update.json"); + } + + JsonDocument doc; + DeserializationError error = deserializeJson(doc, file); + file.close(); + + if (error) { throw std::runtime_error("Failed to parse update.json"); } + + // Get update configuration + JsonObject jObj = doc.as(); + String baseUrl = jsonConstrainString(TAG, jObj, "baseurl", "https://storage.googleapis.com/boothifier/"); + String folderName = jsonConstrainString(TAG, jObj, "folder", "latest/"); + updateUrl = baseUrl + folderName; + + ESP_LOGD(TAG, "updateUrl: %s", updateUrl.c_str()); + } + } catch (const std::exception& e) { + ESP_LOGE(TAG, "Update failed: %s", e.what()); + } +} + +void updateProgress(AppUpdater::UpdateStatus newStatus, int percentage, const char* message = nullptr) { + + char buffer[128]; + const char* msg; + bool isComplete = false; + + switch (newStatus) { + case AppUpdater::UpdateStatus::IDLE: + snprintf(buffer, sizeof(buffer), "Update idle"); + msg = buffer; + break; + case AppUpdater::UpdateStatus::MESSAGE: + msg = message ? message : ""; + break; + case AppUpdater::UpdateStatus::DOWNLOADING: + snprintf(buffer, sizeof(buffer), "%s: Download progress: %d%%", message, percentage); + msg = buffer; + break; + case AppUpdater::UpdateStatus::VERIFYING: + snprintf(buffer, sizeof(buffer), "%s: Verifying update: %d%%", message, percentage); + msg = buffer; + break; + case AppUpdater::UpdateStatus::FILE_SKIPPED: + snprintf(buffer, sizeof(buffer), "%s: Skipping file update, already up to date", message); + msg = buffer; + break; + case AppUpdater::UpdateStatus::FILE_SAVED: + snprintf(buffer, sizeof(buffer), "%s: File Saved", message); + msg = buffer; + break; + case AppUpdater::UpdateStatus::MD5_FAILED: + snprintf(buffer, sizeof(buffer), "%s: MD5 Verification Failed", message); + msg = buffer; + break; + case AppUpdater::UpdateStatus::COMPLETE: + snprintf(buffer, sizeof(buffer), "Firmware Update Complete!!!"); + msg = buffer; + isComplete = true; + break; + case AppUpdater::UpdateStatus::ERROR: + snprintf(buffer, sizeof(buffer), "Error!: %s", message); + msg = buffer; + break; + default: + snprintf(buffer, sizeof(buffer), "Unknown update status: %d", (int)newStatus); + msg = buffer; + break; + } + + ESP_LOGI(TAG, "%s", msg); + sendUpdateMessage(msg, isComplete, percentage); +} + +void sendUpdateMessage(const char* message, bool complete, int progress = -1) { + if(eventProgress && eventProgress->count() > 0) { + JsonDocument jsonDoc; + jsonDoc["message"] = message; + jsonDoc["complete"] = complete; + jsonDoc["progress"] = progress; + String strMessage; + serializeJson(jsonDoc, strMessage); + eventProgress->send(strMessage.c_str(), "update", millis()); + } + else{ + ESP_LOGW(TAG, "No clients connected to event source"); + } + + bleUpgrade_send_message(message); +} + +bool checkManifest(Version& remoteVersion) { + const char* TAG = "manifestCheck"; + String url = updateUrl + "update.json"; + ESP_LOGD(TAG, "Fetching manifest from: %s", url.c_str()); + + // Start the HTTP client and send GET request for manifest + HTTPClient http; + http.begin(url); + int httpCode = http.GET(); + if (httpCode != HTTP_CODE_OK) { + ESP_LOGE(TAG, "HTTP GET failed, error: %d", httpCode); + http.end(); + return false; + } + + // Read the response + String payload = http.getString(); + http.end(); + + //ESP_LOGD(TAG, "%s", payload.c_str()); + + // Parse JSON + JsonDocument jsonManifest; + DeserializationError error = deserializeJson(jsonManifest, payload); + if (error) { + ESP_LOGE(TAG, "Failed to parse manifest: %s", error.c_str()); + return false; + } + + // Check for version section + JsonObject jsonVersion = jsonManifest["version"]; + + if (jsonVersion.isNull()) { + ESP_LOGE(TAG, "No version section in manifest"); + return false; + } + + // Get the remote version + byte major = jsonVersion["major"] | 0; + byte minor = jsonVersion["minor"] | 0; + byte patch = jsonVersion["patch"] | 0; + remoteVersion = {major, minor, patch}; + ESP_LOGI(TAG, "Remote version: %s", remoteVersion.toString().c_str()); + return true; +} + +/* +void setup() { + Serial.begin(115200); + + // Initialize WiFi connection first + // ... WiFi connection code ... + + // Initialize filesystem + if(!LittleFS.begin()) { + Serial.println("LittleFS Mount Failed"); + return; + } + + // Create updater instance with: + // - Current version: "1.0.0" + // - Update server URL: "https://my-update-server.com/" + // - Filesystem: LittleFS + AppUpdater updater("1.0.0", "https://storage.googleapis.com/boothifier/latest/", LittleFS); + + // Set progress callback + updater.setProgressCallback([](int progress) { + Serial.printf("Update progress: %d%%\n", progress); + }); + + // Check and update firmware + if (updater.checkAndUpdate()) { + Serial.println("Update successful! Rebooting..."); + ESP.restart(); + } + + // Update specific files from manifest + int updatedFiles = updater.updateFilesFromManifest("test_update.json"); + Serial.printf("Updated %d files\n", updatedFiles); +} + +*/ \ No newline at end of file diff --git a/src/BLE_SP110E.cpp b/src/BLE_SP110E.cpp new file mode 100644 index 0000000..fba7765 --- /dev/null +++ b/src/BLE_SP110E.cpp @@ -0,0 +1,389 @@ +#include "BLE_SP110E.h" +//#include "NimBLEDevice.h" +#include "esp_log.h" +#include "WiFi.h" +#include "ATALights.h" + +static const char *tag = "BLE_SP110E"; + +#define BT_SERVICE "FFE0" +#define BT_SP110E_CHARACTERISTIC "FFE1" +NimBLECharacteristic *pSP110ECharacteristic = nullptr; + +#define BT_STICK_CHARACTERISTIC "FFE2" +NimBLECharacteristic *pStickCharacteristic = nullptr; + +NimBLEClient* pStickClient; +NimBLERemoteCharacteristic* pRemoteCharacteristic; +bool masterFound = false; +NimBLEAdvertisedDevice* myDevice; + +TaskHandle_t LightStick_Client_Task_Handle = NULL; + +//#define UPGRADE_SERVICE_UUID "abcdef01-2345-6789-1234-56789abcdef0" +//#define UPGRADE_CHARACTERISTIC_UUID "abcdef01-2345-6789-1234-56789abcdef1" + +//typedef enum {SM16703,TM1804,UCS1903,WS2811,WS2801,SK6812,LPD6803,LPD8806,APA102,APA105,DMX512,TM1914,TM1913,P9813,INK1003,P943S,P9411,P9413,TX1812,TX1813,GS8206,GS8208,SK9822,TM1814,SK6812_RGBW,P9414,PG412,IC_MODEL_COUNT } IC_MODELS; +//typedef enum {RGB,RBG,GRB,GBR,BRG,BGR,SEQUENCE_COUNT} SEQUENCES; + +typedef struct { + uint8_t checksum; + uint8_t enable; + uint8_t preset; + uint8_t speed; + uint8_t bright; + uint8_t ic_model; + uint8_t channel; // sequence + uint8_t count_msb; // 16bit number + uint8_t count_lsb; + uint8_t red; + uint8_t green; + uint8_t blue; + uint8_t white; +} INFO_PACK; + +INFO_PACK led_status = {}; +uint8_t led_num = 1; + +uint8_t calculateChecksum(const uint8_t bArr[]) { + return (uint8_t)((bArr[2]) | ((bArr[0] << 1) & 254 & 105) | bArr[1]); +} + +// Class for handling characteristic events +class SP110ECallbacks : public NimBLECharacteristicCallbacks { + void onWrite(NimBLECharacteristic *pCharacteristic) override { + std::string rawValue = pCharacteristic->getValue(); + const uint8_t* value = reinterpret_cast(rawValue.data()); + size_t length = rawValue.length(); + ESP_LOGI(tag, "Data received 0x%02X, 0x%02X, 0x%02X (length %zu):", value[0], value[1], value[2], length); + + sendToAllClients(value, length); + process_BLE_SP110E_Command(value, length, pCharacteristic); + /* + if (length >= 4) { + uint8_t command = value[3]; + ESP_LOGI(tag, "Command received: 0x%02X", command); + + uint8_t response[sizeof(INFO_PACK)]; // Use a single response buffer + + // Handle different commands + switch (command) { + case TURN_ON: + Lights_Set_ON(); + led_status.enable = 1; + //ESP_LOGI(tag, "Lights ON"); + break; + case TURN_OFF: + Lights_Set_OFF(); + led_status.enable = 0; + //ESP_LOGI(tag, "Lights OFF"); + break; + case SET_STATIC_COLOR: + led_status.red = value[1]; + led_status.green = value[2]; + led_status.blue = value[0]; + Lights_Set_Animation(SOLID_COLOR_INDEX, value[0], value[1], value[2]); + //ESP_LOGI(tag, "Color set to R:%d G:%d B:%d", led_status.red, led_status.green, led_status.blue); + break; + case SET_BRIGHT: + led_status.bright = value[0]; + Lights_Set_Brightness(value[0]); + //ESP_LOGI(tag, "Bright set to %d", led_status.bright); + break; + case SET_WHITE: + led_status.white = value[0]; + Lights_Set_White(value[0]); + //ESP_LOGI(tag, "White set to %d", led_status.white); + break; + case SET_PRESET: + led_status.preset = value[0]; + Lights_Set_Animation(value[0], value[1], value[2], 0); + //ESP_LOGI(tag, "Animation set to %d", led_status.preset); + break; + case SET_SPEED: + led_status.speed = value[0]; + ESP_LOGI(tag, "Mode set to %d", led_status.speed); + break; + case GET_CHECK_DEVICE: // This prepends a checksum + led_status.checksum = calculateChecksum((uint8_t *)value); + pCharacteristic->setValue((uint8_t *)&led_status, sizeof(INFO_PACK)); + pCharacteristic->notify(); // Send the data immediately + ESP_LOGI(tag, "Check Device"); + break; + case GET_DEVICE_INFO: // No checksum + led_status.checksum = 0; + pCharacteristic->setValue(((uint8_t *)&led_status) + 1, sizeof(INFO_PACK) - 1); + pCharacteristic->notify(); // Send the data immediately + ESP_LOGI(tag, "Get Device Info"); + break; + case SET_IC_MODEL: + led_status.ic_model = value[0]; + ESP_LOGI(tag, "IC Model set to %d", led_status.ic_model); + break; + case SET_RGB_SEQUENCE: + led_status.channel = 0; + ESP_LOGI(tag, "Set RGB Sequence"); + break; + case SET_LED_NUM: + led_status.count_msb = 0; + led_status.count_lsb = 100; + ESP_LOGI(tag, "Set LED Num"); + break; + case SET_DEVICE_NAME: + ESP_LOGI(tag, "Set Device Name"); + break; + default: + ESP_LOGW(tag, "Unknown command: 0x%02X", command); + break; + } + } else { + ESP_LOGW(tag, "Data too short to process"); + } + */ + } +}; + +class LightStickCallbacks : public NimBLECharacteristicCallbacks { + void onRead(NimBLECharacteristic *pCharacteristic) override { + pCharacteristic->setValue("Hello..."); + pCharacteristic->notify(); + } +}; + + +void sendToAllClients(const uint8_t *data, size_t len) { + // Check if the characteristic is valid and has subscribed clients + if (pStickCharacteristic != nullptr) { + pStickCharacteristic->setValue(data, len); + // This call notifies all connected clients with the same message + pStickCharacteristic->notify(); + } +} + + +void process_BLE_SP110E_Command(const uint8_t* val, uint8_t len, NimBLECharacteristic* bleChar) { + + if (len >= 4) { + uint8_t command = val[3]; + ESP_LOGI(tag, "Command received: 0x%02X", command); + + uint8_t response[sizeof(INFO_PACK)]; // Use a single response buffer + + // Handle different commands + switch (command) { + case TURN_ON: + Lights_Set_ON(); + led_status.enable = 1; + //ESP_LOGI(tag, "Lights ON"); + break; + case TURN_OFF: + Lights_Set_OFF(); + led_status.enable = 0; + //ESP_LOGI(tag, "Lights OFF"); + break; + case SET_STATIC_COLOR: + led_status.red = val[1]; + led_status.green = val[2]; + led_status.blue = val[0]; + Lights_Set_Animation(SOLID_COLOR_INDEX, val[0], val[1], val[2]); + //ESP_LOGI(tag, "Color set to R:%d G:%d B:%d", led_status.red, led_status.green, led_status.blue); + break; + case SET_BRIGHT: + led_status.bright = val[0]; + Lights_Set_Brightness(val[0]); + //ESP_LOGI(tag, "Bright set to %d", led_status.bright); + break; + case SET_WHITE: + led_status.white = val[0]; + Lights_Set_White(val[0]); + //ESP_LOGI(tag, "White set to %d", led_status.white); + break; + case SET_PRESET: + led_status.preset = val[0]; + Lights_Set_Animation(val[0], val[1], val[2], 0); + //ESP_LOGI(tag, "Animation set to %d", led_status.preset); + break; + case SET_SPEED: + led_status.speed = val[0]; + ESP_LOGI(tag, "Mode set to %d", led_status.speed); + break; + case GET_CHECK_DEVICE: // This prepends a checksum + led_status.checksum = calculateChecksum(val); + if(bleChar != nullptr){ + bleChar->setValue((uint8_t *)&led_status, sizeof(INFO_PACK)); + bleChar->notify(); // Send the data immediately + } + ESP_LOGI(tag, "Check Device"); + break; + case GET_DEVICE_INFO: // No checksum + led_status.checksum = 0; + if(bleChar != nullptr){ + bleChar->setValue(((uint8_t *)&led_status) + 1, sizeof(INFO_PACK) - 1); + bleChar->notify(); // Send the data immediately + } + ESP_LOGI(tag, "Get Device Info"); + break; + case SET_IC_MODEL: + led_status.ic_model = val[0]; + ESP_LOGI(tag, "IC Model set to %d", led_status.ic_model); + break; + case SET_RGB_SEQUENCE: + led_status.channel = 0; + ESP_LOGI(tag, "Set RGB Sequence"); + break; + case SET_LED_NUM: + led_status.count_msb = 0; + led_status.count_lsb = 100; + ESP_LOGI(tag, "Set LED Num"); + break; + case SET_DEVICE_NAME: + ESP_LOGI(tag, "Set Device Name"); + break; + default: + ESP_LOGW(tag, "Unknown command: 0x%02X", command); + break; + } + } +} + + +void Init_BLE_SP110E(NimBLEServer* pServer) { + led_status.speed = 10; + led_status.bright = 50; + led_status.ic_model = 0; + led_status.count_lsb = 20; + + // Create BLE Service + NimBLEService *pService = pServer->createService( BT_SERVICE ); + + // Create FFE1 Characteristic with WRITE and NOTIFY properties + pSP110ECharacteristic = pService->createCharacteristic( + BT_SP110E_CHARACTERISTIC, + NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY + ); + + // Register the callback with the characteristic + pSP110ECharacteristic->setCallbacks(new SP110ECallbacks()); + ESP_LOGI(tag, "Custom callback registered!"); + + /************* Light Stick Characteristic ***************/ + pStickCharacteristic = pService->createCharacteristic( + BT_STICK_CHARACTERISTIC, + NIMBLE_PROPERTY::NOTIFY + ); + + // Register the callback with the characteristic + pStickCharacteristic->setCallbacks(new LightStickCallbacks()); + ESP_LOGI(tag, "Custom callback registered!"); + + /****************************************************/ + + + // Start the Service + pService->start(); + + // Configure Advertising + NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); + pAdvertising->addServiceUUID( BT_SERVICE ); // Advertise the FFE0 service UUID + + const uint8_t manufacturerData[] = {0x00, 0x00, 0x38, 0x93, 0x0E, 0x12, 0xAA, 0x08}; // Example Manufacturer Data + pAdvertising->setManufacturerData(std::string((char *)manufacturerData, sizeof(manufacturerData))); + +} + + +// Callback for notifications from characteristic2 +class LightStickClientCallback : public NimBLEClientCallbacks { + void onNotify(NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) { + Serial.print("Notification received from characteristic2: "); + //Serial.write(pData, length); + //Serial.println(); + process_BLE_SP110E_Command(pData, length, nullptr); + } +}; + +void Init_BLE_LightStick_Client(){ + if(LightStick_Client_Task_Handle != NULL) { + ESP_LOGW(tag, "Light Stick Client Task already running"); + return; + } + xTaskCreate(BLE_LightStick_Client_Task, "VersionCheckTask", 1024*8, NULL, 1, &LightStick_Client_Task_Handle); +} + +// Task for the BLE LightStick client +// This task connects to the BLE server and waits for notifications +void BLE_LightStick_Client_Task(void *parameter) { + static const char *tag = "BLE_LightStick_Client_Task"; + ESP_LOGI(tag, "BLE LightStick Client Task started"); + + while (true) { + // Only try to connect if we're not already connected and a device is set. + if ((pStickClient == nullptr || !pStickClient->isConnected()) && myDevice != nullptr) { + // Create a new client instance if needed. + if (pStickClient == nullptr) { + pStickClient = NimBLEDevice::createClient(); + pStickClient->setClientCallbacks(new LightStickClientCallback()); + } + ESP_LOGI(tag, "Attempting to connect to the server"); + if (pStickClient->connect(myDevice)) { + ESP_LOGI(tag, "Connected to the server"); + + // Get the service. + NimBLERemoteService* pRemoteService = pStickClient->getService(BT_SERVICE); + if (pRemoteService == nullptr) { + ESP_LOGE(tag, "Failed to find service UUID: %s", BT_SERVICE); + pStickClient->disconnect(); + } else { + // Get the characteristic. + pRemoteCharacteristic = pRemoteService->getCharacteristic(BT_STICK_CHARACTERISTIC); + if (pRemoteCharacteristic != nullptr && pRemoteCharacteristic->canNotify()) { + pRemoteCharacteristic->subscribe(true, [](NimBLERemoteCharacteristic* pRemoteCharacteristic, + uint8_t* pData, size_t length, bool isNotify) { + Serial.print("Notification received: "); + Serial.write(pData, length); + Serial.println(); + process_BLE_SP110E_Command(pData, length, nullptr); + }); + } else { + ESP_LOGE(tag, "Failed to register for notifications"); + pStickClient->disconnect(); + } + } + } else { + ESP_LOGE(tag, "Failed to connect to the server"); + // Delete the client instance so that a new one is created next time. + if (pStickClient != nullptr) { + NimBLEDevice::deleteClient(pStickClient); + pStickClient = nullptr; + } + // Wait before retrying. + vTaskDelay(pdMS_TO_TICKS(5000)); + continue; + } + } + + // If connected, monitor the connection. + if (pStickClient != nullptr && pStickClient->isConnected()) { + // Here you could add additional connection health checks if needed. + vTaskDelay(pdMS_TO_TICKS(1000)); + } + else { + // Not connected. Wait a while before the next connection attempt. + vTaskDelay(pdMS_TO_TICKS(5000)); + } + } +} + + + +// Callback for BLE scan results +class MyAdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks { + void onResult(NimBLEAdvertisedDevice* advertisedDevice) { + if (advertisedDevice->getName() == "ESP32_BLE_Master") { // Replace with your server's name + NimBLEDevice::getScan()->stop(); + myDevice = advertisedDevice; + masterFound = true; + Serial.println("Device found!"); + } + } +}; \ No newline at end of file diff --git a/src/BLE_UpdateService.cpp b/src/BLE_UpdateService.cpp new file mode 100644 index 0000000..fe0381e --- /dev/null +++ b/src/BLE_UpdateService.cpp @@ -0,0 +1,151 @@ +#include "BLE_UpdateService.h" +#include "WiFi.h" +#include "my_wifi.h" +#include "global.h" +#include "AppUpgrade.h" +#include "AppVersion.h" + +static const char *tag = "BLE_UpdateService"; + +#define UPGRADE_SERVICE_UUID "abcdef01-2345-6789-1234-56789abcdef0" +#define UPGRADE_CHARACTERISTIC1_UUID "abcdef01-2345-6789-1234-56789abcdef1" +#define UPGRADE_CHARACTERISTIC2_UUID "abcdef02-2345-6789-1234-56789abcdef1" + +NimBLEService *pUpgradeService = nullptr; +NimBLECharacteristic *pUpgradeCharacteristic1 = nullptr; +NimBLECharacteristic *pUpgradeCharacteristic2 = nullptr; + +enum WIFI_STAT : byte { WIFI_DISCONNECTED=0, WIFI_BAD_CREDS=1, WIFI_NO_AP=2, WIFI_CONNECTED=3 }; + +struct updateStatus { + WIFI_STAT wifiStatus = WIFI_DISCONNECTED; + bool wifiOnline = false; + byte wifiIP[4] = {0, 0, 0, 0}; + byte currVersion[3] = {FIRMWARE_VERSION_MAJOR, FIRMWARE_VERSION_MINOR, FIRMWARE_VERSION_PATCH}; + byte newVersion[3] = {0, 0, 0}; +}updatePacket; + + + +// Class for handling characteristic events +class UpgradeChar_Callbacks : public NimBLECharacteristicCallbacks { + + void onWrite(NimBLECharacteristic *pCharacteristic) override { + std::string value = pCharacteristic->getValue(); + ESP_LOGD(tag, "Upgrade Char written with value: %s", value.c_str()); + + if (value.compare(0, 12, "wifi-connect") == 0) { // Update WiFi credentials + JsonDocument doc; + deserializeJson(doc, value.substr(13)); + JsonObject wifiJson = doc.as(); + String ssid = wifiJson["ssid"].as(); + String pass = wifiJson["pass"].as(); + ESP_LOGI(tag, "Wifi Credentials: %s, %s", ssid.c_str(), pass.c_str()); + + bool status = StartWifiConnectTask(ssid, pass); + if(status == true){ + updatePacket.wifiStatus = WIFI_DISCONNECTED; + updatePacket.wifiOnline = false; + updatePacket.wifiIP[0] = updatePacket.wifiIP[1] = updatePacket.wifiIP[2] = updatePacket.wifiIP[3] = 0; + }else{ + ESP_LOGI(tag, "Failed to start WiFi connection task"); + } + } + else if (value.compare("version-check") == 0) { // Check if new version is available + ESP_LOGI(tag, "Version check command received: newVersion=%d.%d.%d", otaVersion.major(), otaVersion.minor(), otaVersion.patch()); + if(updatePacket.newVersion[0] == 0){ + startVersionCheckTask(); // start the task and done + }else{ + ESP_LOGI(tag, "Version already checked"); + } + } + else if (value.compare("upgrade-start") == 0) { // Start OTA update + ESP_LOGI(tag, "Start OTA update command received"); + startFirmwareUpdateTask(nullptr); // start the task + } + else if (value.compare("rename-device") == 0) { // Start renaming device + ESP_LOGI(tag, "Start renane device command received"); + } + else { + ESP_LOGW(tag, "Unknown command received: %s", value.c_str()); + } + } + + void onRead(NimBLECharacteristic *pCharacteristic) override { + updatePacket.wifiOnline = InternetAvailable; + if(WiFi.status() == WL_CONNECTED){ + updatePacket.wifiStatus = WIFI_CONNECTED; + if(updatePacket.wifiIP[0] == 0){ + updatePacket.wifiIP[0] = WiFi.localIP()[0]; + updatePacket.wifiIP[1] = WiFi.localIP()[1]; + updatePacket.wifiIP[2] = WiFi.localIP()[2]; + updatePacket.wifiIP[3] = WiFi.localIP()[3]; + } + }else{ + updatePacket.wifiStatus = WIFI_DISCONNECTED; + if(updatePacket.wifiIP[0] > 0){ + updatePacket.wifiIP[0] = 0; + updatePacket.wifiIP[1] = 0; + updatePacket.wifiIP[2] = 0; + updatePacket.wifiIP[3] = 0; + } + } + + //update version + if(otaVersion.major() != 0){ + ESP_LOGI(tag, "Updated new version: major=%d, minor=%d, patch=%d", otaVersion.major(), otaVersion.minor(), otaVersion.patch()); + updatePacket.newVersion[0] = otaVersion.major(); + updatePacket.newVersion[1] = otaVersion.minor(); + updatePacket.newVersion[2] = otaVersion.patch(); + } + + pCharacteristic->setValue(reinterpret_cast(&updatePacket), sizeof(updatePacket)); + ESP_LOGI(tag, "Upgrade Char read"); + } +}; + + +void bleUpgrade_send_message(String s){ + if(pUpgradeCharacteristic2){ + if (s != nullptr) { + pUpgradeCharacteristic2->setValue(s); + pUpgradeCharacteristic2->notify(); + } else { + ESP_LOGW(tag, "Null string passed to bleUpgrade_send_message"); + } + } +} + + +void Init_UpgradeBLEService(NimBLEServer *pServer){ + + // Create Upgrade BLE Service + pUpgradeService= pServer->createService( UPGRADE_SERVICE_UUID ); + + pUpgradeCharacteristic1 = pUpgradeService->createCharacteristic( + UPGRADE_CHARACTERISTIC1_UUID, + NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY + ); + + // Register the callback with the characteristic + pUpgradeCharacteristic1->setCallbacks(new UpgradeChar_Callbacks()); + ESP_LOGI(tag, "Upgrade callback registered!"); + + + pUpgradeCharacteristic2 = pUpgradeService->createCharacteristic( + UPGRADE_CHARACTERISTIC2_UUID, + NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY + ); + + // Register the callback with the characteristic + pUpgradeCharacteristic2->setCallbacks(new UpgradeChar_Callbacks()); + ESP_LOGI(tag, "Upgrade callback registered!"); + + pUpgradeService->start(); + + NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); + pAdvertising->addServiceUUID( UPGRADE_SERVICE_UUID ); // Advertise service UUID + + +} + diff --git a/src/BleServer.cpp b/src/BleServer.cpp new file mode 100644 index 0000000..358d148 --- /dev/null +++ b/src/BleServer.cpp @@ -0,0 +1,68 @@ +#include "BleServer.h" +#include "esp_log.h" +#include "BLE_SP110E.h" +#include "BLE_UpdateService.h" + +static const char* tag = "BleServer"; + + +// Class for handling server events +class ServerCallbacks : public NimBLEServerCallbacks { + void onConnect(NimBLEServer* pServer) override { + ESP_LOGI(tag, "Client connected"); + // Ensure advertising remains active even after a client connects + NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising(); + if (pAdvertising != nullptr) { + pAdvertising->start(); + } + } + + void onDisconnect(NimBLEServer* pServer) override { + ESP_LOGI(tag, "Client disconnected"); + // Restart advertising on disconnect to keep it active always + NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising(); + if (pAdvertising != nullptr) { + pAdvertising->start(); + } + } +}; + +void Init_BleServer( bool isSP110EActive, bool isUpgradeActive) { + + ESP_LOGI(tag, "Initializing BLE..."); + static bool isInitialized = false; + if (!isInitialized) { + NimBLEDevice::init("ATALIGHTS"); + //NimBLEDevice::setMTU(247); // Set preferred MTU size (max 247 for BLE) + } + + NimBLEServer *pServer = NimBLEDevice::createServer(); + if (pServer == nullptr) { + ESP_LOGE(tag, "Failed to create BLE server"); + return; + } + + // setup the Sever Callbacks + pServer->setCallbacks(new ServerCallbacks()); + + // Initialize BLE with SP110E Service + if(isSP110EActive){ + Init_BLE_SP110E(pServer); + } + + // Initialize BLE with Upgrade Service + if(isUpgradeActive){ + Init_UpgradeBLEService(pServer); + } + + // Start BLE advertising + NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); + if (pAdvertising != nullptr) { + if (!pAdvertising->start()) { + ESP_LOGE(tag, "Failed to start advertising"); + return; + } + } else { + ESP_LOGE(tag, "Failed to get advertising object"); + } +} \ No newline at end of file diff --git a/src/ColorPalettes.cpp b/src/ColorPalettes.cpp new file mode 100644 index 0000000..76fbecc --- /dev/null +++ b/src/ColorPalettes.cpp @@ -0,0 +1,12 @@ +#include "ColorPalettes.h" + +void Create_Red_Yellow_Violet_Palette(CRGBPalette16& customPalette) { + customPalette = CRGBPalette16( + CRGB::Red, CRGB::Yellow, CRGB::Violet, + CRGB::Red, CRGB::Yellow, CRGB::Violet, + CRGB::Red, CRGB::Yellow, CRGB::Violet, + CRGB::Red, CRGB::Yellow, CRGB::Violet, + CRGB::Red, CRGB::Yellow, CRGB::Violet, + CRGB::Red + ); +} diff --git a/src/JsonConstrain.cpp b/src/JsonConstrain.cpp new file mode 100644 index 0000000..b8094c2 --- /dev/null +++ b/src/JsonConstrain.cpp @@ -0,0 +1,122 @@ +#include "JsonConstrain.h" +#include +#include + +template +void logConstrainedValue(const char* tag, const char* key, T value); + +template <> +void logConstrainedValue(const char* tag, const char* key, int value) { + ESP_LOGD(tag, "Key [%s] value: %d", key, value); +} + +template <> +void logConstrainedValue(const char* tag, const char* key, float value) { + ESP_LOGD(tag, "Key [%s] value: %f", key, value); +} + +template +void logClamping(const char* tag, const char* key, T value, T limit, const char* condition); + +template <> +void logClamping(const char* tag, const char* key, int value, int limit, const char* condition) { + ESP_LOGW(tag, "Key [%s] value too %s (%d). Clamping to [%d].", key, condition, value, limit); +} + +template <> +void logClamping(const char* tag, const char* key, float value, float limit, const char* condition) { + ESP_LOGW(tag, "Key [%s] value too %s (%f). Clamping to [%f].", key, condition, value, limit); +} + + + + +template +T jsonConstrain(const char *tag, const JsonObject &jsonObject, const char *key, T min, T max, T def) { + // Check if the key exists and is not null + if (!jsonObject[key].is()) { + ESP_LOGW(tag, "Key [%s] not found or null. Using default value.", key); + return def; + } + + // Attempt to parse the value + JsonVariant jsonValue = jsonObject[key]; + + // Check if the value type matches the expected type + if (!jsonValue.is()) { + ESP_LOGW(tag, "Key [%s] type mismatch. Using default value.", key); + return def; + } + + // Get the value + T value = jsonValue.as(); + + // Constrain to range + if (value < min) { + logClamping(tag, key, value, min, "low"); + value = min; + } else if (value > max) { + logClamping(tag, key, value, max, "high"); + value = max; + } + + logConstrainedValue(tag, key, value); + return value; +} + + +const char* jsonConstrainChar(const char *tag, const JsonObject &jsonObject, const char *key, const char *def) { + if (!jsonObject[key].is()) { + ESP_LOGW(tag, "Key [%s] not found or null. Using default value.", key); + return strdup(def); + } + + String value = jsonObject[key].as(); + if (value.isEmpty()) { + ESP_LOGW(tag, "Key [%s] value is empty. Using default value.", key); + return strdup(def); + } + + ESP_LOGD(tag, "Key [%s] value: %s", key, value); + return strdup(value.c_str()); +} + + +String jsonConstrainString(const char *tag, const JsonObject &jsonObject, const char *key, String def) { + // Check if the key exists and is not null + if (!jsonObject[key].is()) { + ESP_LOGW(tag, "Key [%s] not found or null. Using default value [%s].", key, def.c_str()); + return def; + } + + // Extract the value as a String + String value = jsonObject[key].as(); + + // Check if the value is empty + if (value.isEmpty()) { + ESP_LOGW(tag, "Key [%s] value is empty. Using default value [%s].", key, def.c_str()); + return def; + } + + ESP_LOGD(tag, "Key [%s] value: %s", key, value.c_str()); + return value; +} + + +bool jsonConstrainBool(const char *tag, const JsonObject &jsonObject, const char *key, bool def) { + // Check if the key exists and is of type boolean + if (!jsonObject[key].is()) { + ESP_LOGW(tag, "Key [%s] not found or not a boolean. Using default value [%s].", key, def ? "true" : "false"); + return def; + } + + // Retrieve and return the value + bool value = jsonObject[key].as(); + ESP_LOGD(tag, "Key [%s] value: %s", key, value ? "true" : "false"); + return value; +} + + +// Explicit instantiations +template int jsonConstrain(const char *, const JsonObject &, const char *, int, int, int); +template float jsonConstrain(const char *, const JsonObject &, const char *, float, float, float); diff --git a/src/PWM_Output.cpp b/src/PWM_Output.cpp new file mode 100644 index 0000000..d071bfe --- /dev/null +++ b/src/PWM_Output.cpp @@ -0,0 +1,96 @@ +#include "PWM_Output.h" +#include + + +const char* tag = "pwmout"; + +const float binaryPow[17] = { + 0, 2, 4, 8, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767, 65535 +}; + +PWM_Output::PWM_Output(int8_t pin, uint8_t ch, uint8_t res, uint32_t freq, float maxDuty, bool visionCorrected){ + this->currDuty = 0; + this->ch = ch; + this->maxDuty = maxDuty; + this->visionCorrected = visionCorrected; + this->freq = freq; + setResolution(res); + pinMode(pin, OUTPUT); + + uint32_t actualFreq = ledcSetup(ch, freq, res); + if (actualFreq != freq) { + ESP_LOGE(tag, "pwmOut-> ch:%d ledcSetup failed! Requested freq: %d, Actual freq: %d", ch, freq, actualFreq); + return; + } + + ledcAttachPin(pin, ch); + setOutput(this->currDuty); +} + +void PWM_Output::setMaxDuty(float duty) { + // add any validation or constraints here + if(duty < 0) { + duty = 0.0; + } + else if(duty > 100.0) { + duty = 100.0; + } + maxDuty = duty; +} + +// Range is 0 to 100% +void PWM_Output::setOutput(float duty){ + // Clamp the duty cycle to the range [0.0, maxDuty] + if (duty < 0.0) { + duty = 0.0; + } else if (duty > maxDuty) { + duty = maxDuty; + } + + // calculate correct duty value + int outDutyVal; + if(this->visionCorrected){ + outDutyVal = linearizeOutput(duty); + } + else{ + outDutyVal = static_cast(duty * this->standardFactor); + } + + ledcWrite(this->ch, outDutyVal); + this->currOutVal = outDutyVal; + this->currDuty = duty; +} + + +void PWM_Output::setFreq(uint32_t fq){ + uint32_t newFreq; + if(this->freq != fq){ + newFreq = ledcChangeFrequency(this->ch, fq, this->res); + if(newFreq){ + this->freq = fq; + } + } +} + +void PWM_Output::setResolution(uint8_t res){ + this->res = res; + if(this->res < 4) this->res = 4; + if(this->res > 16) this->res = 16; + + this->standardFactor = binaryPow[res] * 0.01; + this->visionFactor = binaryPow[res] * 0.0001; + ESP_LOGD(tag, "factor=%f, vision=%f", this->standardFactor, this->visionFactor); +} + +int PWM_Output::linearizeOutput(float inp){ + if (inp < 0.0) { + inp = 0.0; + } else if (inp > 100.0) { + inp = 100.0; + } + return static_cast(inp * inp * this->visionFactor); +} + +int PWM_Output::getOutVal(void){ + return ledcRead(this->ch); +} \ No newline at end of file diff --git a/src/Ramp_Lights.cpp b/src/Ramp_Lights.cpp new file mode 100644 index 0000000..115a9f0 --- /dev/null +++ b/src/Ramp_Lights.cpp @@ -0,0 +1,62 @@ +#include "Ramp_Lights.h" +#include + +static const char* tag = "ramp"; + +// TODO add tickSkip to instanciation +#define TickDelayCount 5 + +RAMP_LIGHT::RAMP_LIGHT(OneButton* button, PWM_Output* pwmOutput, int min, int max, float step) + : button(button), pwmOutput(pwmOutput), min(min), max(max), step(step) { + + button->attachClick([](void* context) { static_cast(context)->singleClick(); }, this); + button->attachLongPressStart([](void* context) { static_cast(context)->longPressStart(); }, this); + button->attachLongPressStop([](void* context) { static_cast(context)->longPressStop(); }, this); + button->attachDuringLongPress([](void* context) { static_cast(context)->duringLongPress(); }, this); + + if(min < 0.0) min = 0.0; + if(max > 100.0) max = 100.0; + currentValue = min; + IsOn = false; + rampState = RampingUp; +} + +void RAMP_LIGHT::tick(){ + button->tick(); +} + +void RAMP_LIGHT::singleClick(){ + IsOn = ! IsOn; + if(IsOn){ + pwmOutput->setOutput(currentValue); + ESP_LOGD(tag, "Light On"); + }else{ + pwmOutput->setOutput(0.0); + ESP_LOGD(tag, "Light Off"); + } +} + +void RAMP_LIGHT::longPressStart(){ + tickCount = TickDelayCount; + ESP_LOGD(tag, "ramp long press start"); +} + +void RAMP_LIGHT::longPressStop(){ + if(IsOn){ + rampState = (rampState == RampingUp) ? RampingDown : RampingUp; + } + ESP_LOGD(tag, "ramp long press stop"); +} + +// Ramping Logic +void RAMP_LIGHT::duringLongPress(){ + if(IsOn){ + if (tickCount > 0 && --tickCount == 0) { + currentValue += (rampState == RampingUp) ? step : -step; + currentValue = constrain(currentValue, min, max); + pwmOutput->setOutput(currentValue); + ESP_LOGD(tag, "duty: %f, sent val: %d, actual val: %d", currentValue, pwmOutput->currOutVal, pwmOutput->getOutVal()); + tickCount = TickDelayCount; + } + } +} \ No newline at end of file diff --git a/src/common/HSVTable.cpp b/src/common/HSVTable.cpp new file mode 100644 index 0000000..a5643e7 --- /dev/null +++ b/src/common/HSVTable.cpp @@ -0,0 +1,175 @@ +#include "HSVTable.h" +#include +#include "LEDStrip.h" + + +const uint8_t lights[360]={ + 0, 0, 0, 0, 0, 1, 1, 2, + 2, 3, 4, 5, 6, 7, 8, 9, + 11, 12, 13, 15, 17, 18, 20, 22, + 24, 26, 28, 30, 32, 35, 37, 39, + 42, 44, 47, 49, 52, 55, 58, 60, + 63, 66, 69, 72, 75, 78, 81, 85, + 88, 91, 94, 97, 101, 104, 107, 111, +114, 117, 121, 124, 127, 131, 134, 137, +141, 144, 147, 150, 154, 157, 160, 163, +167, 170, 173, 176, 179, 182, 185, 188, +191, 194, 197, 200, 202, 205, 208, 210, +213, 215, 217, 220, 222, 224, 226, 229, +231, 232, 234, 236, 238, 239, 241, 242, +244, 245, 246, 248, 249, 250, 251, 251, +252, 253, 253, 254, 254, 255, 255, 255, +255, 255, 255, 255, 254, 254, 253, 253, +252, 251, 251, 250, 249, 248, 246, 245, +244, 242, 241, 239, 238, 236, 234, 232, +231, 229, 226, 224, 222, 220, 217, 215, +213, 210, 208, 205, 202, 200, 197, 194, +191, 188, 185, 182, 179, 176, 173, 170, +167, 163, 160, 157, 154, 150, 147, 144, +141, 137, 134, 131, 127, 124, 121, 117, +114, 111, 107, 104, 101, 97, 94, 91, + 88, 85, 81, 78, 75, 72, 69, 66, + 63, 60, 58, 55, 52, 49, 47, 44, + 42, 39, 37, 35, 32, 30, 28, 26, + 24, 22, 20, 18, 17, 15, 13, 12, + 11, 9, 8, 7, 6, 5, 4, 3, + 2, 2, 1, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0}; + +const uint8_t HSVlights[61] = +{0, 4, 8, 13, 17, 21, 25, 30, 34, 38, 42, 47, 51, 55, 59, 64, 68, 72, 76, +81, 85, 89, 93, 98, 102, 106, 110, 115, 119, 123, 127, 132, 136, 140, 144, +149, 153, 157, 161, 166, 170, 174, 178, 183, 187, 191, 195, 200, 204, 208, +212, 217, 221, 225, 229, 234, 238, 242, 246, 251, 255}; + +const uint8_t HSVpower[121] = +{0, 2, 4, 6, 8, 11, 13, 15, 17, 19, 21, 23, 25, 28, 30, 32, 34, 36, 38, 40, +42, 45, 47, 49, 51, 53, 55, 57, 59, 62, 64, 66, 68, 70, 72, 74, 76, 79, 81, +83, 85, 87, 89, 91, 93, 96, 98, 100, 102, 104, 106, 108, 110, 113, 115, 117, +119, 121, 123, 125, 127, 130, 132, 134, 136, 138, 140, 142, 144, 147, 149, +151, 153, 155, 157, 159, 161, 164, 166, 168, 170, 172, 174, 176, 178, 181, +183, 185, 187, 189, 191, 193, 195, 198, 200, 202, 204, 206, 208, 210, 212, +215, 217, 219, 221, 223, 225, 227, 229, 232, 234, 236, 238, 240, 242, 244, +246, 249, 251, 253, 255}; + +// the real HSV rainbow +rgbpixel_t trueHSV(int angle) +{ + byte red, green, blue; + + if (angle<60) {red = 255; green = HSVlights[angle]; blue = 0;} else + if (angle<120) {red = HSVlights[120-angle]; green = 255; blue = 0;} else + if (angle<180) {red = 0, green = 255; blue = HSVlights[angle-120];} else + if (angle<240) {red = 0, green = HSVlights[240-angle]; blue = 255;} else + if (angle<300) {red = HSVlights[angle-240], green = 0; blue = 255;} else + {red = 255, green = 0; blue = HSVlights[360-angle];} + + //return {red, green, blue}; + return {red, green, blue}; +} + +// the 'power-conscious' HSV rainbow +rgbpixel_t powerHSV(int angle) +{ + byte red, green, blue; + if (angle<120) {red = HSVpower[120-angle]; green = HSVpower[angle]; blue = 0;} else + if (angle<240) {red = 0; green = HSVpower[240-angle]; blue = HSVpower[angle-120];} else + {red = HSVpower[angle-240]; green = 0; blue = HSVpower[360-angle];} + + return {red, green, blue}; +} + +// sine wave rainbow +rgbpixel_t sineHSV(int angle) +{ + return {lights[(angle+120)%360], lights[angle], lights[(angle+240)%360]}; +} + + +// hsv out: 0-360, input rgb 0-255 +float RGBToHSV(rgbpixel_t rgb) { + float delta; // min; + float h = 0, v; //, s; + + uint8_t min_ = std::min(std::min(rgb.red, rgb.grn), rgb.blu); + v = std::max(std::max(rgb.red, rgb.grn), rgb.blu); + delta = v - min_; + + if (rgb.red == v){ + h = (rgb.grn - rgb.blu) / delta; + }else if (rgb.grn == v){ + h = 2 + (rgb.blu - rgb.red) / delta; + }else if (rgb.blu == v){ + h = 4 + (rgb.red - rgb.grn) / delta; + } + + h *= 60; + + if(h < 0.0){ h = h + 360;} + if(h > 359.99){ h = 359.99;} + return isnan(h) ? 0.0 : h; +} + + +rgbpixel_t HSVToRGB(float H) { + float r = 0.0, g = 0.0, b = 0.0; + float S = 1.0; + float V = 1.0; + + if (H < 0) { H += 360; } + if (H > 359) { H -= 360; } + + if (S == 0.0) { + r = V; g = V; b = V; + }else { + int i; + float f, p, q, t; + + if (H == 360){ H = 0; } + else { H = H / 60; } + + i = (int)trunc(H); + f = H - i; + + p = V * (1.0 - S); + q = V * (1.0 - (S * f)); + t = V * (1.0 - (S * (1.0 - f))); + + switch (i) { + case 0: + r = V; g = t; b = p; break; + case 1: + r = q; g = V; b = p; break; + case 2: + r = p; g = V; b = t; break; + case 3: + r = p; g = q; b = V; break; + case 4: + r = t; g = p; b = V; break; + default: + r = V; g = p; b = q; break; + } + } + + rgbpixel_t pix; + pix.red = (uint8_t)(r * 255); + pix.grn = (uint8_t)(g * 255); + pix.blu = (uint8_t)(b * 255); + + return pix; +} + diff --git a/src/common/HSVTable.h b/src/common/HSVTable.h new file mode 100644 index 0000000..f88957c --- /dev/null +++ b/src/common/HSVTable.h @@ -0,0 +1,23 @@ +#ifndef HSVTABLE_H +#define HSVTABLE_H + +#include "LEDStrip.h" + + +rgbpixel_t trueHSV(int angle); + +rgbpixel_t powerHSV(int angle); + +rgbpixel_t sineHSV(int angle); + +rgbpixel_t HSVToRGB(float H); + +// Returne a 0-360 float +float RGBToHSV(rgbpixel_t rgb); + +// Input 0-360 float + + + + +#endif \ No newline at end of file diff --git a/src/common/LEDStrip.cpp b/src/common/LEDStrip.cpp new file mode 100644 index 0000000..8d7d2f0 --- /dev/null +++ b/src/common/LEDStrip.cpp @@ -0,0 +1,473 @@ +#include "LEDStrip.h" +#include "driver/i2s.h" +#include +#include "HSVTable.h" + +static const char* tag = "LEDStrip"; + +LEDSTRIP::LEDSTRIP(int port, int size, int pin, LED_ORDER ledOrder, int shift, int offset) +{ + this->port = port; + this->size = size; + this->shift = shift; + this->offset = offset; + this->effSize = this->size - this->offset; + this->ledOrder = ledOrder; + + // create pixel array and buffer array + pixels = new rgbpixel_t[size]; + out_buffer_size = size * PIXEL_SIZE; + out_buffer = new uint8_t[out_buffer_size + RESET_BUFFER_SIZE]; + reset_buffer = &out_buffer[out_buffer_size + 1]; // pointer to reset buffer section + active_out_buffer = &out_buffer[0] + (offset * PIXEL_SIZE); // start of the first active + + // initialize buffer + memset(pixels, 0, this->size * sizeof(rgbpixel_t)); + memset(out_buffer, 0, out_buffer_size + RESET_BUFFER_SIZE); + + dma_frame_size = I2S_TX_DMA_BUFF_SIZE; + if((size * PIXEL_SIZE) < I2S_TX_DMA_BUFF_SIZE) { + dma_frame_size = size * PIXEL_SIZE; + } + + // Although the buffer can be smaller than total buffer size it will require more cpu interrupts + // So this provides for the entire size rounded up to the closes buffer chunk (I2S_TX_DMA_BUFF_SIZE) + int dma_buff_count = 1 + ((out_buffer_size + RESET_BUFFER_SIZE + 2) / I2S_TX_DMA_BUFF_SIZE); + + // install driver + i2s_driver_config_t i2s_config = { + .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_TX), + .sample_rate = SAMPLE_RATE, + .bits_per_sample = I2S_BITS_PER_SAMPLE_8BIT, + .channel_format = i2s_channel_fmt_t(I2S_CHANNEL_FMT_RIGHT_LEFT), + .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S), + .intr_alloc_flags = ESP_INTR_FLAG_LEVEL2, // high interrupt priority, + //.intr_alloc_flags = ESP_INTR_FLAG_LOWMED, // high interrupt priority, + .dma_buf_count = dma_buff_count, // + .dma_buf_len = I2S_TX_DMA_BUFF_SIZE, + .use_apll = true + }; // possibly needed for accuracy + + //i2sEventQueue = xQueueCreate(8, sizeof(i2s_event_t)); + esp_err_t err = i2s_driver_install((i2s_port_t)port, &i2s_config, 8, &i2sEventQueue); + + if(err){ + if(err == ESP_ERR_INVALID_ARG) {ESP_LOGE(tag, "I2S driver error: Parameter error");} + else if(err == ESP_ERR_NO_MEM) {ESP_LOGE(tag, "I2S driver error: Out of Memory");} + else if(err == ESP_ERR_INVALID_STATE) {ESP_LOGE(tag, "I2S driver error: Port is in use");} + } + else{ + i2s_pin_config_t pin_config = {.bck_io_num = I2S_PIN_NO_CHANGE, + .ws_io_num = I2S_PIN_NO_CHANGE, + .data_out_num = pin, + .data_in_num = I2S_PIN_NO_CHANGE }; + + ESP_LOGD(tag, "I2S port pin configured...."); + err = i2s_set_pin((i2s_port_t)port, &pin_config); + if(err){ + if(err == ESP_ERR_INVALID_ARG) {ESP_LOGE(tag, "I2S driver error: Parameter error");} + else if(err == ESP_FAIL) {ESP_LOGE(tag, "I2S driver error: IO error");} + } + else{ + ESP_LOGD(tag, "I2S pin config success!"); + } + } + +} + + +#if(HIGH_RES_BIT_RATE == 1) +static const uint8_t bitpatterns[2] = {0b11000000, 0b11111100}; +void LEDSTRIP::updateBuff(void) { + uint8_t* buff = active_out_buffer; + for (uint16_t i = 0; i < this->effSize; i++) + { + uint8_t red = pixels[i].red >> powerDiv; + uint8_t grn = pixels[i].grn >> powerDiv; + uint8_t blu = pixels[i].blu >> powerDiv; + + *buff++ = bitpatterns[red >> 7 & 0x01]; + *buff++ = bitpatterns[red >> 6 & 0x01]; + *buff++ = bitpatterns[red >> 5 & 0x01]; + *buff++ = bitpatterns[red >> 4 & 0x01]; + *buff++ = bitpatterns[red >> 3 & 0x01]; + *buff++ = bitpatterns[red >> 2 & 0x01]; + *buff++ = bitpatterns[red >> 1 & 0x01]; + *buff++ = bitpatterns[red & 0x01]; + + *buff++ = bitpatterns[grn >> 7 & 0x01]; + *buff++ = bitpatterns[grn >> 6 & 0x01]; + *buff++ = bitpatterns[grn >> 5 & 0x01]; + *buff++ = bitpatterns[grn >> 4 & 0x01]; + *buff++ = bitpatterns[grn >> 3 & 0x01]; + *buff++ = bitpatterns[grn >> 2 & 0x01]; + *buff++ = bitpatterns[grn >> 1 & 0x01]; + *buff++ = bitpatterns[grn & 0x01]; + + *buff++ = bitpatterns[blu >> 7 & 0x01]; + *buff++ = bitpatterns[blu >> 6 & 0x01]; + *buff++ = bitpatterns[blu >> 5 & 0x01]; + *buff++ = bitpatterns[blu >> 4 & 0x01]; + *buff++ = bitpatterns[blu >> 3 & 0x01]; + *buff++ = bitpatterns[blu >> 2 & 0x01]; + *buff++ = bitpatterns[blu >> 1 & 0x01]; + *buff++ = bitpatterns[blu & 0x01]; + } + + *buff++ = 0; +} +#else +static const uint16_t bitpatterns[4] = {0x88, 0x8e, 0xe8, 0xee}; +void LEDSTRIP::updateBuff(void) +{ + uint8_t* buff = active_out_buffer; + + switch(this->ledOrder){ + case ORDER_RGB: + for (uint16_t i = 0; i < this->effSize; i++) + { + uint8_t red = pixels[i].red >> powerDiv; + uint8_t grn = pixels[i].grn >> powerDiv; + uint8_t blu = pixels[i].blu >> powerDiv; + + *buff++ = bitpatterns[red >> 6 & 0x03]; + *buff++ = bitpatterns[red >> 4 & 0x03]; + *buff++ = bitpatterns[red >> 2 & 0x03]; + *buff++ = bitpatterns[red & 0x03]; + + *buff++ = bitpatterns[grn >> 6 & 0x03]; + *buff++ = bitpatterns[grn >> 4 & 0x03]; + *buff++ = bitpatterns[grn >> 2 & 0x03]; + *buff++ = bitpatterns[grn & 0x03]; + + *buff++ = bitpatterns[blu >> 6 & 0x03]; + *buff++ = bitpatterns[blu >> 4 & 0x03]; + *buff++ = bitpatterns[blu >> 2 & 0x03]; + *buff++ = bitpatterns[blu & 0x03]; + } + break; + case ORDER_RBG: + for (uint16_t i = 0; i < this->size; i++) + { + uint8_t red = pixels[i].red >> powerDiv; + uint8_t grn = pixels[i].grn >> powerDiv; + uint8_t blu = pixels[i].blu >> powerDiv; + + *buff++ = bitpatterns[red >> 6 & 0x03]; + *buff++ = bitpatterns[red >> 4 & 0x03]; + *buff++ = bitpatterns[red >> 2 & 0x03]; + *buff++ = bitpatterns[red & 0x03]; + + *buff++ = bitpatterns[blu >> 6 & 0x03]; + *buff++ = bitpatterns[blu >> 4 & 0x03]; + *buff++ = bitpatterns[blu >> 2 & 0x03]; + *buff++ = bitpatterns[blu & 0x03]; + + *buff++ = bitpatterns[grn >> 6 & 0x03]; + *buff++ = bitpatterns[grn >> 4 & 0x03]; + *buff++ = bitpatterns[grn >> 2 & 0x03]; + *buff++ = bitpatterns[grn & 0x03]; + } + break; + case ORDER_GRB: + for (uint16_t i = 0; i < this->size; i++) + { + uint8_t red = pixels[i].red >> powerDiv; + uint8_t grn = pixels[i].grn >> powerDiv; + uint8_t blu = pixels[i].blu >> powerDiv; + + *buff++ = bitpatterns[grn >> 6 & 0x03]; + *buff++ = bitpatterns[grn >> 4 & 0x03]; + *buff++ = bitpatterns[grn >> 2 & 0x03]; + *buff++ = bitpatterns[grn & 0x03]; + + *buff++ = bitpatterns[red >> 6 & 0x03]; + *buff++ = bitpatterns[red >> 4 & 0x03]; + *buff++ = bitpatterns[red >> 2 & 0x03]; + *buff++ = bitpatterns[red & 0x03]; + + *buff++ = bitpatterns[blu >> 6 & 0x03]; + *buff++ = bitpatterns[blu >> 4 & 0x03]; + *buff++ = bitpatterns[blu >> 2 & 0x03]; + *buff++ = bitpatterns[blu & 0x03]; + } + break; + case ORDER_GBR: + for (uint16_t i = 0; i < this->size; i++) + { + uint8_t red = pixels[i].red >> powerDiv; + uint8_t grn = pixels[i].grn >> powerDiv; + uint8_t blu = pixels[i].blu >> powerDiv; + + *buff++ = bitpatterns[grn >> 6 & 0x03]; + *buff++ = bitpatterns[grn >> 4 & 0x03]; + *buff++ = bitpatterns[grn >> 2 & 0x03]; + *buff++ = bitpatterns[grn & 0x03]; + + *buff++ = bitpatterns[blu >> 6 & 0x03]; + *buff++ = bitpatterns[blu >> 4 & 0x03]; + *buff++ = bitpatterns[blu >> 2 & 0x03]; + *buff++ = bitpatterns[blu & 0x03]; + + *buff++ = bitpatterns[red >> 6 & 0x03]; + *buff++ = bitpatterns[red >> 4 & 0x03]; + *buff++ = bitpatterns[red >> 2 & 0x03]; + *buff++ = bitpatterns[red & 0x03]; + } + break; + case ORDER_BRG: + for (uint16_t i = 0; i < this->size; i++) + { + uint8_t red = pixels[i].red >> powerDiv; + uint8_t grn = pixels[i].grn >> powerDiv; + uint8_t blu = pixels[i].blu >> powerDiv; + + *buff++ = bitpatterns[blu >> 6 & 0x03]; + *buff++ = bitpatterns[blu >> 4 & 0x03]; + *buff++ = bitpatterns[blu >> 2 & 0x03]; + *buff++ = bitpatterns[blu & 0x03]; + + *buff++ = bitpatterns[red >> 6 & 0x03]; + *buff++ = bitpatterns[red >> 4 & 0x03]; + *buff++ = bitpatterns[red >> 2 & 0x03]; + *buff++ = bitpatterns[red & 0x03]; + + *buff++ = bitpatterns[grn >> 6 & 0x03]; + *buff++ = bitpatterns[grn >> 4 & 0x03]; + *buff++ = bitpatterns[grn >> 2 & 0x03]; + *buff++ = bitpatterns[grn & 0x03]; + } + break; + default: //ORDER_BGR: + for (uint16_t i = 0; i < this->size; i++) + { + uint8_t red = pixels[i].red >> powerDiv; + uint8_t grn = pixels[i].grn >> powerDiv; + uint8_t blu = pixels[i].blu >> powerDiv; + + *buff++ = bitpatterns[blu >> 6 & 0x03]; + *buff++ = bitpatterns[blu >> 4 & 0x03]; + *buff++ = bitpatterns[blu >> 2 & 0x03]; + *buff++ = bitpatterns[blu & 0x03]; + + *buff++ = bitpatterns[grn >> 6 & 0x03]; + *buff++ = bitpatterns[grn >> 4 & 0x03]; + *buff++ = bitpatterns[grn >> 2 & 0x03]; + *buff++ = bitpatterns[grn & 0x03]; + + *buff++ = bitpatterns[red >> 6 & 0x03]; + *buff++ = bitpatterns[red >> 4 & 0x03]; + *buff++ = bitpatterns[red >> 2 & 0x03]; + *buff++ = bitpatterns[red & 0x03]; + } + break; + } +} +#endif + +void LEDSTRIP::show(bool update) +{ + if(update){ updateBuff(); }; + + //i2s_stop((i2s_port_t)this->port); + i2s_start((i2s_port_t)this->port); + i2s_zero_dma_buffer((i2s_port_t)this->port); + i2s_write_data(out_buffer, out_buffer_size + RESET_BUFFER_SIZE); + + /* + i2s_event_t i2s_event; + while(1){ + if(xQueueReceive(i2sEventQueue, &i2s_event, 100)){ + if(i2s_event.type == I2S_EVENT_TX_DONE) { break;} + }else{ + break; + } + } + */ +} + +void LEDSTRIP::setPowerDiv(uint8_t div){ + if(div < 0){div = 0;} + if(div > 4){div = 4;} + powerDiv = div; +} + +// Function to write data to the I2S buffer +void LEDSTRIP::i2s_write_data(const void *data, size_t len) +{ + size_t bytesWritten = 0; + + while (bytesWritten < len) { + size_t bytesToWrite = len - bytesWritten; + + size_t written = 0; + esp_err_t err = i2s_write(I2S_NUM_0, (const char*)data + bytesWritten, bytesToWrite, &written, portMAX_DELAY); + if (err != ESP_OK) { + // Handle error + ESP_LOGE(tag, "i2s send error: %d", err); + break; + } + + bytesWritten += written; + } +} + +inline int LEDSTRIP::calcIndex(int index) +{ + //int x = (index + shift) % effSize; + //if(x < 0) { // convert nex index to positive range + // x += effSize; + //} + //return x + offset; + int x = (index + shift) % effSize; + x = (x < 0) ? (x + effSize) : x; + return (x + offset) % effSize; +} + +void LEDSTRIP::setPixel(int index, rgbpixel_t& col) +{ + pixels[calcIndex(index)] = col; +} + +void LEDSTRIP::setPixel(int index, const rgbpixel_t col) +{ + pixels[calcIndex(index)] = col; +} + +/* +void LEDSTRIP::setPixel(int index, rgbpixel_t col, uint8_t scale) +{ + uint16_t n = scale + 1; + uint8_t r = (col.red * n) >> 8; + uint8_t g = (col.grn * n) >> 8; + uint8_t b = (col.blu * n) >> 8; + pixels[calcIndex(index)] = {r, g, b}; +} +*/ + +void LEDSTRIP::setPixelRaw(int index, rgbpixel_t& col){ + pixels[index] = col; +} + +void LEDSTRIP::setPixelMirrored(int index, rgbpixel_t col) +{ + pixels[calcIndex(index)] = col; + pixels[calcIndex(-index-1)] = col; +} + +rgbpixel_t LEDSTRIP::getPixel(int index) +{ + return pixels[calcIndex(index)]; +} + +void LEDSTRIP::rotatePixels(LED_DIR dir) +{ + // store the shifted out pixels + rgbpixel_t tPix; + if(dir == DIR_FWD){ + tPix = pixels[size -1]; + for(int i=size-1; i > offset; i--){ + pixels[i] = pixels[i-1]; + } + pixels[offset] = tPix; + }else{ + tPix = pixels[offset]; + for(int i=offset; i < size-1; i++){ + pixels[i] = pixels[i+1]; + } + pixels[size-1] = tPix; + } +} + +void LEDSTRIP::shiftPixels(int numPixels, LED_DIR dir) +{ + // Calculate the shift direction and absolute shift count + int shiftDir = (dir == DIR_FWD) ? 1 : -1; + int absShiftCount = (numPixels % this->effSize) * shiftDir; + + // Shift the pixels by swapping them in place using circular buffering + for (int i = 0; i < this->effSize; i++) { + rgbpixel_t temp = pixels[i]; + int j = i; + + while (true) { + int k = j + absShiftCount; + + if (k < 0) { + k += this->effSize; + } else if (k >= this->effSize) { + k -= this->effSize; + } + + if (k == i) { + break; + } + + pixels[j] = pixels[k]; + j = k; + } + + pixels[j] = temp; + + // Check if we have completed a cycle + if (j == i + absShiftCount) { + break; + } + } +} + +void scalePixel(rgbpixel_t& pix, uint8_t _scale) +{ + uint16_t n = _scale + 1; + pix.red = (pix.red * n) >> 8; + pix.grn = (pix.grn * n) >> 8; + pix.blu = (pix.blu * n) >> 8; +} + +void linearizePixel(rgbpixel_t& pix){ + pix.red = ((int)pix.red * (int)pix.red ) >> 8; + pix.grn = ((int)pix.grn * (int)pix.grn ) >> 8; + pix.blu = ((int)pix.blu * (int)pix.blu ) >> 8; +} + + +void PixelFadeToBlack(rgbpixel_t& pix, uint8_t fadeValue) { + pix.red = (pix.red <= 8) ? 0 : pix.red - ((pix.red * fadeValue)>>8); + pix.grn = (pix.grn <= 8) ? 0 : pix.grn - ((pix.grn * fadeValue)>>8); + pix.blu = (pix.blu <= 8) ? 0 : pix.blu - ((pix.blu * fadeValue)>>8); +} + +void LEDSTRIP::zeroPixels(void) +{ + for(int i=0; i < this->size; i++){ + pixels[i] = {0,0,0}; + } +} + +void LEDSTRIP::fill(rgbpixel_t color, int startIndex, int count) +{ + int endIndex = startIndex + count; + for (int i = startIndex; i < endIndex; i++) { + pixels[calcIndex(i)] = color; + } +} + +// a factor of 0=no changer, factor=255 newCol replaces 100% +void LEDSTRIP::transitionPixel(int index, rgbpixel_t& newCol, uint8_t factor) +{ + int i = calcIndex(index); + int f = factor + 1; + pixels[i].red = (pixels[i].red * (256 - f) + newCol.red * f) >> 8; + pixels[i].grn = (pixels[i].grn * (256 - f) + newCol.grn * f) >> 8; + pixels[i].blu = (pixels[i].blu * (256 - f) + newCol.blu * f) >> 8; +} + +LED_ORDER getRGBOrder(const char* order){ + if(strcmp(order, "rgb")==0){ return ORDER_RGB; } + if(strcmp(order, "rbg")==0){ return ORDER_RBG; } + if(strcmp(order, "grb")==0){ return ORDER_GRB; } + if(strcmp(order, "gbr")==0){ return ORDER_GBR; } + if(strcmp(order, "brg")==0){ return ORDER_BRG; } + if(strcmp(order, "bgr")==0){ return ORDER_BGR; } + else{ return ORDER_GRB; } +} \ No newline at end of file diff --git a/src/common/LEDStrip.h b/src/common/LEDStrip.h new file mode 100644 index 0000000..6c18f0a --- /dev/null +++ b/src/common/LEDStrip.h @@ -0,0 +1,117 @@ +#ifndef LEDSTRIP_H +#define LEDSTRIP_H + +//#include +#include "LEDStrip.h" +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "esp_intr_alloc.h" + +#define HIGH_RES_BIT_RATE 0 + +#if(HIGH_RES_BIT_RATE == 1) + #define PIXEL_SIZE (8*3) + #define SAMPLE_RATE (360000) // 1.6us cycle + #define RESET_BUFFER_SIZE (100 * PIXEL_SIZE) // led latching timeout 300us mininum (50us older chips) +#else + // This can be set to 1 (normally 50) as long at the update freq isn't fast enough to interfere with the required reset length time + // This reduces memory requirements + #define RESET_PIXELS 1 + #define PIXEL_SIZE (4*3)// each colour takes 4 bytes and 3 colors per pixel + #define SAMPLE_RATE (160000) // 1.6us cycle + #define RESET_BUFFER_SIZE (RESET_PIXELS * PIXEL_SIZE) // led latching timeout 4 or more pixel lengths +#endif + +#define I2S_TX_DMA_BUFF_SIZE 128 // smaller buffer means it will start quicker + +typedef struct{ + uint8_t red; + uint8_t grn; + uint8_t blu; +} rgbpixel_t; + +enum LED_DIR{DIR_REV, DIR_FWD}; + +enum LED_ORDER{ORDER_RGB, ORDER_RBG, ORDER_GRB, ORDER_GBR, ORDER_BRG, ORDER_BGR}; + +LED_ORDER getRGBOrder(const char* order); + +void PixelFadeToBlack(rgbpixel_t& pix, uint8_t fadeValue); + +void scalePixel(rgbpixel_t& pix, uint8_t _scale); +void linearizePixel(rgbpixel_t& pix); +//rgbpixel_t scalePixel(rgbpixel_t pix, uint8_t _scale); + +//void i2sDmaInterruptHandler(void *arg); + +class LEDSTRIP { + public: + SemaphoreHandle_t i2sSemaphore; + int effSize; + int size; + int shift; + int offset; + int powerDiv = 0; + LED_ORDER ledOrder; + rgbpixel_t* pixels; + QueueHandle_t i2sEventQueue; + + LEDSTRIP(int port, int size, int pin, LED_ORDER ledOrder, int shift=0, int offset=0); + // process buffer before sending + void updateBuff(void); + // Send data to dma and I2S port + void show(bool update=true); + + void setPixel(int index, rgbpixel_t& col); + void setPixel(int index, const rgbpixel_t col); + void setPixel(int index, rgbpixel_t& col, uint8_t scale=255); + //void setPixelTrueHue(int index, int hueAngle); + //void setPixelPowerHue(int index, int hueAngle); + //void setPixelSineHue(int index, int hueAngle); + void setPixelRaw(int index, rgbpixel_t& col); + + void setPixelMirrored(int index, rgbpixel_t col); + rgbpixel_t getPixel(int index); + + //void scale(rgbpixel_t& pix, uint8_t _scale); + void fill(rgbpixel_t color, int startIndex, int count); + void fade(rgbpixel_t& col); + void zeroPixels(void); + //void setMirrored(void); + + // mirror pixels about the shift position + void rotatePixels(LED_DIR dir); + void shiftPixels(int numPixels, LED_DIR dir); + void transitionPixel(int index, rgbpixel_t& newCol, uint8_t factor); + //rgbpixel_t hueToRGB(uint8_t hue, uint8_t sat, uint8_t val); + + void setPowerDiv(uint8_t div); + + + private: + static int instanceCount; //number in class instances + + int port; + + + //intr_handle_t i2sInterruptHandle; + + int dma_frame_size; + // dma output buffer + uint8_t* out_buffer; + int out_buffer_size; + // pointer to actual active part of buffer + uint8_t* active_out_buffer; + // index position calculation + uint8_t* reset_buffer; + void i2s_write_data(const void *data, size_t size); + int calcIndex(int ind); + +}; + +//void IRAM_ATTR i2s0DmaTransmitComplete(void* arg); +//void IRAM_ATTR i2s1DmaTransmitComplete(void* arg); + +#endif diff --git a/src/common/color_tools.cpp b/src/common/color_tools.cpp new file mode 100644 index 0000000..3dc53d0 --- /dev/null +++ b/src/common/color_tools.cpp @@ -0,0 +1,69 @@ +#include "color_tools.h" +#include + +#define MAX_HUE 360.0 + + +/************************* CLASS - HUE PALLET DISPENSER ************************/ + +HUE_PALLET_DISPENSER::HUE_PALLET_DISPENSER(void){ + Initialize(0, 360, 6); +} + +void HUE_PALLET_DISPENSER::Initialize(float hue, float range, int colSteps){ + this->range = range; + this->hueSteps = colSteps; + + this->startHue = hue - this->range / 2; + if(this->startHue < 0.0){ + this->startHue += MAX_HUE; + }else if(this->startHue > MAX_HUE){ + this->startHue -= MAX_HUE; + } + + this->hueIndex = 0; + + if(this->hueSteps <= 1){ + this->hueIncrement = 0.0; + this->currHue = hue; + }else{ + this->hueIncrement = this->range / (this->hueSteps - 1); + } +} + + +int HUE_PALLET_DISPENSER::GetNextPalletHue(void){ + if(this->hueSteps > 1){ + this->currHue = this->startHue + this->hueIndex * this->hueIncrement; + if(this->currHue < 0){ + this->currHue += MAX_HUE; + }else if(this->currHue > MAX_HUE){ + this->currHue -= MAX_HUE; + } + + // TODO Remove later + //rgbpixel_t p = HUEtoRGB(huePallet.currHue); + //Log.traceln(" index: %d, hue= %F, col: %d, %d, %d", huePallet.hueIndex, huePallet.currHue, p.red, p.grn, p.blu); + + this->hueIndex = ++this->hueIndex % this->hueSteps; + return round(this->currHue); + }else{ + return round(this->currHue); + } +} + +float HUE_PALLET_DISPENSER::PeekNextPalletHue(int hueOffset){ + float tempHue = this->startHue + (this->hueIndex + hueOffset) * this->hueIncrement; + + if(tempHue < 0){ + tempHue += MAX_HUE; + }else if(tempHue > MAX_HUE){ + tempHue -= MAX_HUE; + } + + return tempHue; +} + +void HUE_PALLET_DISPENSER::SetHueIndex(int hueIndex){ + this->currHue = hueIndex; +} diff --git a/src/common/color_tools.h b/src/common/color_tools.h new file mode 100644 index 0000000..176ef2c --- /dev/null +++ b/src/common/color_tools.h @@ -0,0 +1,38 @@ +#ifndef _COLOR_TOOLS_H +#define _COLOR_TOOLS_H + + +class HUE_PALLET_DISPENSER { + public: + float range = 1.0; + float hueIncrement = 1.0; + float currHue = 1.0; + int hueIndex = 0; + int hueSteps = 1; + float startHue = 0; + + //HUE_PALLET_DISPENSER(float hue1, float hue2, int colSteps); + HUE_PALLET_DISPENSER(void); + void Initialize(float hue, float range, int colSteps); + int GetNextPalletHue(void); + float PeekNextPalletHue(int offset); + void SetHueIndex(int hueIndex); + private: +}; + +enum SPEED_PROFILE {SPEED_SIMPLE, SPEED_SQUARE, SPEED_LIN_ACCEL}; + +class SPEED_DISPENSER{ + public: + + SPEED_DISPENSER(void); + + void Initialize(int min, int max, int steps, SPEED_PROFILE prof); + int GetNextSpeedInterval(void); +}; + + + + + +#endif \ No newline at end of file diff --git a/src/common/fileSystem.cpp b/src/common/fileSystem.cpp new file mode 100644 index 0000000..685cb6b --- /dev/null +++ b/src/common/fileSystem.cpp @@ -0,0 +1,153 @@ +#include "fileSystem.h" +#include +#include + +static const char *tag = "fs"; + +#define LogEnabled true + +void Init_File_System(void){ + if (!LittleFS.begin()){ + ESP_LOGE(tag, "An Error occured while mounting LittleFS"); + return; + } + + // Get all information of your LITTLEFS + ESP_LOGD(tag, "File system info."); + ESP_LOGD(tag, "Total: %d, Used: %d, Free:%d", LittleFS.totalBytes(), LittleFS.usedBytes(), LittleFS.totalBytes() - LittleFS.usedBytes()); + + // Open dir folder + File dir = LittleFS.open("/"); + + // Cycle all the content + printAllFiles(); +} + +void getAllDirectories(String (&directoryList)[MAX_DIRECTORIES], int &count){ + File root = LittleFS.open("/"); + if (!root || !root.isDirectory()){ + ESP_LOGE("FileSystem", "Failed to open root directory or root is not a directory"); + return; + } + + directoryList[0] = "/"; + count = 1; + + File file = root.openNextFile(); + while (file) { + if (file.isDirectory()){ + if (count < 10){ // Ensure we don't overflow the array + directoryList[count] = '/' + String(file.name()); + count++; + }else{ + ESP_LOGW("FileSystem", "Directory list array is full"); + break; + } + } + file = root.openNextFile(); + } + + root.close(); +} + +void printFilesInDirectories(const String directoryList[], int count){ + for (int i = 0; i < count; i++){ + ESP_LOGD(tag, "Dir: %s", directoryList[i].c_str()); + + File dir = LittleFS.open(directoryList[i]); + if (!dir || !dir.isDirectory()){ + ESP_LOGE(tag, "Failed to open directory: %s", directoryList[i].c_str()); + continue; + } + + File file = dir.openNextFile(); + while (file){ + if (!file.isDirectory()){ + ESP_LOGD(tag, " File: %s", file.name()); + } + file = dir.openNextFile(); + } + + dir.close(); + } +} + +void printAllFiles(void){ + String directories[MAX_DIRECTORIES]; + int dirCount = 0; + + getAllDirectories(directories, dirCount); + + ESP_LOGD(tag, "File System Listing:"); + printFilesInDirectories(directories, dirCount); +} + +/*****************************************************************************************/ + +/* +void getAllDirectoriesRecursive(const String& path, String directoryList[], int& count, int maxSize) { + File dir = LittleFS.open(path); + if (!dir || !dir.isDirectory()) { + ESP_LOGE("FileSystem", "Failed to open directory: %s", path.c_str()); + return; + } + + File file = dir.openNextFile(); + while (file) { + if (file.isDirectory()) { + if (count < maxSize) { // Ensure we don't overflow the array + String dirName = String(file.name()); + if (!dirName.startsWith("/")) { + dirName = "/" + dirName; + } + directoryList[count] = dirName; + count++; + getAllDirectoriesRecursive(dirName, directoryList, count, maxSize); // Recursive call for subdirectories + } else { + ESP_LOGW("FileSystem", "Directory list array is full"); + break; + } + } + file = dir.openNextFile(); + } + + dir.close(); +} + +void getAllDirectories(String directoryList[], int& count, int maxSize) { + count = 0; + getAllDirectoriesRecursive("/", directoryList, count, maxSize); +} + +void printFilesInDirectories(const String directoryList[], int count) { + for (int i = 0; i < count; i++) { + ESP_LOGD(tag, "Dir: %s", directoryList[i].c_str()); + + File dir = LittleFS.open(directoryList[i]); + if (!dir || !dir.isDirectory()) { + ESP_LOGE(tag, "Failed to open directory: %s", directoryList[i].c_str()); + continue; + } + + File file = dir.openNextFile(); + while (file) { + if (!file.isDirectory()) { + ESP_LOGD(tag, " File: %s", file.name()); + } + file = dir.openNextFile(); + } + + dir.close(); + } +} + +void printAllFiles(int maxDirs) { + String directories[maxDirs]; + int dirCount = 0; + + getAllDirectories(directories, dirCount, maxDirs); + + ESP_LOGD(tag, "File System Listing:"); + printFilesInDirectories(directories, dirCount); +} +*/ \ No newline at end of file diff --git a/src/common/fileSystem.h b/src/common/fileSystem.h new file mode 100644 index 0000000..8c97d04 --- /dev/null +++ b/src/common/fileSystem.h @@ -0,0 +1,28 @@ +#ifndef _FILESYSTEM_H +#define _FILESYSTEM_H + +#define SPIFFS LITTLEFS +#include + +const int MAX_DIRECTORIES = 16; + +void Init_File_System(void); + +void readTestFile(void); + +void writeFilesToSerial(void); + +//void getAllDirectoriesRecursive(const String& path, String directoryList[], int& count, int maxSize); + +//void getAllDirectories(String directoryList[], int& count, int maxSize); +void getAllDirectories(String (&directoryList)[MAX_DIRECTORIES], int& count); + +void printFilesInDirectories(const String directoryList[], int count); + +//void printAllFiles(int maxDirs); +void printAllFiles(void); + + + + +#endif \ No newline at end of file diff --git a/src/common/luma-stiks.cpp b/src/common/luma-stiks.cpp new file mode 100644 index 0000000..3ad80e3 --- /dev/null +++ b/src/common/luma-stiks.cpp @@ -0,0 +1,249 @@ + +#include "luma-stiks.h" +#include "WiFi.h" + +static const char* tag = "stiks"; + +int PeerCount = 0; +PEER_ADDR peerList[LUMA_MAX_PEERS]; + +/* + -30 is absolute min, -90 is max +*/ +// TODO default rssi +LUMA_NODE* Luma_ScanAPMaching(String startName, float minRssi, int &countFound){ + // Initialize count + countFound = 0; + + // Start WiFi scan + int n = WiFi.scanNetworks(); + if (n == 0) { + Serial.println("No networks found"); + return nullptr; + } else { + // Dynamically allocate memory for lumaStik array + LUMA_NODE *node = new LUMA_NODE[n]; + + Serial.println("Networks found:"); + for (int i = 0; i < n; ++i) { + //if (WiFi.SSID(i).startsWith(nameFilter) && WiFi.RSSI(i) >= minRssi) { + if ( WiFi.SSID(i).startsWith(startName) ) { + // Update lumaStik array with details + node[countFound].ssid = WiFi.SSID(i); + memccpy(&node[countFound].peer.peer_addr, WiFi.BSSID(i), 6, sizeof(uint8_t)); + countFound++; + + ESP_LOGD(tag, "SSID: %s, MAC: %s, RSSi: %d", WiFi.SSID(i), WiFi.BSSIDstr(i), WiFi.RSSI(i)); + } + vTaskDelay(10); // Short delay for responsiveness + } + + WiFi.scanDelete(); + // If count is 0, free the allocated memory and return nullptr + if (countFound == 0) { + delete[] node; + return nullptr; + } + + return node; + } +} + +//bool Luma_Find_Node(const char* master_ssid, PEER_ADDR &addr, int &ch){ +bool Luma_Find_Node(const char* master_ssid, esp_now_peer_info_t &node){ + int n = WiFi.scanNetworks(); + bool ret = false; + + for(int i = 0; i < n; ++i){ + if(strcmp(WiFi.SSID(i).c_str(), master_ssid) == 0){ + uint8_t* mac = WiFi.BSSID(i); + memcpy(node.peer_addr, mac, 6); + node.channel = WiFi.channel(i); + String macStr = Luma_MacToStr(mac); + ESP_LOGD(tag, "Found: %s, mac: %s, ch: %d", WiFi.SSID(i).c_str(), macStr.c_str(), node.channel); + ret = true; + break; + } + } + + WiFi.scanDelete(); + return ret; +} + + +#define MAC_LENGTH 6 +int Luma_ConvertMACStringToMACAddress(const char* mChar, uint8_t* macAddr) { + // Check for null pointer or empty string + if (mChar == nullptr || strlen(mChar) < 17) { + return 1; + } + + uint8_t cnt = 0; + + for (uint8_t i = 0, j = 0; i < 17; i += 3, j++) { + int highNibble, lowNibble; + + highNibble = mChar[i]; + lowNibble = mChar[i + 1]; + + // Convert hexadecimal digit to decimal + highNibble = isdigit(highNibble) ? highNibble - '0' : toupper(highNibble) - 'A' + 10; + lowNibble = isdigit(lowNibble) ? lowNibble - '0' : toupper(lowNibble) - 'A' + 10; + + // Construct the byte and save it + macAddr[j] = (highNibble << 4) | lowNibble; + } + + return 0; +} + +esp_err_t Luma_Send_Packet(PEER_ADDR &peerAddr, LUMA_PACKET &packet){ + ESP_LOGD(tag, "Sending to Mac: %s", Luma_MacToStr(peerAddr).c_str()); + + esp_err_t result = esp_now_send(peerAddr, (const uint8_t*) &packet, sizeof(LUMA_PACKET)); + + if (result == ESP_OK) { + Serial.println("Success"); + } else if (result == ESP_ERR_ESPNOW_NOT_INIT) { + Serial.println("ESPNOW not Init."); + } else if (result == ESP_ERR_ESPNOW_ARG) { + Serial.println("Invalid Argument"); + } else if (result == ESP_ERR_ESPNOW_INTERNAL) { + Serial.println("Internal Error"); + } else if (result == ESP_ERR_ESPNOW_NO_MEM) { + Serial.println("ESP_ERR_ESPNOW_NO_MEM"); + } else if (result == ESP_ERR_ESPNOW_NOT_FOUND) { + Serial.println("Peer not found."); + } else { + Serial.println("Not sure what happened"); + } + + return result; +} + + +void Luma_SendToAll_Packet(LUMA_PACKET &packet){ + for(int i = 0; i < PeerCount; i++){ + Luma_Send_Packet(peerList[i], packet); + } +} + + +esp_err_t Luma_Node_Check(PEER_ADDR &addr){ + uint8_t a = 100; + return esp_now_send(addr, (const uint8_t*) &a, 1); +} + + +LUMA_NODE* Luma_Scan_For_Stations(String nameFilter, float minRssi, int &countFound) { + countFound = 0; + + // Start WiFi scan + int n = WiFi.scanNetworks(); + if (n == 0) { + Serial.println("No networks found"); + return nullptr; + } + else { + // Dynamically allocate memory for lumaStik array + LUMA_NODE *node = new LUMA_NODE[n]; + + Serial.println("Networks found:"); + for (int i = 0; i < n; ++i) { + //if (WiFi.SSID(i).startsWith(nameFilter) && WiFi.RSSI(i) >= minRssi) { + if ( WiFi.SSID(i).startsWith(nameFilter) ) { + // Update lumaStik array with details + node[countFound].ssid = WiFi.SSID(i); + memccpy(&node[countFound].peer.peer_addr, WiFi.BSSID(i), 6, sizeof(uint8_t)); + countFound++; + + ESP_LOGD(tag, "SSID: %s, MAC: %s, RSSi: %d", WiFi.SSID(i), WiFi.BSSIDstr(i), WiFi.RSSI(i)); + } + vTaskDelay(10); // Short delay for responsiveness + } + + // If count is 0, free the allocated memory and return nullptr + if (countFound == 0) { + delete[] node; + return nullptr; + } + + return node; + } +} + +esp_err_t Luma_Register_with_Master( esp_now_peer_info_t &masterNode, const char* nodeName){ + LUMA_PACKET packet; + packet.type = LPT_REG; + LUMA_REGISTER_PACKET *reg = (LUMA_REGISTER_PACKET *)packet.data; + strncpy(reg->ssid, nodeName, strlen(reg->ssid) + 1); + return Luma_Send_Packet(masterNode.peer_addr, packet); +} + +String Luma_MacToStr(uint8_t* mac){ + String macStr; + + for (int i = 0; i < 6; ++i) { + // Convert each byte to a hexadecimal string + // and append it to the String object + String section = String(mac[i], HEX); + if (section.length() == 1) { + section = "0" + section; + } + macStr += section; + + // Add colon between sections, but not after the last one + if (i < 5) { + macStr += ":"; + } + } + + // Convert the String to uppercase + macStr.toUpperCase(); + + return macStr; +} + +int Luma_PeerCount(){ + return PeerCount; +} + +bool Luma_Add_Peer(esp_now_peer_info_t &node) { + // check if the peer exists + bool exists = esp_now_is_peer_exist(node.peer_addr); + if ( exists) { + // Slave already paired. + ESP_LOGD(tag, "Already Paired"); + return true; + } else { + // Slave not paired, attempt pair + esp_err_t addStatus = esp_now_add_peer(&node); + if (addStatus == ESP_OK) { + // Pair success + memcpy(&peerList[PeerCount][0], node.peer_addr, 6); + PeerCount++; + ESP_LOGD(tag, "Pair success"); + return true; + } else if (addStatus == ESP_ERR_ESPNOW_NOT_INIT) { + // How did we get so far!! + ESP_LOGD(tag, "ESPNOW Not Init"); + return false; + } else if (addStatus == ESP_ERR_ESPNOW_ARG) { + ESP_LOGD(tag, "Invalid Argument"); + return false; + } else if (addStatus == ESP_ERR_ESPNOW_FULL) { + ESP_LOGD(tag, "Peer list full"); + return false; + } else if (addStatus == ESP_ERR_ESPNOW_NO_MEM) { + ESP_LOGD(tag, "Out of memory"); + return false; + } else if (addStatus == ESP_ERR_ESPNOW_EXIST) { + ESP_LOGD(tag, "Peer Exists"); + return true; + } else { + ESP_LOGD(tag, "Not sure what happened"); + return false; + } + } + +} \ No newline at end of file diff --git a/src/common/luma-stiks.h b/src/common/luma-stiks.h new file mode 100644 index 0000000..f635950 --- /dev/null +++ b/src/common/luma-stiks.h @@ -0,0 +1,63 @@ +#ifndef _LUMA_STIKS_H +#define _LUMA_STIKS_H + +#include +#include + +#define LUMA_MAX_PEERS 10 + +typedef uint8_t PEER_ADDR[6]; + +typedef struct { + bool enabled; + String ssid; + esp_now_peer_info_t peer; +}LUMA_NODE; + +enum LUMA_PACKET_TYPE { LPT_RAW, LPT_REG, LPT_ANIM}; + +typedef struct{ + uint8_t data[64]; +}LUMA_RAW_PACKET; + +typedef struct{ + char ssid[24]; + uint8_t mac[6]; + uint8_t ch; +}LUMA_REGISTER_PACKET; + +typedef struct{ + LUMA_PACKET_TYPE type; + uint8_t data[64]; +}LUMA_PACKET; + + +LUMA_NODE* Luma_ScanAPMaching(String startName, float minRssi, int &count); + +bool Luma_Find_Node(const char* master_ssid, esp_now_peer_info_t &node); + +int Luma_ConvertMACStringToMACAddress(const char* mChar, uint8_t* macAddr); + +esp_err_t Luma_Node_Check(PEER_ADDR &addr); + +esp_err_t Luma_Send_Packet(PEER_ADDR &peerAddr, LUMA_PACKET &packet); + +void Luma_SendToAll_Packet(LUMA_PACKET &packet); + + +//void Luma_Send_Node_Array(LUMA_NODE *node[], int count, LUMA_PACKET &packet); + +// TODO default RSSI +LUMA_NODE* Luma_Scan_For_Stations(String nameFilter, float minRssi, int &countFound); + +esp_err_t Luma_Register_with_Master( esp_now_peer_info_t &masterNode, const char* nodeName); + +String Luma_MacToStr(uint8_t* mac); + +bool Luma_Add_Peer(esp_now_peer_info_t &node); + +int Luma_PeerCount(); + + + +#endif \ No newline at end of file diff --git a/src/common/neo_colors.h b/src/common/neo_colors.h new file mode 100644 index 0000000..41e4cbd --- /dev/null +++ b/src/common/neo_colors.h @@ -0,0 +1,138 @@ +#ifndef NEO_COLORS_H +#define NEO_COLORS_H + +#include "LEDStrip.h" + +/* +rgbpixel_t pallet_rainbow[1]; +rgbpixel_t pallet_white[1]; +rgbpixel_t pallet_USA[1]; +rgbpixel_t pallet_halloween[1]; +rgbpixel_t pallet_christmas[1]; +rgbpixel_t pallet_autumn[1]; +rgbpixel_t pallet_summer[1]; +rgbpixel_t pallet_neon[1]; +*/ + + + +#define USE_CORRECTED_COLORS 0 + +#define col_black color_pallet[0] +#define col_white color_pallet[1] +#define col_red color_pallet[2] +#define col_green color_pallet[3] +#define col_blue color_pallet[4] +#define col_orange color_pallet[5] +#define col_yellow color_pallet[6] +#define col_cyan color_pallet[7] +#define col_magenta color_pallet[8] +#define col_purple color_pallet[9] +#define col_pink color_pallet[10] +#define col_teal color_pallet[11] +#define col_lime color_pallet[12] +#define col_indigo color_pallet[13] +#define col_maroon color_pallet[14] +#define col_navy color_pallet[15] +#define col_olive color_pallet[16] +#define col_beige color_pallet[17] +#define col_brown color_pallet[18] +#define col_coral color_pallet[19] +#define col_gold color_pallet[20] +#define col_gray color_pallet[21] +#define col_ivory color_pallet[22] +#define col_khaki color_pallet[23] +#define col_lavender color_pallet[24] +#define col_peach color_pallet[25] +#define col_periwinkle color_pallet[26] +#define col_salmon color_pallet[27] +#define col_sienna color_pallet[28] +#define col_silver color_pallet[29] +#define col_tan color_pallet[30] +#define col_turquoise color_pallet[31] +#define col_violet color_pallet[32] + + +#if USE_CORRECTED_COLORS == 0 + +const rgbpixel_t color_pallet[] = { + {0 , 0 , 0 }, // col_black + {255, 255, 255}, // col_white + {255, 0 , 0 }, // col_red + {0 , 255, 0 }, // col_green + {0 , 0 , 255}, // col_blue + {255, 165, 0 }, // col_orange + {255, 255, 0 }, // col_yellow + {0 , 255, 255}, // col_cyan + {255, 0 , 255}, // col_magenta + {128, 0 , 128}, // col_purple + {255, 192, 203}, // col_pink + {0 , 128, 128}, // col_teal + {0 , 255, 0 }, // col_lime + {75 , 0 , 130}, // col_indigo + {128, 0 , 0 }, // col_maroon + {0 , 0 , 128}, // col_navy + {128, 128, 0 }, // col_olive + {245, 245, 220}, // col_beige + {165, 42 , 42 }, // col_brown + {255, 127, 80 }, // col_coral + {255, 215, 0 }, // col_gold + {128, 128, 128}, // col_gray + {255, 255, 240}, // col_ivory + {240, 230, 140}, // col_khaki + {230, 230, 250}, // col_lavender + {255, 218, 185}, // col_peach + {204, 204, 255}, // col_periwinkle + {250, 128, 114}, // col_salmon + {160, 82 , 45}, // col_sienna + {192, 192, 192}, // col_silver + {210, 180, 140}, // col_tan + {64 , 224, 208}, // col_turquoise + {238, 130, 238} // col_violet +}; + +#else + +const rgbpixel_t color_pallet[] = +{ + {0 , 0 , 0 }, // col_black + {255, 255, 255}, // col_white + {255, 0 , 0 }, // col_red + {0 , 255, 0 }, // col_green + {0 , 0 , 255}, // col_blue + + {255, 128, 0 }, // col_orange + {255, 255, 0 }, // col_yellow + {0 , 255, 255}, // col_cyan + {255, 0 , 255}, // col_magenta + {170, 0 , 255}, // col_purple + {255, 170, 255}, // col_pink + {0 , 128, 128}, // col_teal + {128, 255, 0 }, // col_lime + {85 , 0 , 255}, // col_indigo + {128, 0 , 0 }, // col_maroon + {0 , 0 , 128}, // col_navy + {128, 128, 0 }, // col_olive + {255, 230, 204}, // col_beige + {153, 51 , 0 }, // col_brown + {255, 102, 102}, // col_coral + {204, 153, 0 }, // col_gold + {128, 128, 128}, // col_gray + {255, 255, 204}, // col_ivory + {204, 204, 0 }, // col_khaki + {204, 153, 255}, // col_lavender + {255, 204, 153}, // col_peach + {153, 153, 255}, // col_periwinkle + {255, 153, 102}, // col_salmon + {153, 76 , 0 }, // col_sienna + {204, 204, 204}, // col_silver + {204, 153, 102}, // col_tan + {0 , 204, 204}, // col_turquoise + {204, 0 , 255} // col_violet +}; +#endif + + + + +#endif diff --git a/src/global.cpp b/src/global.cpp new file mode 100644 index 0000000..6e0ed45 --- /dev/null +++ b/src/global.cpp @@ -0,0 +1,369 @@ +#include "global.h" +#include +#include +#include +#include +#include "JsonConstrain.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +static const char* tag = "global"; +const char* appName; + +SYS_TASK_HANDLES TaskList; +enum COMM_MODE commMode = COMM_WIFI_AP_BLE; +CHIP_INFO chipInfo; +Version localVersion = {FIRMWARE_VERSION_MAJOR, FIRMWARE_VERSION_MINOR, FIRMWARE_VERSION_PATCH}; + +void get_chip_mac(char* macStr, size_t size) { + uint64_t chipMAC = ESP.getEfuseMac(); + uint8_t macByte[6]; + + // Extract the MAC address bytes + for (int i = 0; i < 6; ++i) { + macByte[i] = (chipMAC >> (8 * (5 - i))) & 0xFF; + } + + // Format the MAC address string + snprintf(macStr, size, "%02X:%02X:%02X:%02X:%02X:%02X", + macByte[0], macByte[1], macByte[2], + macByte[3], macByte[4], macByte[5]); +} + +void print_ram_info(void) { + char s[64]; + snprintf(s, sizeof(s), "Total RAM: %u, Free: %u, Used: %u", + ESP.getHeapSize(), ESP.getFreeHeap(), ESP.getHeapSize() - ESP.getFreeHeap()); + ESP_LOGD(tag, "RAM: %s", s); +} + +void print_chip_info(void) { + char macStr[64]; + get_chip_mac(macStr, sizeof(macStr)); + ESP_LOGD(tag, "Chip MAC: %s", macStr); + + print_ram_info(); +} + + +void printTaskInfo(TaskStatus_t taskStatus) { + uint32_t ulTotalRunTime = taskStatus.ulRunTimeCounter; + uint32_t ulStatsAsPercentage = (ulTotalRunTime * 100) / ulTotalRunTime; // Total runtime equals 100% + + ESP_LOGI(tag, "TaskInfo: %s\t%u\t%u\t%u\t%u%%", + taskStatus.pcTaskName, + taskStatus.uxCurrentPriority, + taskStatus.xCoreID, + configMINIMAL_STACK_SIZE - taskStatus.usStackHighWaterMark, + ulStatsAsPercentage); +} + +void printTaskCPUUsage(TaskHandle_t xTask) { + char buffer[512]; + vTaskGetRunTimeStats(buffer); + + // Parse the buffer to find the information for xTask + char* taskInfo = strstr(buffer, pcTaskGetName(xTask)); + if (taskInfo != NULL) { + unsigned long execTime, cpuUsage; + int parsedItems = sscanf(taskInfo, "%*s %*s %*s %*s %*s %*s %*s %lu %*s %lu", &execTime, &cpuUsage); + if (parsedItems == 2) { + ESP_LOGI(tag, "Task CPU Usage: %lu%%", cpuUsage); + } else { + ESP_LOGE(tag, "Failed to parse task statistics."); + } + } else { + ESP_LOGE(tag, "Task not found in statistics."); + } +} + +void addTaskHandleToList(SYS_TASK_HANDLES &list, TaskHandle_t handle){ + list.handle[list.count] = handle; + list.count++; +} + +void printTaskStackWatermark(TaskHandle_t xTask) { + UBaseType_t watermark = uxTaskGetStackHighWaterMark(xTask); + ESP_LOGI(tag, "Task Stack High Watermark: %u", watermark); +} + +// Stack, Heap and CPU Reporting +void report_system_stats() +{ + #if FREERTOS_DIAGNOSTICS == 1 + /* + // Get the number of tasks + UBaseType_t taskCount = uxTaskGetNumberOfTasks(); + UBaseType_t highWaterMark = uxTaskGetStackHighWaterMark(NULL); + // Allocate memory for task status array + TaskStatus_t *taskStatusArray = (TaskStatus_t*) malloc(sizeof(TaskStatus_t) * taskCount); + + // Get the system status and total CPU time + uint32_t totalCpuTime = 0; + uxTaskGetSystemState(taskStatusArray, taskCount, &totalCpuTime); + // Print task stats header + Serial.println("Task Name\tStack High Water Mark\tCPU Usage"); + + // Print task stats for each task + for (int i = 0; i < taskCount; i++) { + // Get the task status + TaskStatus_t taskStatus = taskStatusArray[i]; + + // Calculate task stack high water mark in bytes + uint32_t stackHighWaterMark = taskStatus.usStackHighWaterMark * sizeof(StackType_t); + + // Calculate task CPU usage as a percentage + uint32_t cpuUsage = (taskStatus.ulRunTimeCounter * 100) / totalCpuTime; + + // Print task stats + Serial.printf("%s\t\t%u bytes\t\t%u%%\n", taskStatus.pcTaskName, stackHighWaterMark, cpuUsage); + } + + // Free task status array memory + free(taskStatusArray); + */ + #endif +} + +String macToStr(uint8_t* mac){ + String macStr; + + for (int i = 0; i < 6; ++i) { + // Convert each byte to a hexadecimal string + // and append it to the String object + String section = String(mac[i], HEX); + if (section.length() == 1) { + section = "0" + section; + } + macStr += section; + + // Add colon between sections, but not after the last one + if (i < 5) { + macStr += ":"; + } + } + + // Convert the String to uppercase + macStr.toUpperCase(); + + return macStr; +} + +String getMacAddress(void) { + // Initialize an array to hold the MAC address + uint8_t macAddr[6]; + + // Get the MAC address for the Wi-Fi interface + WiFi.macAddress(macAddr); + + return macToStr(macAddr); +} + +bool updateJsonDocument(JsonDocument &doc, const char* filePath) { + // Check for available space + //if (LittleFS.totalBytes() - LittleFS.usedBytes() < doc.memoryUsage()) { + // ESP_LOGE(tag, "Not enough space to update file"); + // return false; + //} + + String tempFilePath = String(filePath) + ".tmp"; + String backupFilePath = String(filePath) + ".bak"; + + File tempFile = LittleFS.open(tempFilePath, "w"); + if (!tempFile) { + ESP_LOGE(tag, "Failed to create temporary file"); + return false; + } + + if (serializeJsonPretty(doc, tempFile) == 0) { + ESP_LOGE(tag, "Failed to write to temporary file"); + tempFile.close(); + LittleFS.remove(tempFilePath); + return false; + } + tempFile.close(); + + // Rename original file to backup (instead of deleting it) + if (LittleFS.exists(filePath)) { + if (!LittleFS.rename(filePath, backupFilePath)) { + ESP_LOGE(tag, "Failed to rename original file to backup"); + LittleFS.remove(tempFilePath); + return false; + } + } + + // Rename temporary file to original file name + if (!LittleFS.rename(tempFilePath, filePath)) { + ESP_LOGE(tag, "Failed to rename temporary file to original"); + LittleFS.rename(backupFilePath, filePath); // Attempt to restore from backup + return false; + } + + // Remove backup file + LittleFS.remove(backupFilePath); + + ESP_LOGD(tag, "File successfully updated"); + return true; +} + + +#include "soc/ledc_struct.h" +#define LEDC_CHANNEL_COUNT 8 // ESP32 supports up to 8 channels (0 to 7) +int findUnusedLedcChannel() { + // Iterate over all LEDC channels + for (int channel = 0; channel < LEDC_CHANNEL_COUNT; ++channel) { + // Check group 0 registers for channel usage + if (LEDC.channel_group[0].channel[channel].conf0.val == 0) { + return channel; // Found an unused channel + } + } + return -1; // Return -1 if no unused channel is found +} + + + + +#include "esp_timer.h" +#include "freertos/task.h" +void Log_CPU_Load(void) { + static const char* tag = "CPU_Load"; + + // Get run time stats + char *stats = (char*)malloc(2048); + if (!stats) { + ESP_LOGE(tag, "Failed to allocate memory for stats"); + return; + } + + vTaskGetRunTimeStats(stats); + + // Calculate percentage load for each core + uint32_t total_runtime = 0; + uint32_t idle_0 = 0; + uint32_t idle_1 = 0; + + TaskStatus_t *task_array; + UBaseType_t task_count; + + // Get task array + task_count = uxTaskGetNumberOfTasks(); + task_array = (TaskStatus_t*)malloc(sizeof(TaskStatus_t) * task_count); + + if (!task_array) { + free(stats); + ESP_LOGE(tag, "Failed to allocate memory for task array"); + return; + } + + // Get task stats + task_count = uxTaskGetSystemState(task_array, task_count, &total_runtime); + + // Find IDLE tasks + for (UBaseType_t i = 0; i < task_count; i++) { + if (strcmp(task_array[i].pcTaskName, "IDLE0") == 0) { + idle_0 = task_array[i].ulRunTimeCounter; + } + else if (strcmp(task_array[i].pcTaskName, "IDLE1") == 0) { + idle_1 = task_array[i].ulRunTimeCounter; + } + } + + // Calculate loads + float core0_load = 100.0f - ((float)idle_0 * 100.0f / (float)total_runtime); + float core1_load = 100.0f - ((float)idle_1 * 100.0f / (float)total_runtime); + + // Log results + ESP_LOGI(tag, "CPU Load - Core 0: %.1f%%, Core 1: %.1f%%", core0_load, core1_load); + + // Cleanup + free(stats); + free(task_array); +} + +/* +void print_task_watermarks(void) { + static const char* tag = "TaskWatermarks"; + UBaseType_t taskCount = uxTaskGetNumberOfTasks(); + TaskStatus_t* taskStatusArray = (TaskStatus_t*)pvPortMalloc(taskCount * sizeof(TaskStatus_t)); + + if (taskStatusArray != NULL) { + UBaseType_t actualTaskCount = uxTaskGetSystemState(taskStatusArray, taskCount, NULL); + + ESP_LOGI(tag, "Task Stack Watermarks:"); + ESP_LOGI(tag, "%-20s %-10s %-10s %-10s", "Task Name", "Priority", "Stack", "Watermark"); + + for (UBaseType_t i = 0; i < actualTaskCount; i++) { + UBaseType_t watermark = taskStatusArray[i].usStackHighWaterMark; + const char* warning = ""; + + // Warning levels + if (watermark < 50) { + warning = " !!! CRITICAL !!!"; + } else if (watermark < 100) { + warning = " ! WARNING !"; + } + + ESP_LOGI(tag, "%-20s %-10d %-10d %-10d%s", + taskStatusArray[i].pcTaskName, + (int)taskStatusArray[i].uxCurrentPriority, + (int)taskStatusArray[i].usStackHighWaterMark * sizeof(StackType_t), + (int)watermark, + warning); + } + + vPortFree(taskStatusArray); + } else { + ESP_LOGE(tag, "Failed to allocate memory for task status array"); + } +} +*/ + +/* +void print_task_watermarks(void) { + static const char* tag = "TaskWatermarks"; + char* buf = (char*)malloc(2048); + + if (buf) { + vTaskList(buf); + ESP_LOGI(tag, "\nTask Name\tStatus\tPrio\tHWM\tTask#\n%s", buf); + free(buf); + } else { + ESP_LOGE(tag, "Failed to allocate buffer for task list"); + } +} +*/ + +void print_task_watermarks(void) { + static const char* tag = "TaskWatermarks"; + + // Get current task handle + TaskHandle_t currentTask = xTaskGetCurrentTaskHandle(); + + if (currentTask != NULL) { + // Get task info + const char* name = pcTaskGetName(currentTask); + UBaseType_t priority = uxTaskPriorityGet(currentTask); + eTaskState state = eTaskGetState(currentTask); + UBaseType_t watermark = uxTaskGetStackHighWaterMark(currentTask); + + // Print header + ESP_LOGI(tag, "\nTask Statistics"); + ESP_LOGI(tag, "%-16s %-8s %-8s %-8s %s", "Name", "State", "Prio", "HWM", "Status"); + ESP_LOGI(tag, "---------------------------------------------------"); + + // Set warning level + const char* warning = ""; + if (watermark < 50) warning = " !CRITICAL!"; + else if (watermark < 100) warning = " !WARNING!"; + + // Print task info + ESP_LOGI(tag, "%-16s %-8d %-8d %-8d%s", + name, + state, + priority, + watermark, + warning); + } else { + ESP_LOGE(tag, "Failed to get current task handle"); + } +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..2bdcad7 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,595 @@ +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +// #include "freertos/FreeRTOSConfig.h" +#include "sdkconfig.h" +#include + +#include +#include +#include "soc/ledc_reg.h" +#include "soc/ledc_struct.h" +#include "esp_adc_cal.h" +#include "esp_log.h" + +#include +#include +#include +#include "system.h" +#include "JsonConstrain.h" +#include "common/fileSystem.h" +#include "my_board.h" +#include "global.h" +#include "my_wifi.h" +#include "my_buzzer.h" +#include "my_tsensor.h" +#include "my_buttons.h" +#include "PWM_Output.h" +#include "Ramp_Lights.h" +#include "BleServer.h" +#include "ATALights.h" +#include "OnEveryN.h" +#include "BLE_SP110E.h" + +#define FREERTOs_DIAGNOSTICS 0 +#define OLED_ENABLED 0 +#define WIFI_ENABLED 0 +#define STRIPS_ENABLED 1 +#define LUMASTIK_ENABLED 0 +#define PRINT_SYSTEM_STATUS 0 + +#if OLED_ENABLED +#include "my_oled.h" +#endif + +#if WIFI_ENABLED +#include "my_wifi.h" +#endif + +#if STRIPS_ENABLED +#include "led_strip.h" +#endif + +#if LUMASTIK_ENABLED +#include "luma_master.h" +#endif + +#if LUMASTIK_ENABLED +#define LumaCountReset (20000 / 25) +int LumaCountdown = LumaCountReset; +LUMA_PACKET lumaPacket; +#endif + +static const char *tag = "main"; +SYS_SETTINGS sys_settings; +PWM_Output *pwmOutputs[4]; +RAMP_LIGHT *rampLight1; +RAMP_LIGHT *rampLight2; + +void Init_ADC(void); +float readBoardInputVoltage(void); +void setupLogLevels(esp_log_level_t logLevel); +void Get_Board_and_Booth_File_Paths(const char *, String &, String &); +void Load_Booth_Settings(SYS_SETTINGS &sys, const String &boothPath); +void Init_PWM_Outputs(int8_t (&pin)[4], PWM_OUT_SETTINGS (&pwmSettings)[4]); +void Init_Ramp_Lights(RAMP_LIGHT_SETTINGS (&settings)[2], OneButton *(&btn)[3], PWM_Output *(&pwm)[4]); + +void checkLEDCChannels() +{ + for (int i = 0; i < 8; i++) + { + if (LEDC.channel_group[0].channel[i].conf0.val != 0 || LEDC.channel_group[1].channel[i].conf0.val != 0) + { + ESP_LOGD(tag, "Channel %d is configured\n", i); + } + } +} + +#define Button1Pin 8 +void setup() +{ + Serial.begin(115200); + while (!Serial) + ; + + // Initialize I2C Port for TSensor, ... + Wire.begin(I2C_SDA1_Pin, I2C_SCL1_Pin); + + sys_settings.ledStripSettings[0] = &ledSettings[0]; + sys_settings.ledStripSettings[1] = &ledSettings[1]; + + // Set Logging Levels + // esp_log_level_t logLevel = ESP_LOG_INFO; + // pinMode(Button1Pin, INPUT); // Button1 + // if(!digitalRead(Button1Pin)){ + // logLevel = ESP_LOG_VERBOSE; + //} + // setupLogLevels(logLevel); + setupLogLevels(ESP_LOG_INFO); + + // chip id, mac, + print_chip_info(); + + // Init LittleFS + Init_File_System(); + + String board_file_path, booth_file_path; + Get_Board_and_Booth_File_Paths("/system/system.json", board_file_path, booth_file_path); + + // Load Board Pins + Load_Board_Pins(sys_settings.boardPins, board_file_path); + + // Load Booth Settings + Load_Booth_Settings(sys_settings, booth_file_path); + + // Load Wifi Settings + Wifi_Load_Settings("/system/wifi.json"); + + // config stat, relays, buttons + Init_Board_Basic(sys_settings.boardPins); + + // Load tunes.json and initialize + Init_Buzzer(sys_settings.boardPins.buzzer, "/system/tunes.json"); + + // Initialize PWM Outputs + Init_PWM_Outputs(sys_settings.boardPins.relay, sys_settings.pwmOutSettings); + + // activate button clicks + Init_ButtonEvents(sys_settings.boardPins.btn); + + // Initialize Ramp Lights + Init_Ramp_Lights(sys_settings.rampLightSettings, boardButtons, pwmOutputs); + + // Initialize Temperature Sensor + Init_TSensor(72); + + Init_ADC(); + + float val = readBoardInputVoltage(); + ESP_LOGI(tag, "Input Volage = %f", val); + + // Initialize BLE + if (digitalRead(sys_settings.boardPins.btn[0]) == LOW) + { + ESP_LOGW(tag, "Enabling BLE and Update Service"); + Init_BleServer(true, true); + ESP_LOGW(tag, "Enabling Wifi AP and Client"); + Wifi_Init(); + } + else + { + Init_BleServer(true, false); // Dont start the Upgrade service + } + + // If lightstick mode is enabled, start ble lightstick client task + if (sys_settings.mode == BOOTH_MODE_STIK) + { + Init_BLE_LightStick_Client(); + } + +#if OLED_ENABLED + // Init OLED + Init_OLED(sys_settings.oledSettings.width, sys_settings.oledSettings.height, sys_settings.boardPins.oled_mosi, sys_settings.boardPins.oled_sck, sys_settings.boardPins.oled_dc, sys_settings.boardPins.oled_rst, sys_settings.boardPins.oled_cs); +#endif + +#if STRIPS_ENABLED + Init_Lights_Task(); +#endif + + Buzzer_Play_Tune(TUNE_BOOT, true, true); + + // TODO... Test if this is still necessary need to configure pin 0 for some reason + // pinMode(0, INPUT); // button0/boot pin + +#if LUMASTIK_ENABLED + Init_Luma_Master(); +#endif + + vTaskDelay(100); + Lights_Control_Task_Resume(); +} + +void loop() +{ + + // Button Scanning + ON_EVERY_N_MILLISECONDS(10) + { + if (boardButtons[0] != NULL) + { + boardButtons[0]->tick(); + } + if (boardButtons[1] != NULL) + { + boardButtons[1]->tick(); + } + if (boardButtons[2] != NULL) + { + boardButtons[2]->tick(); + } + } + + // Temperature Monitor + ON_EVERY_N_MILLISECONDS(5000) + { + static float boardTemperature; + + // Read temperature if the sensor is enabled + if (sys_settings.tSensorSettings.enabled) + { + boardTemperature = tSensor->readTemperatureF(); + // ESP_LOGD(tag, "Board T: %F", boardTemperature); + } + + // Fan Control + if (sys_settings.tSensorSettings.enabled) + { + UpdateFanControl(boardTemperature, pwmOutputs[sys_settings.tSensorSettings.pwmIndex]); + } + } + + // Update Tune Playing + if (anyrtttl::nonblocking::isPlaying()) + { + anyrtttl::nonblocking::play(); + } + +// Animation TestMode Timeout +#if LEDS_ENABLED + if (animStatus.EventTestCountdown) + { + if (--animStatus.EventTestCountdown == 0) + { + ESP_LOGD(tag, "Test Timeout trigger"); + PostLastNormalEvent(); + if (Strip1_Task_Handle) + { + xTaskNotifyGive(Strip1_Task_Handle); + } // trigger exit of animation loop + } + }; +#endif + +// Reboot requested +#if WIFI_ENABLED + if (RebootSystem) + { + if (--RebootSystem == 0) + { + for (int i = 0; i < 3; i++) + { +#if BUZZER_ENABLED + Buzzer_Play_Tune(TUNE_BEEP, false); // blocking +#endif + vTaskDelay(200); + } + ESP_LOGW(tag, "Restarting..."); + vTaskDelay(200); + ESP.restart(); + } + } +#endif + + // Toggle Status LED L2 + ON_EVERY_N_MILLISECONDS(500) + { + if (sys_settings.boardPins.stat[1] >= 0) + { + static bool ledState = false; + // digitalWrite(sys_settings.boardPins.stat[1], ledState = !ledState); + setStatusPin2(ledState = !ledState) + } + } + +#if FREERTOs_DIAGNOSTICS + ON_EVERY_N_MILLISECONDS(60000) + { + print_task_watermarks(); + } +#endif +} + +void setupLogLevels(esp_log_level_t logLevel) +{ + // Options: ESP_LOG_ERROR,ESP_LOG_WARN,ESP_LOG_INFO,ESP_LOG_DEBUG,ESP_LOG_VERBOSE + esp_log_level_set("*", logLevel); + esp_log_level_set("fs", logLevel); + esp_log_level_set("main", logLevel); + esp_log_level_set("board", logLevel); + esp_log_level_set("ramp", logLevel); + esp_log_level_set("button", logLevel); + esp_log_level_set("buzzer", logLevel); + esp_log_level_set("global", logLevel); + esp_log_level_set("jsCon", logLevel); + esp_log_level_set("ble", logLevel); + esp_log_level_set("pwmout", logLevel); + esp_log_level_set("wifi", logLevel); + esp_log_level_set("stiks", logLevel); + esp_log_level_set("oled", logLevel); + esp_log_level_set("trx433", logLevel); + esp_log_level_set("tsensor", logLevel); +} + +// TODO Restore original setOutput code.. +#define RELAY_RES 10 +void Init_PWM_Outputs(int8_t (&pin)[4], PWM_OUT_SETTINGS (&pwmSettings)[4]) +{ + for (int i = 0; i < 4; i++) + { + int chIndex = findUnusedLedcChannel(); + if (chIndex < 0) + { + ESP_LOGE(tag, "No available LEDC channel for PWM Output%d", i); + continue; + } + pwmOutputs[i] = new PWM_Output(pin[i], chIndex, RELAY_RES, pwmSettings[i].freq, pwmSettings[i].max, false); + pwmOutputs[i]->setOutput(pwmSettings[i].def); + // pwmOutputs[i]->setOutput(5.0); + ESP_LOGD(tag, "PWM Output%d: Pin=%d, Freq=%d, ch=%d", i, pin[i], pwmSettings[i].freq, chIndex); + } +} + +void Init_Ramp_Lights(RAMP_LIGHT_SETTINGS (&settings)[2], OneButton *(&btn)[3], PWM_Output *(&pwm)[4]) +{ + if (settings[0].enabled) + { + rampLight1 = new RAMP_LIGHT(btn[settings[0].btnIndex], pwm[settings[0].pwmOutIndex], 5.0, 100.0, 1.5); + ESP_LOGD(tag, "RampLight%d: btn=%d, pwmIndex=%d", 1, settings[0].btnIndex, settings[0].pwmOutIndex); + } + + if (settings[1].enabled) + { + rampLight2 = new RAMP_LIGHT(btn[settings[1].btnIndex], pwm[settings[1].pwmOutIndex], 5.0, 100.0, 1.5); + ESP_LOGD(tag, "RampLight%d: btn=%d, pwmIndex=%d", 2, settings[1].btnIndex, settings[1].pwmOutIndex); + } +} + +// Get the files that should be used to setup the system +void Get_Board_and_Booth_File_Paths(const char *sysPath, String &boardPath, String &boothPath) +{ + File file = LittleFS.open(sysPath); + + if (!file) + { + ESP_LOGE(tag, "Error opening %s...", sysPath); + return; + } + + JsonDocument doc; + DeserializationError error = deserializeJson(doc, file); + file.close(); + + if (error) + { + ESP_LOGE(tag, "%s deserialize error!..", sysPath); + return; + } + + // get hardware version string + boardPath = jsonConstrainString(tag, doc.as(), "boardfile", "/cfg/boards/board15.json"); + boothPath = jsonConstrainString(tag, doc.as(), "configfile", "/cfg/booths/custom.json"); +} + +void Load_Booth_Settings(SYS_SETTINGS &sys, const String &boothPath) +{ + File file = LittleFS.open(boothPath); + if (!file) + { + ESP_LOGE(tag, "Error opening %s...", boothPath.c_str()); + return; + } + + JsonDocument doc; + DeserializationError error = deserializeJson(doc, file); + file.close(); + + if (error) + { + ESP_LOGE(tag, "%s deserialize error!..", boothPath.c_str()); + return; + } + + // ********** Mode *********** + String modeStr = jsonConstrainString(tag, doc.as(), "mode", "booth"); + if (modeStr == "roamer") + { + sys_settings.mode = BOOTH_MODE_ROAMER; + } + else if (modeStr == "stik") + { + sys_settings.mode = BOOTH_MODE_STIK; + } + else + { + sys_settings.mode = BOOTH_MODE_NONE; + } + + // ********** PWM Out *********** + JsonArray pwmJsonArray = doc["pwmout"]; + if (!pwmJsonArray.isNull()) + { + int pwmIndex = 0; + for (JsonObject obj : pwmJsonArray) + { + if (pwmIndex >= 4) + break; + sys_settings.pwmOutSettings[pwmIndex].enabled = jsonConstrainBool(tag, obj, "en", true); + sys_settings.pwmOutSettings[pwmIndex].freq = jsonConstrain(tag, obj, "freq", 100, 5000, 250); + sys_settings.pwmOutSettings[pwmIndex].min = jsonConstrain(tag, obj, "min", 0.0, 95.0, 0.0); + sys_settings.pwmOutSettings[pwmIndex].max = jsonConstrain(tag, obj, "max", 5.0, 100.0, 100.0); + sys_settings.pwmOutSettings[pwmIndex].def = jsonConstrain(tag, obj, "default", 0.0, 100.0, 0.0); + sys_settings.pwmOutSettings[pwmIndex].deltaRate = 1.0; + // sys_settings.pwmOutSettings[pwmIndex]. = jsonConstrainBool(tag, obj, "vision", true); + pwmIndex++; + } + ESP_LOGI(tag, "Loaded PWmOutput settings..."); + } + else + { + ESP_LOGE(tag, "Error!, %s key: pwmout not found..", boothPath); + } + + // ********** Ramp Lights *********** + JsonArray rampJsonArray = doc["ramp-lights"]; + if (!rampJsonArray.isNull()) + { + int rampIndex = 0; + for (JsonObject obj : rampJsonArray) + { + if (rampIndex >= 2) + break; + sys_settings.rampLightSettings[rampIndex].enabled = jsonConstrainBool(tag, obj, "en", true); + sys_settings.rampLightSettings[rampIndex].vision = jsonConstrainBool(tag, obj, "vision", true); + sys_settings.rampLightSettings[rampIndex].pwmOutIndex = jsonConstrain(tag, obj, "relay-index", 0, 1, 0); + sys_settings.rampLightSettings[rampIndex].btnIndex = jsonConstrain(tag, obj, "button-index", 0, 1, 0); + sys_settings.rampLightSettings[rampIndex].min = jsonConstrain(tag, obj, "min", 0.0, 100.0, 0.0); + sys_settings.rampLightSettings[rampIndex].max = jsonConstrain(tag, obj, "max", 5.0, 100.0, 100.0); + sys_settings.rampLightSettings[rampIndex].step = jsonConstrain(tag, obj, "step", 0.01, 100.0, 1.5); + rampIndex++; + } + ESP_LOGI(tag, "Loaded Ramp Lights settings..."); + } + else + { + ESP_LOGE(tag, "Error!, %s key: ramp-lights not found..", boothPath); + } + + // ********** Fan *********** + JsonObject sensorJson = doc["t-sensor"]; + if (!sensorJson.isNull()) + { + sys_settings.tSensorSettings.enabled = jsonConstrainBool(tag, sensorJson, "en", true); + sys_settings.tSensorSettings.pwmIndex = jsonConstrain(tag, sensorJson, "relay", 0, 3, 3); + sys_settings.tSensorSettings.setpoint1 = jsonConstrain(tag, sensorJson, "sp1", 0.0, 100.0, 80.0); + sys_settings.tSensorSettings.setpoint2 = jsonConstrain(tag, sensorJson, "sp2", 0.0, 100.0, 85.0); + sys_settings.tSensorSettings.fanPower1 = jsonConstrain(tag, sensorJson, "fan-pwr1", 0.0, 100.0, 50.0); + sys_settings.tSensorSettings.fanPower2 = jsonConstrain(tag, sensorJson, "fan-pwr2", 0.0, 100.0, 50.0); + sys_settings.tSensorSettings.hyst = jsonConstrain(tag, sensorJson, "hyst", 1.0, 10.0, 1.0); + sys_settings.tSensorSettings.intervalMs = jsonConstrain(tag, sensorJson, "interval", 1000, 30000, 5000); + ESP_LOGI(tag, "Loaded TSensor settings..."); + ESP_LOGD(tag, " SP1: %F, SP2 %F, Hyst: %F", sys_settings.tSensorSettings.setpoint1, sys_settings.tSensorSettings.setpoint2, sys_settings.tSensorSettings.hyst); + } + else + { + ESP_LOGE(tag, "Error!, %s key: t-sensor not found..", boothPath); + } + + // ********** RGB Strips *********** + JsonArray stripsJsonArray = doc["strips"]; + if (!stripsJsonArray.isNull()) + { + int stripIndex = 0; + for (JsonObject obj : stripsJsonArray) + { + if (stripIndex >= 2) + break; + sys_settings.ledStripSettings[stripIndex]->enabled = jsonConstrainBool(tag, obj, "en", true); + sys_settings.ledStripSettings[stripIndex]->size = jsonConstrain(tag, obj, "size", 1, 250, 25); + sys_settings.ledStripSettings[stripIndex]->chip = jsonConstrainString(tag, obj, "chip", "WS2812B"); + sys_settings.ledStripSettings[stripIndex]->rgbOrder = jsonConstrainString(tag, obj, "rgb-order", "WS2812B"); + sys_settings.ledStripSettings[stripIndex]->shift = jsonConstrain(tag, obj, "shift", -250, 250, 0); + sys_settings.ledStripSettings[stripIndex]->offset = jsonConstrain(tag, obj, "offset", -250, 250, 0); + sys_settings.ledStripSettings[stripIndex]->bright = jsonConstrain(tag, obj, "bright", 5, 255, 200); + sys_settings.ledStripSettings[stripIndex]->powerDiv = 0; + sys_settings.ledStripSettings[stripIndex]->i2sCh = 0; + sys_settings.ledStripSettings[stripIndex]->core = jsonConstrain(tag, obj, "core", 0, 1, 0); + stripIndex++; + } + + sys_settings.ledStripSettings[0]->pin = sys_settings.boardPins.rgb1; + sys_settings.ledStripSettings[1]->pin = sys_settings.boardPins.rgb2; + ESP_LOGI(tag, "Loaded LED Strip settings..."); + } + else + { + ESP_LOGE(tag, "Error!, %s key: strips not found.."); + } + + // ********** BLE *********** + JsonObject bleJson = doc["ble"]; + if (!bleJson.isNull()) + { + sys_settings.bleSettings.enabled = jsonConstrainBool(tag, bleJson, "en", true); + sys_settings.bleSettings.name = jsonConstrainString(tag, bleJson, "name", "ATA_LIGHTS"); + + ESP_LOGI(tag, "Loaded BLE settings..."); + } + else + { + ESP_LOGE(tag, "Error!, %s key: ble not found..", boothPath); + } + + // ********** RF Remote*********** + /* + JsonObject rfJson = doc["wifi-ap"]; + if(!rfJson.isNull()){ + //sys_settings.rampLights[0].enabled = jsonConstrainBool(tag, wifiApJson, "en", true); + ESP_LOGI(tag, "Loaded RF Remote settings..."); + }else{ + ESP_LOGE(tag, "Error!, %s key: wifi-ap not found..", boothPath); + } + */ +} + +void Init_ADC(void) +{ + // Configure ADC + analogReadResolution(12); // 12-bit ADC + analogSetAttenuation(ADC_11db); + if (sys_settings.boardPins.adc1 >= 0) + { + analogSetPinAttenuation(sys_settings.boardPins.adc1, ADC_11db); + } + + // Enable ADC calibration + esp_adc_cal_characteristics_t adc_chars; + esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 0, &adc_chars); + + // Check calibration success + if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP) == ESP_OK) + { + ESP_LOGI(tag, "ADC calibration: Using Two Point values from eFuse"); + } + else if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_VREF) == ESP_OK) + { + ESP_LOGI(tag, "ADC calibration: Using reference voltage from eFuse"); + } + else + { + ESP_LOGW(tag, "ADC calibration: Using default reference voltage"); + } +} + +float readBoardInputVoltage(void) +{ + const int SAMPLES = 64; + uint32_t reading = 0; + + if (sys_settings.boardPins.adc1 < 0) + { + ESP_LOGE(tag, "ADC Pin not valid"); + return 0.0; + } + + // Multiple readings for averaging + for (int i = 0; i < SAMPLES; i++) + { + reading += analogRead(sys_settings.boardPins.adc1); + delayMicroseconds(50); // Small delay between samples + } + reading /= SAMPLES; + + // Convert raw ADC to voltage using calibration + uint32_t voltage_mv; + esp_adc_cal_characteristics_t adc_chars; + esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 1100, &adc_chars); + voltage_mv = esp_adc_cal_raw_to_voltage(reading, &adc_chars); + + // Scale to 12V range + float voltage = (voltage_mv / 1000.0f) * (10470.0f / 470.0f); + + return voltage; +} \ No newline at end of file diff --git a/src/my_board.cpp b/src/my_board.cpp new file mode 100644 index 0000000..f0bbcdb --- /dev/null +++ b/src/my_board.cpp @@ -0,0 +1,92 @@ +#include "my_board.h" + +#include +#include +#include + +#include "global.h" +#include "JsonConstrain.h" +#include "system.h" + +static const char* tag = "board"; + +BOARD_PINS* thisBoardPins; + +void Load_Board_Pins(BOARD_PINS& boardPins, String& path){ + thisBoardPins = &boardPins; + File file = LittleFS.open(path); + + if (!file) { + ESP_LOGE(tag, "Error opening %s...", path.c_str()); + return; + } + + JsonDocument doc; + DeserializationError error = deserializeJson(doc, file); + file.close(); + + if (error) { + ESP_LOGE(tag, "%s deserialize error!..", path.c_str()); + return; + } + + JsonObject boardJson = doc.as(); + boardPins.rgb1 = jsonConstrain(tag, boardJson, "rgb1", -1, 48, -1); + boardPins.rgb2 = jsonConstrain(tag, boardJson, "rgb2", -1, 48, -1); + boardPins.btn[0] = jsonConstrain(tag, boardJson, "btn1", -1, 48, -1); + boardPins.btn[1] = jsonConstrain(tag, boardJson, "btn2", -1, 48, -1); + boardPins.btn[2] = jsonConstrain(tag, boardJson, "btn3", -1, 48, -1); + boardPins.buzzer = jsonConstrain(tag, boardJson, "buzzer", -1, 48, -1); + boardPins.touch[0] = jsonConstrain(tag, boardJson, "touch1", -1, 48, -1); + boardPins.touch[1] = jsonConstrain(tag, boardJson, "touch2", -1, 48, -1); + boardPins.touch[2] = jsonConstrain(tag, boardJson, "touch3", -1, 48, -1); + boardPins.touch[3] = jsonConstrain(tag, boardJson, "touch4", -1, 48, -1); + boardPins.touch[4] = jsonConstrain(tag, boardJson, "touch5", -1, 48, -1); + boardPins.shield = jsonConstrain(tag, boardJson, "shield", -1, 48, -1); + boardPins.relay[0] = jsonConstrain(tag, boardJson, "relay1", -1, 48, -1); + boardPins.relay[1] = jsonConstrain(tag, boardJson, "relay2", -1, 48, -1); + boardPins.relay[2] = jsonConstrain(tag, boardJson, "relay3", -1, 48, -1); + boardPins.relay[3] = jsonConstrain(tag, boardJson, "relay4", -1, 48, -1); + boardPins.stat[0] = jsonConstrain(tag, boardJson, "stat1", -1, 48, -1); + boardPins.stat[1] = jsonConstrain(tag, boardJson, "stat2", -1, 48, -1); + boardPins.adc1 = jsonConstrain(tag, boardJson, "adc1", -1, 48, -1); + boardPins.oled_dc = jsonConstrain(tag, boardJson, "oled_dc", -1, 48, -1); + boardPins.oled_rst = jsonConstrain(tag, boardJson, "oled_rst", -1, 48, -1); + boardPins.oled_mosi = jsonConstrain(tag, boardJson, "oled_mosi", -1, 48, -1); + boardPins.oled_sck = jsonConstrain(tag, boardJson, "oled_sck", -1, 48, -1); + boardPins.oled_cs = jsonConstrain(tag, boardJson, "oled_cs", -1, 48, -1); + boardPins.ext[0] = jsonConstrain(tag, boardJson, "ext1", -1, 48, -1); + boardPins.ext[1] = jsonConstrain(tag, boardJson, "ext2", -1, 48, -1); + boardPins.rf433tx = jsonConstrain(tag, boardJson, "rf433tx", -1, 48, -1); + boardPins.rf433rx = jsonConstrain(tag, boardJson, "rf433rx", -1, 48, -1); + + // TODO Add hardware version to log + ESP_LOGI(tag, "loaded Pins from %s", path.c_str()); + +} + + +void Init_Board_Basic(BOARD_PINS& boardPins) +{ + + if(boardPins.stat[0] >= 0){ pinMode(boardPins.stat[0], OUTPUT); } + if(boardPins.stat[1] >= 0){ pinMode(boardPins.stat[1], OUTPUT); } + + if(boardPins.btn[0] >= 0){ pinMode(boardPins.btn[0], INPUT_PULLUP); } + if(boardPins.btn[1] >= 0){ pinMode(boardPins.btn[1], INPUT_PULLUP); } + if(boardPins.btn[2] >= 0){ pinMode(boardPins.btn[2], INPUT); } + + if(boardPins.rgb1 >= 0){ pinMode(boardPins.rgb1, OUTPUT); } + if(boardPins.rgb2 >= 0){ pinMode(boardPins.rgb2, OUTPUT); } + + if(boardPins.relay[0] >= 0){ pinMode(boardPins.relay[0], OUTPUT); } + if(boardPins.relay[1] >= 0){ pinMode(boardPins.relay[1], OUTPUT); } + if(boardPins.relay[2] >= 0){ pinMode(boardPins.relay[2], OUTPUT); } + if(boardPins.relay[3] >= 0){ pinMode(boardPins.relay[3], OUTPUT); } + + ESP_LOGI(tag, "Board pins initialized..."); +} + + + + diff --git a/src/my_buttons.cpp b/src/my_buttons.cpp new file mode 100644 index 0000000..5de9ae9 --- /dev/null +++ b/src/my_buttons.cpp @@ -0,0 +1,137 @@ +#include "my_buttons.h" +#include "global.h" +#include "BLE_UpdateService.h" + +static const char* tag = "button"; +OneButton *boardButtons[3]; + +void Init_ButtonEvents(int8_t (&pin)[3]){ + + + if(pin[0] >= 0) { + boardButtons[0] = new OneButton(pin[0], true, true); + ESP_LOGD(tag, "Button1 Events, pin=%d", pin[0]); + } + if(pin[1] >= 0) { + boardButtons[1] = new OneButton(pin[1], true, true); + ESP_LOGD(tag, "Button2 Events, pin=%d", pin[1]); + } + if(pin[2] >= 0) { + boardButtons[2] = new OneButton(pin[2], true, false); + ESP_LOGD(tag, "Button3 Events, pin=%d", pin[2]); + } + + /* + if(boardButtons[0] != NULL){ + //boardButtons[0]->setDebounceTicks(DEBOUCE_TIME); // just below the update period to guarantee 1 sample delay + boardButtons[0]->attachClick(btn1_click); + boardButtons[0]->attachDoubleClick(btn1_doubleClick); + boardButtons[0]->attachLongPressStart(btn1_LongPressStart); + boardButtons[0]->attachLongPressStop(btn1_LongPressStop); + boardButtons[0]->attachDuringLongPress(btn1_DuringLongPress); + } + else { + ESP_LOGD(tag, "Button1 Not Initialized"); + } + + if(boardButtons[1] != NULL){ + //boardButtons[1]->setDebounceTicks(DEBOUCE_TIME); + boardButtons[1]->attachClick(btn2_click); + boardButtons[1]->attachDoubleClick(btn2_doubleClick); + boardButtons[1]->attachLongPressStart(btn2_LongPressStart); + boardButtons[1]->attachLongPressStop(btn2_LongPressStop); + boardButtons[1]->attachDuringLongPress(btn2_DuringLongPress); + } + else { + ESP_LOGD(tag, "Button2 Not Initialized"); + } +*/ + + if(boardButtons[2] != NULL){ + //boardButtons[2]->setDebounceTicks(DEBOUCE_TIME); + boardButtons[2]->attachClick(btn3_click); + boardButtons[2]->attachDoubleClick(btn3_doubleClick); + boardButtons[2]->attachLongPressStart(btn3_LongPressStart); + boardButtons[2]->attachLongPressStop(btn3_LongPressStop); + boardButtons[2]->attachDuringLongPress(btn3_DuringLongPress); + ESP_LOGD(tag, "Button3 Events Initialized"); + } + else { + ESP_LOGD(tag, "Button3 Not Initialized"); + } + +} + +void btn1_click() { + //IncrementEventIndex(); + //Pulse_LED_Status(150); + ESP_LOGD(tag, "btn1 1x"); +} + +void btn1_doubleClick() { + ESP_LOGD(tag, "btn1 2x"); +} + +void btn1_LongPressStart(){ + ESP_LOGD(tag, "btn1 long press start"); +} + +void btn1_LongPressStop(){ + ESP_LOGD(tag, "btn1 long press stop"); +} + +void btn1_DuringLongPress(){ + ESP_LOGD(tag, "btn1 long press"); +} + + +/**************************/ + +void btn2_click() { + //Pulse_LED_Status(150); + //Buzzer_Beep(150); + // send packet + ESP_LOGD(tag, "btn2 1x"); +} + +void btn2_doubleClick() { + ESP_LOGD(tag, "btn2 2x"); +} + +void btn2_LongPressStart(){ + ESP_LOGD(tag, "btn2 long press start"); +} + +void btn2_LongPressStop(){ + ESP_LOGD(tag, "btn2 long press stop"); +} + +void btn2_DuringLongPress(){ + ESP_LOGD(tag, "btn2 long press"); +} + + +/**************************/ + +void btn3_click() { + //Pulse_LED_Status(150); + ESP_LOGD(tag, "btn3 1x"); +} + +void btn3_doubleClick() { + bleUpgrade_send_message("Hello....3"); + ESP_LOGD(tag, "btn3 2x"); +} + +void btn3_LongPressStart(){ + ESP_LOGD(tag, "btn3 long press start"); +} + +void btn3_LongPressStop(){ + ESP_LOGD(tag, "btn3 long press stop"); +} + +void btn3_DuringLongPress(){ + ESP_LOGD(tag, "btn3 long press"); +} + diff --git a/src/my_buzzer.cpp b/src/my_buzzer.cpp new file mode 100644 index 0000000..5452cbc --- /dev/null +++ b/src/my_buzzer.cpp @@ -0,0 +1,109 @@ +#include "my_buzzer.h" +#include +#include + +#include +#include +#include + +#include "JsonConstrain.h" + +const char* DEFAULT_MELODY = "Ack:d=16,o=5,b=200:c,e,g"; + +// serial debugging enabled +//#define ANY_RTTTL_INFO + +static const char* tag = "buzzer"; + +BUZZ_TUNE buzzTune[TUNE_MAX_COUNT]; +int8_t buzzPin; + + +void Init_Buzzer(int8_t pin, const char* configFile) +{ + buzzPin = pin; + if(buzzPin >= 0){ + pinMode(buzzPin, OUTPUT); + } + + Buzzer_Load_Tunes(configFile); // Load Tunes + ESP_LOGD(tag, "Buzzer initialized.."); +} + +void Buzzer_Play_Tune(TUNE_TYPE tune, bool async, bool hasPriority) +{ + static int prev_tune = -1; + if(buzzPin < 0) return; + + if (hasPriority || !anyrtttl::nonblocking::isPlaying()) { + if (anyrtttl::nonblocking::isPlaying()) { + anyrtttl::nonblocking::stop(); + } + + // Load nonblocking if different from previous + if (prev_tune != tune && async) { + anyrtttl::nonblocking::begin(buzzPin, buzzTune[tune].melody.c_str()); + } + + ESP_LOGD(tag, "Playing tune: %d, melody: %s", tune, buzzTune[tune].melody.c_str()); + + for (int c = 0; c < buzzTune[tune].cycles; c++) { + if (async) { + anyrtttl::nonblocking::play(); + } else { + anyrtttl::blocking::play(buzzPin, buzzTune[tune].melody.c_str()); + } + } + + prev_tune = tune; + } else { + ESP_LOGD(tag, "buzzer busy"); + } +} + +// TODO Buzzer Beep finish +void Buzzer_Beep(int mSecs, int freq) +{ + /* + ledcAttachPin(buzzPin, buzzerCh); + ledcSetup(buzzerCh, 2000, 8); + ledcWrite(buzzerCh, 125); + vTaskDelay(mSecs); + ledcWrite(buzzerCh, 0); + */ +} + +// TODO Reduce tunes to load () +void Buzzer_Load_Tunes(const char* tunesPath){ + File file = LittleFS.open(tunesPath); + + if (!file) { + ESP_LOGE(tag, "Error opening %s...", tunesPath); + return; + } + + JsonDocument doc; + DeserializationError error = deserializeJson(doc, file); + file.close(); + + if(error){ + ESP_LOGE(tag, "%s deserialize error!..", tunesPath); + return; + } + + JsonArray tuneJsonArray = doc["tunes"]; + if(!tuneJsonArray.isNull()){ + int tuneIndex = 0; + for(JsonObject obj : tuneJsonArray){ + if(tuneIndex >= TUNE_MAX_COUNT) break; + buzzTune[tuneIndex].cycles = jsonConstrain(tag, obj, "cycles", 1, 100, 1); + buzzTune[tuneIndex].pause = jsonConstrain(tag, obj, "pause", 0, 100, 0); + buzzTune[tuneIndex].melody = jsonConstrainString(tag, obj, "tune", DEFAULT_MELODY); + ESP_LOGD(tag, "tune %d : %s", tuneIndex, buzzTune[tuneIndex].melody.c_str()); + tuneIndex++; + } + ESP_LOGI(tag, "Loaded tunes..."); + }else{ + ESP_LOGE(tag, "Error!, %s key: tunes not found..", tunesPath); + } +} \ No newline at end of file diff --git a/src/my_oled.cpp b/src/my_oled.cpp new file mode 100644 index 0000000..1b8de4d --- /dev/null +++ b/src/my_oled.cpp @@ -0,0 +1,125 @@ +#include "my_oled.h" +#include +#include + +static const char* tag = "oled"; +Adafruit_SSD1306 *oled; + +void Init_OLED(uint8_t width, uint8_t height, uint8_t mosiPin, uint8_t sckPin, uint8_t dcPin, uint8_t rstPin, uint8_t csPin) +{ + oled = new Adafruit_SSD1306(width, height, mosiPin, sckPin, dcPin, rstPin, csPin); + + if(!oled->begin(SSD1306_SWITCHCAPVCC)) { + ESP_LOGE(tag, "SSD1306 allocation failed"); + for(;;); // Don't proceed, loop forever + } + + oled_ShowInfo(); +} + +void oled_ShowInfo(void){ + //if(sysProps.oledEnabled) { + oled->display(); + vTaskDelay(2000); // Pause for 2 seconds + + // Clear the buffer + oled->clearDisplay(); + + // Draw a single pixel in white + oled->drawPixel(10, 10, SSD1306_WHITE); + + // Show the display buffer on the screen. You MUST call display() after + // drawing commands to make them visible on screen! + oled->display(); + vTaskDelay(2000); + // display.display() is NOT necessary after every single drawing command, + // unless that's what you want...rather, you can batch up a bunch of + // drawing operations and then update the screen all at once by calling + // display.display(). These examples demonstrate both approaches... + + //testdrawline(*oled); // Draw many lines + + //testdrawrect(*oled); // Draw rectangles (outlines) + + testdrawline(oled); // Draw many lines + + testdrawrect(oled); // Draw rectangles (outlines) + //} +} + +void testdrawline(Adafruit_SSD1306* display) { + //if(sysProps.oledEnabled) { + int16_t i; + + display->clearDisplay(); // Clear display buffer + + for(i=0; iwidth(); i+=4) { + display->drawLine(0, 0, i, display->height()-1, SSD1306_WHITE); + display->display(); // Update screen with each newly-drawn line + vTaskDelay(1); + } + for(i=0; iheight(); i+=4) { + display->drawLine(0, 0, display->width()-1, i, SSD1306_WHITE); + display->display(); + vTaskDelay(1); + } + vTaskDelay(250); + + display->clearDisplay(); + + for(i=0; iwidth(); i+=4) { + display->drawLine(0, display->height()-1, i, 0, SSD1306_WHITE); + display->display(); + vTaskDelay(1); + } + for(i=display->height()-1; i>=0; i-=4) { + display->drawLine(0, display->height()-1, display->width()-1, i, SSD1306_WHITE); + display->display(); + vTaskDelay(1); + } + vTaskDelay(250); + + display->clearDisplay(); + + for(i=display->width()-1; i>=0; i-=4) { + display->drawLine(display->width()-1, display->height()-1, i, 0, SSD1306_WHITE); + display->display(); + vTaskDelay(1); + } + for(i=display->height()-1; i>=0; i-=4) { + display->drawLine(display->width()-1, display->height()-1, 0, i, SSD1306_WHITE); + display->display(); + vTaskDelay(1); + } + vTaskDelay(250); + + display->clearDisplay(); + + for(i=0; iheight(); i+=4) { + display->drawLine(display->width()-1, 0, 0, i, SSD1306_WHITE); + display->display(); + vTaskDelay(1); + } + for(i=0; iwidth(); i+=4) { + display->drawLine(display->width()-1, 0, i, display->height()-1, SSD1306_WHITE); + display->display(); + vTaskDelay(1); + } + + vTaskDelay(2000); // Pause for 2 seconds + //} +} + +void testdrawrect(Adafruit_SSD1306* display) { + //if(sysProps.oledEnabled) { + display->clearDisplay(); + + for(int16_t i=0; iheight()/2; i+=2) { + display->drawRect(i, i, display->width()-2*i, display->height()-2*i, SSD1306_WHITE); + display->display(); // Update screen with each newly-drawn rectangle + vTaskDelay(1); + } + + vTaskDelay(2000); + //} +} \ No newline at end of file diff --git a/src/my_tsensor.cpp b/src/my_tsensor.cpp new file mode 100644 index 0000000..bc2b4fa --- /dev/null +++ b/src/my_tsensor.cpp @@ -0,0 +1,48 @@ +#include "my_tsensor.h" +#include + +static const char* tag = "tsensor"; + +T_SENSOR tSensorSettings; +TI_TMP102_Compatible *tSensor; + +/******************* Temperature Control ********************/ + void Init_TSensor(uint8_t addr) { + //tSensor = new TI_TMP102_Compatible(72); + tSensor = new TI_TMP102_Compatible(addr); + } + + + void UpdateFanControl(float temperature, PWM_Output* pwmOut) { + static uint8_t FanState = 0; + float currentDuty = pwmOut->currDuty; + float newDuty = currentDuty; + + // Fan State Logic + if ((FanState == 2) && (temperature < (tSensorSettings.Setpoint2 - tSensorSettings.hyst))) { + newDuty = tSensorSettings.fanPower1; + FanState = 1; + //ESP_LOGD(tag, "Dropping down to FanPower1"); + } + else if ((FanState == 1) && (temperature < (tSensorSettings.Setpoint1 - tSensorSettings.hyst))) { + newDuty = 0; + FanState = 0; + //ESP_LOGD(tag, "Dropping down to FanPower0"); + } + else if ((FanState <= 1) && (temperature > tSensorSettings.Setpoint1)) { + newDuty = tSensorSettings.fanPower1; + if (temperature > tSensorSettings.Setpoint2) { + newDuty = tSensorSettings.fanPower2; + FanState = 2; + //ESP_LOGD(tag, "Raising up to FanPower2"); + } //else { + //ESP_LOGD(tag, "Raising up to FanPower1"); + //} + } + + // Apply new duty cycle if changed + if (currentDuty != newDuty) { + pwmOut->setOutput(newDuty); + ESP_LOGD(tag, "Board T: %.2f, Fan -> %.2f", temperature, newDuty); + } +} \ No newline at end of file diff --git a/src/my_wifi.cpp b/src/my_wifi.cpp new file mode 100644 index 0000000..f3cde55 --- /dev/null +++ b/src/my_wifi.cpp @@ -0,0 +1,1396 @@ +#include "my_wifi.h" +#include +#include +#include +#include +#include +#include +#include "esp_log.h" +#include +#include +#include +#include "common/fileSystem.h" +#include "global.h" +#include "my_buzzer.h" +#include +#include +#include "jsonconstrain.h" +#include "AppUpgrade.h" +#include "my_board.h" + +static const char *tag = "WIFI"; + +volatile bool WifiClientConnected = false; +volatile bool InternetAvailable; +AsyncWebServer webServer(80); +AsyncEventSource eventUpgradeProgress("/upgrade-progress"); +// DNSServer *dnsServer; +// #define DNS_PORT 53 + +String client_ssid; +String client_pass; +String ap_ssid; +String ap_pass; + +String mDnsName; +String HostName; + +IPAddress local_IP(192, 168, 10, 1); +IPAddress gateway(192, 168, 10, 1); +IPAddress subnet(255, 255, 255, 0); + +// for file manager page +String filesDropdownOptions((char *)0); +String dirDropdownOptions((char *)0); +String savePath((char *)0); // needed for storing file when editing a file +String savePathInput((char *)0); +const char *http_username = "admin"; +const char *http_password = "admin"; +const char *param_delete_path = "delete-path"; +const char *param_edit_path = "edit-path"; +const char *param_dir_pad = "dir-path"; +const char *param_edit_textarea = "edit-textarea"; +const char *param_save_path = "save-path"; +String allowedExtensionsForEdit = "txt, h, htm, html, css, cpp, js, json, ini, cfg"; + +volatile bool scanInProgress = false; + +static String networkList = ""; +int networkCount = 0; + +volatile int scanStatus = 0; // 0=none, 1=scanning, 2=complete, -1=error +String scanResults = ""; // Store scan results globally + +TaskHandle_t Wifi_Task_Handle; +static const uint32_t CONNECT_TIMEOUT_MS = 20000; +static const uint32_t ATTEMPT_DELAY_MS = 2000; +static const uint8_t MAX_ATTEMPTS = 10; +static SemaphoreHandle_t wifiMutex = nullptr; +volatile bool wifi_task_running = false; + +void Wifi_Init() +{ + // Initialize LittleFS + if (!LittleFS.begin(true)) + { + ESP_LOGE(tag, "LittleFS mount failed"); + return; + } + + Wifi_Load_Settings("/system/wifi.json"); + + // Set Wi-Fi task to run on Core 1 + esp_wifi_set_ps(WIFI_PS_NONE); // Disable power save mode for better responsiveness + + // Set WiFi to AP+STA mode + WiFi.mode(WIFI_MODE_APSTA); + + // Configure and start AP + WiFi.softAPConfig(local_IP, gateway, subnet); + if (!WiFi.softAP(ap_ssid, ap_pass)) + { + ESP_LOGE(tag, "AP start failed"); + return; + } + + // Add CORS headers + DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); + DefaultHeaders::Instance().addHeader("Access-Control-Allow-Methods", "GET, POST, PUT"); + DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "Content-Type"); + + Setup_WebServer_Handlers(webServer); + + WiFi.onEvent(onWiFiEvent); + WiFi.setHostname(mDnsName.c_str()); + webServer.begin(); + ESP_LOGD(tag, "AP started with IP: %s", WiFi.softAPIP().toString().c_str()); + + StartWifiConnectTask(client_ssid, client_pass); + + // Wifi_Scan_for_Networks(); +} + +bool StartWifiConnectTask(String ssid = "", String pass = "") +{ + if (ssid.isEmpty() || pass.length() < 8) + { + ESP_LOGE(tag, "Invalid SSID or password"); + return false; + } + + if (!wifi_task_running) + { + client_ssid = ssid; + client_pass = pass; + if (Wifi_Task_Handle == NULL) + { + ESP_LOGD(tag, "Creating WiFi task"); + WifiClientConnected = false; + xTaskCreatePinnedToCore(Wifi_ConnectTask, "Wifi_Task", 1024 * 4, NULL, 1, &Wifi_Task_Handle, 0); + } + else + { + ESP_LOGD(tag, "WiFi task already running"); + } + + return true; + } + else + { + ESP_LOGE(tag, "Task already running"); + } + + return false; +} + +void Wifi_ConnectTask(void *parameter) +{ + static const char *tag = "Wifi_Task"; + wifi_task_running = true; + + if (!WifiClientConnected || client_ssid != WiFi.SSID()) + { + WifiClientConnected = false; + ESP_LOGD(tag, "Connecting to: %s", client_ssid.c_str()); + + // Disconnect and connect to new network + WiFi.disconnect(true); + WiFi.setAutoReconnect(false); + WiFi.begin(client_ssid.c_str(), client_pass.c_str()); + + // Wait for connection + uint8_t attempts = 0; + while (WiFi.status() != WL_CONNECTED && attempts < MAX_ATTEMPTS) + { + switch (WiFi.status()) + { + case WL_NO_SSID_AVAIL: + ESP_LOGW(tag, "SSID not found: %s", client_ssid.c_str()); + break; + case WL_CONNECT_FAILED: + ESP_LOGW(tag, "Connection failed - check password"); + break; + default: + ESP_LOGD(tag, "Connecting... (%d/%d)", attempts + 1, MAX_ATTEMPTS); + } + vTaskDelay(pdMS_TO_TICKS(ATTEMPT_DELAY_MS)); + attempts++; + } + + // Check if connected + if (WiFi.status() == WL_CONNECTED) + { + ESP_LOGI(tag, "Connected to %s", client_ssid.c_str()); + WifiClientConnected = true; + WiFi.setAutoReconnect(true); + + if (!Wifi_Save_Credentials("/system/wifi.json")) + { + ESP_LOGW(tag, "Failed to save credentials"); + } + + Wifi_Check_Internet(); + } + else + { + ESP_LOGE(tag, "Failed to connect after %d attempts", MAX_ATTEMPTS); + } + } + + ESP_LOGI(tag, "Wifi Task ended"); + + Wifi_Task_Handle = NULL; + wifi_task_running = false; + vTaskDelete(NULL); +} + +void Wifi_Check_Internet() +{ + // Check for internet connection + const char *host = "8.8.8.8"; // Google DNS server + if (Ping.ping(host, 1)) + { + InternetAvailable = true; + ESP_LOGI(tag, "Internet connection verified"); + } + else + { + InternetAvailable = false; + ESP_LOGW(tag, "No internet connection"); + } +} + +bool Wifi_Save_Credentials(String path) +{ + // Load existing JSON + JsonDocument doc; + File readFile = LittleFS.open(path, "r"); + if (readFile) + { + DeserializationError error = deserializeJson(doc, readFile); + readFile.close(); + if (error) + { + ESP_LOGE(tag, "Failed to parse existing JSON"); + return false; + } + } + + // Update or create wifi-client section + JsonObject wifiClient = doc["wifi-client"].to(); + wifiClient["ssid"] = client_ssid; + wifiClient["pass"] = client_pass; + + // Save updated JSON + File writeFile = LittleFS.open(path, "w"); + if (!writeFile) + { + ESP_LOGE(tag, "Error opening %s for writing", path.c_str()); + return false; + } + + // Serialize JSON with pretty formatting + if (serializeJsonPretty(doc, writeFile) == 0) + { + ESP_LOGE(tag, "Failed to write JSON to file"); + writeFile.close(); + return false; + } + + writeFile.close(); + return true; +} + +void Wifi_Load_Settings(String path) +{ + // Load WiFi settings + File file = LittleFS.open(path, "r"); + if (!file) + { + ESP_LOGE(tag, "Error opening %s", path.c_str()); + return; + } + + JsonDocument doc; + DeserializationError error = deserializeJson(doc, file); + file.close(); + + if (error) + { + ESP_LOGE(tag, "Failed to deserialize %s", path.c_str()); + return; + } + + JsonObject wifiJson = doc.as(); + if (wifiJson.isNull()) + { + ESP_LOGE(tag, "%s is empty", path.c_str()); + return; + } + + // Load AP settings + JsonObject apJson = wifiJson["wifi-ap"]; + if (!apJson.isNull()) + { + ap_ssid = jsonConstrainString(tag, apJson, "ssid", "ATA-AP"); + ap_pass = jsonConstrainString(tag, apJson, "pass", "12345678"); + local_IP.fromString(jsonConstrainString(tag, apJson, "ip", "192.168.10.1")); + gateway.fromString(jsonConstrainString(tag, apJson, "gateway", "192.168.10.1")); + subnet.fromString(jsonConstrainString(tag, apJson, "subnet", "255.255.255.0")); + } + + // Load Client settings + JsonObject clientJson = wifiJson["wifi-client"]; + if (!apJson.isNull()) + { + client_ssid = jsonConstrainString(tag, clientJson, "ssid", "none"); + client_pass = jsonConstrainString(tag, clientJson, "pass", "12345678"); + } +} + +void Wifi_Scan_for_Networks() +{ + // Start a scan for available networks + WiFi.scanNetworks(false, false); + while (WiFi.scanComplete() == WIFI_SCAN_RUNNING) + { + vTaskDelay(100); // Wait for scan to complete + } + + networkCount = WiFi.scanComplete(); + if (networkCount >= 0) + { + JsonDocument doc; + JsonArray networks = doc["networks"].to(); + + for (int i = 0; i < networkCount; i++) + { + auto network = networks.add(); + network["ssid"] = WiFi.SSID(i); + network["rssi"] = WiFi.RSSI(i); + network["encryption"] = WiFi.encryptionType(i) != WIFI_AUTH_OPEN; + } + + String jsonString; + serializeJson(doc, jsonString); + networkList = jsonString; + WiFi.scanDelete(); + } + else + { + ESP_LOGE(tag, "WiFi scan failed"); + } +} + +void Setup_WebServer_Handlers(AsyncWebServer &server) +{ + + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) + { request->send(LittleFS, "/www/index.html", "text/html"); }); + server.on("/home", HTTP_GET, [](AsyncWebServerRequest *request) + { sendHtmlFile("/www/home.html", request, HomeHtmlProcessor); }); + server.on("/setup", HTTP_GET, [](AsyncWebServerRequest *request) + { + if (!request->authenticate(http_username, http_password)) + { + return request->requestAuthentication(); + } + // sendHtmlFile("/www/setup.html", request, htmlProcessor); + }); + server.on("/about", HTTP_GET, [](AsyncWebServerRequest *request) + { + sendHtmlFile("/www/about.html", request, fileManagerHtmlProcessor); + // request->send(LittleFS, "/www/about.html", "text/html"); + }); + + // Wifi related handlers + server.on("/wifi/connect", HTTP_POST, [](AsyncWebServerRequest *request) + { + // Input validation + if (!request->hasParam("ssid", false, false) || !request->hasParam("pass", false, false)) { + ESP_LOGE(tag, "Missing required parameters"); + request->send(400, "application/json", "{\"error\":\"Missing ssid or password\"}"); + return; + } + + // Get and store credentials + String ssid = request->getParam("ssid", false, false)->value(); + String pass = request->getParam("pass", false, false)->value(); + + // Validate credentials + if (client_ssid.length() < 1 || client_pass.length() < 8) { + ESP_LOGE(tag, "Invalid credentials"); + request->send(400, "application/json", "{\"error\":\"Invalid credentials\"}"); + return; + } + + // Start connection + StartWifiConnectTask(ssid, pass); + + ESP_LOGD(tag, "Starting connection to %s", client_ssid.c_str()); + request->send(200, "application/json", "{\"status\":\"connecting\"}"); }); + server.on("/wifi/status", HTTP_GET, [](AsyncWebServerRequest *request) + { + String jsonStr = "{\"status\":\"" + String(WifiClientConnected ? "Connected" : "Disconnected") + + "\",\"ip\":\"" + WiFi.localIP().toString() + "\"}"; + request->send(200, "application/json", jsonStr); }); + server.on("/wifi/scans", HTTP_GET, [](AsyncWebServerRequest *request) + { + if(networkCount <= 0) { + request->send(400, "application/json", "{\"error\":\"No scan results\"}"); + return; + } + + request->send(200, "application/json", networkList); }); + server.on("/wifi", HTTP_GET, [](AsyncWebServerRequest *request) + { + if(WiFi.getMode() == WIFI_MODE_APSTA){ + // TODO Disable navigation bar + } + //sendHtmlFile("/www/wifi.html", request, htmlProcessor); + request->send(LittleFS, "/www/wifi.html", "text/html"); }); + + // File Manager related handlers + server.on("/files/upload", HTTP_POST, [](AsyncWebServerRequest *request) + { request->send(200); }, handleFilesUpload_OnBody); + + server.on("/files/download", HTTP_GET, [](AsyncWebServerRequest *request) + { + if (!request->hasParam("file")) { + ESP_LOGE(tag, "Missing file parameter"); + request->send(400, "text/plain", "Missing file parameter"); + return; + } + + try { + String filename = uriDecode(request->getParam("file")->value()); + ESP_LOGD(tag, "Download request for: %s", filename.c_str()); + request->send(LittleFS, filename, "application/octet-stream"); + } + catch (const std::exception& e) { + ESP_LOGE(tag, "Download failed: %s", e.what()); + request->send(404, "text/plain", "File not found!"); + } }); + server.on("/files/delete", HTTP_GET, [](AsyncWebServerRequest *request) + { + static const char* tag = "DeleteHandler"; + + // Authentication check + if (!request->authenticate(http_username, http_password)) { + ESP_LOGW(tag, "Authentication failed for delete request"); + return request->requestAuthentication(); + } + + // Parameter validation + if (!request->hasParam(param_delete_path)) { + ESP_LOGE(tag, "Missing delete path parameter"); + request->send(400, "text/plain", "Missing file path"); + return; + } + + // Get and validate filename + String filename = uriDecode(request->getParam(param_delete_path)->value()); + if (filename == "choose") { + request->redirect("/files"); + return; + } + + // Ensure path starts with / + if (!filename.startsWith("/")) { + filename = "/" + filename; + } + + try { + if (LittleFS.remove(filename.c_str())) { + ESP_LOGI(tag, "Successfully deleted file: %s", filename.c_str()); + } else { + ESP_LOGE(tag, "Failed to delete file: %s", filename.c_str()); + request->send(500, "text/plain", "Delete failed"); + return; + } + } catch (const std::exception& e) { + ESP_LOGE(tag, "Exception during delete: %s", e.what()); + request->send(500, "text/plain", "Delete failed"); + return; + } + + request->redirect("/files"); }); + server.on("/files/edit", HTTP_GET, [](AsyncWebServerRequest *request) + { + static const char* tag = "EditHandler"; + + // Authentication check + if (!request->authenticate(http_username, http_password)) { + ESP_LOGW(tag, "Authentication failed"); + return request->requestAuthentication(); + } + + // Parameter validation + if (!request->hasParam(param_edit_path)) { + ESP_LOGE(tag, "Missing edit path parameter"); + request->send(400, "text/plain", "Missing file path"); + return; + } + + // Get and decode filename + String fileName = uriDecode(request->getParam(param_edit_path)->value()); + ESP_LOGD(tag, "Edit request for file: %s", fileName.c_str()); + + // Set save path + savePath = (fileName == "new") ? "/new.txt" : fileName; + + // Validate path + if (!savePath.startsWith("/")) { + savePath = "/" + savePath; + } + + ESP_LOGD(tag, "Save path set to: %s", savePath.c_str()); + + try { + sendHtmlFile("/www/edit.html", request, fileManagerHtmlProcessor); + } catch (const std::exception& e) { + ESP_LOGE(tag, "Failed to send edit page: %s", e.what()); + request->send(500, "text/plain", "Internal server error"); + } }); + server.on("/files/save", HTTP_GET, [](AsyncWebServerRequest *request) + { + if(!request->authenticate(http_username, http_password)){ + return request->requestAuthentication(); + } + String inputMessage((char*)0); + if (request->hasParam(param_edit_textarea)) { + inputMessage = request->getParam(param_edit_textarea)->value(); + } + if (request->hasParam(param_save_path)) { + savePath = uriDecode(request->getParam(param_save_path)->value()); + } + writeFile(LittleFS, savePath.c_str(), inputMessage.c_str()); + + request->redirect("/files"); }); + server.on("/files", HTTP_GET, [](AsyncWebServerRequest *request) + { + if(!request->authenticate(http_username, http_password)){ + return request->requestAuthentication(); + } + sendHtmlFile("/www/files.html", request, fileManagerHtmlProcessor); }); + + // Lights related handlers + server.on("/lights/settings", HTTP_GET, [](AsyncWebServerRequest *request) + { request->send(200); }); + server.on("/lights/settings", HTTP_POST, [](AsyncWebServerRequest *request) + { request->send(200); }); + server.on("/lights/animation", HTTP_POST, [](AsyncWebServerRequest *request) + { request->send(200); }); + server.on("/lights/setpixel", HTTP_POST, [](AsyncWebServerRequest *request) + { request->send(200); }); + + // System and LED related handlers + server.on("/system/summary", HTTP_GET, [](AsyncWebServerRequest *request) + { + String response = "{\"status\":\"" + String(WifiClientConnected ? "Connected" : "Disconnected") + "\"}"; + request->send(200, "application/json", response); }); + server.on("/leds/settings", HTTP_GET, [](AsyncWebServerRequest *request) + { + /* + //CreateSysSummmaryPacket(doc); + String summary; + serializeJson(jsDoc, summary); + request->send(200, "application/json", summary); + */ + String response = "{\"status\":\"" + String(WifiClientConnected ? "Connected" : "Disconnected") + "\"}"; + request->send(200, "application/json", response); }); + server.on("/leds/settings", HTTP_POST, [](AsyncWebServerRequest *request) + { + String response = "{\"status\":\"" + String(WifiClientConnected ? "Connected" : "Disconnected") + "\"}"; + request->send(200, "application/json", response); }); + + // LightStik related handlers + server.on("/lightstik/settings", HTTP_GET, [](AsyncWebServerRequest *request) + { + String response = "{\"status\":\"" + String(WifiClientConnected ? "Connected" : "Disconnected") + "\"}"; + request->send(200, "application/json", response); }); + server.on("/lightstik/settings", HTTP_POST, [](AsyncWebServerRequest *request) + { + String response = "{\"status\":\"" + String(WifiClientConnected ? "Connected" : "Disconnected") + "\"}"; + request->send(200, "application/json", response); }); + server.on("/lightstik/register", HTTP_POST, [](AsyncWebServerRequest *request) + { + String response = "{\"status\":\"" + String(WifiClientConnected ? "Connected" : "Disconnected") + "\"}"; + request->send(200, "application/json", response); }); + + // Firmware Update Handlers + server.on("/upgrade/check", HTTP_GET, [](AsyncWebServerRequest *request) + { + //String newVersion; + loadUpdateJson(); + //bool avai = checkManifest(FIRMWARE_VERSION, newVersion); + checkManifest(otaVersion); + bool avail = otaVersion > localVersion; + + JsonDocument doc; + doc["currentVersion"] = localVersion.toString(); + doc["latestVersion"] = otaVersion.toString(); + doc["updateAvailable"] = avail; + + String response; + serializeJson(doc, response); + request->send(200, "application/json", response); }); + // Start update process + server.on("/upgrade/start", HTTP_POST, [](AsyncWebServerRequest *request) + { + startFirmwareUpdateTask(&eventUpgradeProgress); + request->send(200); }); + eventUpgradeProgress.onConnect([](AsyncEventSourceClient *client) + { + if (client->lastId()) + { + Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId()); + } + // send event with message "hello!", id current millis + // and set reconnect delay to 1 second + // client->send("hello!", NULL, millis(), 10000); + }); + server.on("/upgrade", HTTP_GET, [](AsyncWebServerRequest *request) + { + if(!request->authenticate(http_username, http_password)){ + return request->requestAuthentication(); + } + request->send(LittleFS, "/www/upgrade.html", "text/html"); }); + server.addHandler(&eventUpgradeProgress); + + // Basic Connection status check + server.on("/api/status", HTTP_GET, [](AsyncWebServerRequest *request) + { request->send(200, "application/json", "{\"status\":\"connected\"}"); }); + // Server requested files that aren't template processed + server.on("/*", HTTP_GET, [](AsyncWebServerRequest *request) { // handle file uploads + // Validate request + if (!request) + { + ESP_LOGE(tag, "Invalid request"); + return; + } + + // Get and validate file path + String filePath = request->url(); + if (filePath.isEmpty()) + { + ESP_LOGE(tag, "Empty file path"); + request->send(400, "text/plain", "Invalid file path"); + return; + } + + // Ensure path starts with '/' + if (!filePath.startsWith("/")) + { + filePath = "/" + filePath; + } + + try + { + // Get content type once + const char *contentType = getFileType(getFileExtension(filePath.c_str())); + if (!contentType) + { + ESP_LOGW(tag, "Unknown file type: %s", filePath.c_str()); + contentType = "application/octet-stream"; + } + + ESP_LOGD(tag, "Sending file: %s (%s)", filePath.c_str(), contentType); + request->send(LittleFS, filePath, contentType); + } + catch (const std::runtime_error &e) + { + ESP_LOGE(tag, "FileSystem error: %s for path: %s", e.what(), filePath.c_str()); + request->send(404, "text/plain", "File not found"); + } + catch (const std::exception &e) + { + ESP_LOGE(tag, "Error: %s for path: %s", e.what(), filePath.c_str()); + request->send(500, "text/plain", "Internal server error"); + } + }); + // 404 handler + server.onNotFound([](AsyncWebServerRequest *request) + { + ESP_LOGE(tag, "404: %s", request->url().c_str()); + request->send(404, "text/plain", "Not found"); }); +} + +void handleFilesUpload_OnBody(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) +{ + static const size_t MAX_UPLOAD_SIZE = 1024 * 1024; // 1MB limit + + if (!index) + { + // Initial upload chunk + if (!request->hasParam("dir-path", true, false)) + { + ESP_LOGE(tag, "Missing dir-path parameter"); + request->send(400, "text/plain", "Missing dir-path"); + return; + } + + AsyncWebParameter *p = request->getParam("dir-path", true, false); + String path = p->value() + "/" + filename; + ESP_LOGD(tag, "Starting upload: %s", path.c_str()); + + // Validate path + if (!path.startsWith("/")) + { + path = "/" + path; + } + + request->_tempFile = LittleFS.open(path, "w"); + if (!request->_tempFile) + { + ESP_LOGE(tag, "Failed to create file: %s", path.c_str()); + request->send(500, "text/plain", "Failed to create file"); + return; + } + } + + // Write chunk + if (len && request->_tempFile) + { + if (index + len > MAX_UPLOAD_SIZE) + { + request->_tempFile.close(); + LittleFS.remove(request->_tempFile.name()); + ESP_LOGE(tag, "Upload too large"); + request->send(413, "text/plain", "File too large"); + return; + } + + if (!request->_tempFile.write(data, len)) + { + ESP_LOGE(tag, "Write failed"); + request->_tempFile.close(); + request->send(500, "text/plain", "Write failed"); + return; + } + } + + if (final) + { + request->_tempFile.close(); + ESP_LOGD(tag, "Upload complete: %s, %u bytes", filename.c_str(), index + len); + request->redirect("/files"); + } +} + +// Send html file with template processing {{VAR}} +void sendHtmlFile(const char *filePath, AsyncWebServerRequest *request, String (*callback)(const String &)) +{ + try + { + const char *htmlFile = readFile(LittleFS, filePath); + if (!htmlFile) + { + ESP_LOGE(tag, "Failed to read file: %s", filePath); + request->send(404, "text/plain", "File not found"); + return; + } + + String processedData = varReplace(htmlFile, callback); + delete[] htmlFile; // Clean up allocated memory + + ESP_LOGD(tag, "Sent file: %s", filePath); + request->send(200, "text/html", processedData); + } + catch (const std::exception &e) + { + ESP_LOGE(tag, "Error processing file %s: %s", filePath, e.what()); + request->send(500, "text/plain", "Server error"); + } +} + +const char *getFileExtension(const char *filename) +{ + // Input validation + if (!filename) + { + ESP_LOGW(tag, "Null filename provided"); + return ""; + } + + // Find last dot + const char *lastDot = strrchr(filename, '.'); + if (!lastDot || lastDot == filename || *(lastDot + 1) == '\0') + { + ESP_LOGD(tag, "No valid extension found in: %s", filename); + return ""; + } + + ESP_LOGD(tag, "Found extension: %s", lastDot + 1); + return lastDot + 1; +} + +const char *getFileType(const char *ext) +{ + if (!ext) + return "application/octet-stream"; + + ESP_LOGD(tag, "Getting file type for extension: %s", ext); + + if (strcmp(ext, "png") == 0) + return "image/png"; + if (strcmp(ext, "jpg") == 0 || strcmp(ext, "jpeg") == 0) + return "image/jpeg"; + if (strcmp(ext, "gif") == 0) + return "image/gif"; + if (strcmp(ext, "ico") == 0) + return "image/x-icon"; + if (strcmp(ext, "txt") == 0) + return "text/plain"; + if (strcmp(ext, "css") == 0) + return "text/css"; + if (strcmp(ext, "htm") == 0 || strcmp(ext, "html") == 0) + return "text/html"; + if (strcmp(ext, "js") == 0) + return "text/javascript"; + if (strcmp(ext, "json") == 0) + return "application/json"; + + ESP_LOGW(tag, "Unknown file extension: %s", ext); + return "application/octet-stream"; +} + +// Finds segments between {{VAR}} and calls a callback function to replace VAR with new content +String varReplace(const String &input, String (*callback)(const String &)) +{ + static const char *tag = "varReplace"; + + // Validate inputs + if (input.isEmpty() || !callback) + { + ESP_LOGW(tag, "Empty input or null callback"); + return input; + } + + // Pre-allocate result string with estimated size + String result; + result.reserve(input.length() * 1.2); // Add 20% for potential replacements + + const int maxSegmentLength = 32; + int startPos = 0; + + // Process all segments + while (true) + { + // Find next variable + int start = input.indexOf("{{", startPos); + if (start == -1) + { + break; + } + + // Add text before variable + result += input.substring(startPos, start); + + // Find end of variable + int end = input.indexOf("}}", start + 2); + if (end == -1) + { + ESP_LOGW(tag, "Unmatched {{ at position %d", start); + result += input.substring(start); + break; + } + + // Extract and validate segment + String segment = input.substring(start + 2, end); + if (segment.length() <= maxSegmentLength) + { + try + { + String replacement = callback(segment); + result += replacement; + } + catch (const std::exception &e) + { + ESP_LOGE(tag, "Callback error: %s", e.what()); + result += input.substring(start, end + 2); + } + } + else + { + ESP_LOGW(tag, "Segment too long: %d chars", segment.length()); + result += input.substring(start, end + 2); + } + + startPos = end + 2; + } + + // Add remaining text + if (startPos < input.length()) + { + result += input.substring(startPos); + } + + return result; +} + +const char *convertFileSize(const size_t bytes) +{ + static char fileSizeBuffer[16]; // Pre-allocated buffer for the file size + + if (bytes < 1024) + { + snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%d B", bytes); + } + else if (bytes < 1024 * 1024) + { + snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%.2f kB", bytes / 1024.0); + } + else if (bytes < 1024 * 1024 * 1024) + { + snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%.2f MB", bytes / (1024.0 * 1024.0)); + } + else + { + snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%.2f GB", bytes / (1024.0 * 1024.0 * 1024.0)); + } + + return fileSizeBuffer; +} + +void onWiFiEvent(WiFiEvent_t event) +{ + // Serial.printf("[WiFi-event] event: %d\n", event); + switch (event) + { + case ARDUINO_EVENT_WIFI_READY: + ESP_LOGD(tag, "WiFi interface ready"); + break; + case ARDUINO_EVENT_WIFI_SCAN_DONE: + ESP_LOGD(tag, "Completed scan for access points"); + break; + case ARDUINO_EVENT_WIFI_STA_START: + ESP_LOGD(tag, "WiFi client started"); + break; + case ARDUINO_EVENT_WIFI_STA_STOP: + ESP_LOGD(tag, "WiFi clients stopped"); + break; + case ARDUINO_EVENT_WIFI_STA_CONNECTED: + ESP_LOGD(tag, "Connected to AP"); + break; + case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: + WifiClientConnected = false; + ESP_LOGD(tag, "WiFi Disconnected"); + setStatusPin1(false); + Buzzer_Play_Tune(TUNE_DISCONNECTED); + break; + case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE: + ESP_LOGD(tag, "Authentication mode of access point has changed"); + break; + case ARDUINO_EVENT_WIFI_STA_GOT_IP: + WifiClientConnected = true; + ESP_LOGD(tag, "My IP: %s", WiFi.localIP().toString()); + // Wifi_Start_MDNS(); + setStatusPin1(true); + Buzzer_Play_Tune(TUNE_CONNECTED); + break; + case ARDUINO_EVENT_WIFI_STA_LOST_IP: + WifiClientConnected = false; + ESP_LOGD(tag, "Lost IP address and IP address is reset to 0"); + break; + case ARDUINO_EVENT_WPS_ER_SUCCESS: + ESP_LOGD(tag, "WiFi Protected Setup (WPS): succeeded in enrollee mode"); + break; + case ARDUINO_EVENT_WPS_ER_FAILED: + ESP_LOGD(tag, "WiFi Protected Setup (WPS): failed in enrollee mode"); + break; + case ARDUINO_EVENT_WPS_ER_TIMEOUT: + ESP_LOGD(tag, "WiFi Protected Setup (WPS): timeout in enrollee mode"); + break; + case ARDUINO_EVENT_WPS_ER_PIN: + ESP_LOGD(tag, "WiFi Protected Setup (WPS): pin code in enrollee mode"); + break; + case ARDUINO_EVENT_WIFI_AP_START: + ESP_LOGD(tag, "WiFi access point started"); + break; + case ARDUINO_EVENT_WIFI_AP_STOP: + ESP_LOGD(tag, "WiFi access point stopped"); + break; + case ARDUINO_EVENT_WIFI_AP_STACONNECTED: + ESP_LOGD(tag, "Client connected"); + break; + case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED: + ESP_LOGD(tag, "SoftAP Client Disconnected"); + Buzzer_Play_Tune(TUNE_DISCONNECTED); + break; + case ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED: + ESP_LOGD(tag, "SoftAP Client Connected"); + Buzzer_Play_Tune(TUNE_CONNECTED); + break; + case ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED: + ESP_LOGD(tag, "Received probe request"); + break; + case ARDUINO_EVENT_WIFI_AP_GOT_IP6: + ESP_LOGD(tag, "AP IPv6 is preferred"); + break; + case ARDUINO_EVENT_WIFI_STA_GOT_IP6: + ESP_LOGD(tag, "STA IPv6 is preferred"); + break; + case ARDUINO_EVENT_ETH_GOT_IP6: + ESP_LOGD(tag, "Ethernet IPv6 is preferred"); + break; + case ARDUINO_EVENT_ETH_START: + ESP_LOGD(tag, "Ethernet started"); + break; + case ARDUINO_EVENT_ETH_STOP: + ESP_LOGD(tag, "Ethernet stopped"); + break; + case ARDUINO_EVENT_ETH_CONNECTED: + ESP_LOGD(tag, "Ethernet connected"); + break; + case ARDUINO_EVENT_ETH_DISCONNECTED: + ESP_LOGD(tag, "Ethernet disconnected"); + break; + case ARDUINO_EVENT_ETH_GOT_IP: + ESP_LOGD(tag, "Obtained IP address"); + break; + default: + break; + } +} + +void Wifi_Start_MDNS(void) +{ + ESP_LOGV(tag, "Initializing MDNS: %s", mDnsName.c_str()); + if (!MDNS.begin(mDnsName.c_str())) + { + ESP_LOGE(tag, "Error setting up MDNS responder!"); + } + else + { + ESP_LOGV(tag, "You can access device via http://%s.local", mDnsName); + } +} + +bool writeFile(fs::FS &fs, const char *path, const char *message) +{ + // Validate inputs + if (!path || !message) + { + ESP_LOGE(tag, "Invalid parameters: path=%p message=%p", path, message); + return false; + } + + // Open file with error checking + File file = fs.open(path, "w"); + if (!file) + { + ESP_LOGE(tag, "Failed to open file: %s", path); + return false; + } + + // Write with error handling + try + { + size_t bytesWritten = file.print(message); + if (bytesWritten == 0) + { + ESP_LOGE(tag, "Failed to write to file: %s", path); + file.close(); + return false; + } + + // Ensure all data is written + file.flush(); + file.close(); + ESP_LOGD(tag, "Successfully wrote %u bytes to %s", bytesWritten, path); + return true; + } + catch (const std::exception &e) + { + ESP_LOGE(tag, "Exception while writing file %s: %s", path, e.what()); + file.close(); + return false; + } +} + +char *readFile(fs::FS &fs, const char *path) +{ + static const char *tag = "readFile"; + static const size_t MAX_FILE_SIZE = 1024 * 1024; // 1MB limit + + // Validate input + if (!path) + { + ESP_LOGE(tag, "Invalid path parameter"); + return nullptr; + } + + // Open file + File file = fs.open(path, "r"); + if (!file || file.isDirectory()) + { + ESP_LOGE(tag, "Failed to open file: %s", path); + return nullptr; + } + + // Check file size + size_t fileSize = file.size(); + if (fileSize == 0 || fileSize > MAX_FILE_SIZE) + { + ESP_LOGE(tag, "Invalid file size: %u bytes", fileSize); + file.close(); + return nullptr; + } + + // Allocate memory + char *fileContent = new (std::nothrow) char[fileSize + 1]; + if (!fileContent) + { + ESP_LOGE(tag, "Memory allocation failed for size: %u", fileSize + 1); + file.close(); + return nullptr; + } + + // Read file + size_t bytesRead = file.readBytes(fileContent, fileSize); + file.close(); + + if (bytesRead != fileSize) + { + ESP_LOGE(tag, "Read failed: expected %u bytes, got %u", fileSize, bytesRead); + delete[] fileContent; + return nullptr; + } + + // Null terminate + fileContent[bytesRead] = '\0'; + ESP_LOGD(tag, "Successfully read %u bytes from %s", bytesRead, path); + return fileContent; +} + +String getSoftAPMacAddress() +{ + uint8_t mac[6]; + WiFi.softAPmacAddress(mac); + + String macString = ""; + for (int i = 0; i < 6; i++) + { + macString += String(mac[i], HEX); + if (i < 5) + macString += ":"; + } + return macString; +} + +String listDirAsHtml(String directoryList[], int count) +{ + String listedFiles; + + for (int i = 0; i < count; i++) + { + // directory html + listedFiles += "Dir: "; + listedFiles += directoryList[i]; + listedFiles += "/-\n"; + + filesDropdownOptions += "\n"; + + dirDropdownOptions += "\n"; + + File dir = LittleFS.open(directoryList[i]); + File file = dir.openNextFile(); + while (file) + { + String fileName = file.name(); + if (!file.isDirectory()) + { + // Serial.println(" File: " + String(file.name())); + listedFiles += "  "; + listedFiles += fileName; + listedFiles += ""; + listedFiles += convertFileSize(file.size()); + listedFiles += "\n"; + + filesDropdownOptions += "\n"; + } + file = dir.openNextFile(); + } + dir.close(); + } + + return listedFiles; +} + +/******************** Specific Html Processors ********************/ +// file manager html processor +String fileManagerHtmlProcessor(const String &var) +{ + if (var == "ALLOWED_EXTENSIONS_EDIT") + { + return allowedExtensionsForEdit; + } + + if (var == "FS_FREE_BYTES") + { + return convertFileSize(LittleFS.totalBytes() - LittleFS.usedBytes()); + } + + if (var == "FS_USED_BYTES") + { + return convertFileSize(LittleFS.usedBytes()); + } + + if (var == "FS_TOTAL_BYTES") + { + return convertFileSize(LittleFS.totalBytes()); + } + + if (var == "RAM_FREE_BYTES") + { + return convertFileSize(LittleFS.totalBytes()); + } + + if (var == "RAM_USED_BYTES") + { + return convertFileSize(LittleFS.totalBytes()); + } + + if (var == "RAM_TOTAL_BYTES") + { + return convertFileSize(LittleFS.totalBytes()); + } + + if (var == "LISTED_FILES") + { + filesDropdownOptions = ""; // clear out + dirDropdownOptions = ""; // clear out + String directories[MAX_DIRECTORIES]; + int dirCount = 0; + getAllDirectories(directories, dirCount); + return listDirAsHtml(directories, dirCount); + } + + if (var == "EDIT-DEL_FILES") + { + return filesDropdownOptions; + } + + if (var == "DIR_LIST") + { + return dirDropdownOptions; + } + + if (var == "SAVE_PATH_INPUT") + { + return savePath; + } + + if (var == "FIRM_VER") + { + return localVersion.toString(); + } + + return var; +} + +String HomeHtmlProcessor(const String &var) +{ + if (var == "APP_NAME") + { + // return sysProps.appName; + return "N/A"; + } + if (var == "OLED") + { + return "N/A"; + } + if (var == "STRIP1") + { + // return (strip1) ? "Yes" : "No"; + return "N/A"; + } + if (var == "STRIP2") + { + // return (strip2) ? "Yes" : "No"; + return "N/A"; + } + if (var == "FRONT_LIGHT") + { + // return (animProps.frontLight.enabled) ? "Yes" : "No"; + return "N/A"; + } + if (var == "REAR_LIGHT") + { + // return (animProps.rearLight.enabled) ? "Yes" : "No"; + return "N/A"; + } + if (var == "FIRMWARE") + { + return localVersion.toString(); + } + if (var == "BOOTH_T") + { + // return String(sysProps.t_sensor.temperature) + "F"; + return "N/A"; + } + if (var == "SETPOINT") + { + // return String(sysProps.t_sensor.Setpoint1) + "F"; + return "N/A"; + } + if (var == "FLASH_SIZE") + { + return convertFileSize(ESP.getSketchSize()); + } + if (var == "FLASH_FREE") + { + return convertFileSize(ESP.getFreeSketchSpace()); + } + if (var == "HEAP_SIZE") + { + return convertFileSize(ESP.getHeapSize()); + } + if (var == "HEAP_FREE") + { + return convertFileSize(ESP.getFreeHeap()); + } + if (var == "CPU_FREQ") + { + return String(ESP.getCpuFreqMHz()) + "Mhz"; + } + if (var == "IP") + { + return WiFi.localIP().toString(); + } + if (var == "MAC") + { + return chipInfo.macStr; + } + if (var == "SSID") + { + return WiFi.SSID(); + } + if (var == "RSSI") + { + return String(WiFi.RSSI()); + } + if (var == "WIFI_CH") + { + return String(WiFi.channel()); + } + if (var == "ENCRYP") + { + return String(WiFi.encryptionType(0)); + } + if (var == "AP_SSID") + { + return WiFi.softAPSSID(); + } + if (var == "AP_CLIENTS") + { + return String(WiFi.softAPgetStationNum()); + } + if (var == "BLE") + { + return (commMode == COMM_WIFI_AP_BLE) ? "Yes" : "No"; + } + if (var == "BLE_SSID") + { + // return (commMode == COMM_WIFI_AP_BLE) ? BLEDeviceName : ""; + return "N/A"; + } + if (var == "BLE_CLIENTS") + { + // return (BTDeviceConnected) ? "1" : "0"; + return "N/A"; + } + if (var == "AP_MAC") + { + return getSoftAPMacAddress(); + } + + // Return an empty string if the variable is not recognized + return var; +} + +void handleUpdateProgress(AsyncWebServerRequest *request) +{ + static const char *tag = "UpdateProgress"; + + // if (!request->authenticate(http_username, http_password)) { + // return request->requestAuthentication(); + // } + + request->send(200, "text/plain", "Update progress"); // Send a simple response +} diff --git a/temporary/BLE-FlashStick-Service.h b/temporary/BLE-FlashStick-Service.h new file mode 100644 index 0000000..29260e8 --- /dev/null +++ b/temporary/BLE-FlashStick-Service.h @@ -0,0 +1,4 @@ +#pragma once + +#include +#include diff --git a/temporary/BLE-FlaskStick-Service.cpp b/temporary/BLE-FlaskStick-Service.cpp new file mode 100644 index 0000000..2172ffc --- /dev/null +++ b/temporary/BLE-FlaskStick-Service.cpp @@ -0,0 +1,157 @@ +#include "BLE-FlashStick-Service.h" +#include "WiFi.h" +#include "my_wifi.h" +#include "global.h" +#include "AppUpgrade.h" +#include "AppVersion.h" + +static const char *tag = "BLE_FlashStickService"; + +#define UPGRADE_SERVICE_UUID "abcdef03-2345-6789-1234-56789abcdef0" +#define UPGRADE_CHARACTERISTIC1_UUID "abcdef03-2345-6789-1234-56789abcdef1" + +NimBLEService *pUpgradeService = nullptr; +NimBLECharacteristic *pUpgradeCharacteristic1 = nullptr; + +enum WIFI_STAT : byte { WIFI_DISCONNECTED=0, WIFI_BAD_CREDS=1, WIFI_NO_AP=2, WIFI_CONNECTED=3 }; + +struct FLASHSTICK_PACKET { + bool reistered = false; + char msg[16] = "Hello..."; +}flashstickPacket; + + +// Class for handling server events +class ServerCallbacks : public NimBLEServerCallbacks { + void onConnect(NimBLEServer* pServer) override { + ESP_LOGI(tag, "Flash-Stick connected"); + } + + void onDisconnect(NimBLEServer* pServer) override { + ESP_LOGI(tag, "Flash-Stick disconnected"); + } +}; + + +// Class for handling characteristic events +class UpgradeChar_Callbacks : public NimBLECharacteristicCallbacks { + + void onWrite(NimBLECharacteristic *pCharacteristic) override { + std::string value = pCharacteristic->getValue(); + ESP_LOGD(tag, "Upgrade Char written with value: %s", value.c_str()); + + if (value.compare(0, 12, "wifi-connect") == 0) { // Update WiFi credentials + JsonDocument doc; + deserializeJson(doc, value.substr(13)); + JsonObject wifiJson = doc.as(); + String ssid = wifiJson["ssid"].as(); + String pass = wifiJson["pass"].as(); + ESP_LOGI(tag, "Wifi Credentials: %s, %s", ssid.c_str(), pass.c_str()); + + bool status = StartWifiConnectTask(ssid, pass); + if(status == true){ + updatePacket.wifiStatus = WIFI_DISCONNECTED; + updatePacket.wifiOnline = false; + updatePacket.wifiIP[0] = updatePacket.wifiIP[1] = updatePacket.wifiIP[2] = updatePacket.wifiIP[3] = 0; + }else{ + ESP_LOGI(tag, "Failed to start WiFi connection task"); + } + } + else if (value.compare("version-check") == 0) { // Check if new version is available + ESP_LOGI(tag, "Version check command received: newVersion=%d.%d.%d", otaVersion.major(), otaVersion.minor(), otaVersion.patch()); + if(updatePacket.newVersion[0] == 0){ + startVersionCheckTask(); // start the task and done + }else{ + ESP_LOGI(tag, "Version already checked"); + } + } + else if (value.compare("upgrade-start") == 0) { // Start OTA update + ESP_LOGI(tag, "Start OTA update command received"); + startFirmwareUpdateTask(nullptr); // start the task + } + else if (value.compare("rename-device") == 0) { // Start renaming device + ESP_LOGI(tag, "Start renane device command received"); + } + else { + ESP_LOGW(tag, "Unknown command received: %s", value.c_str()); + } + } + + void onRead(NimBLECharacteristic *pCharacteristic) override { + updatePacket.wifiOnline = InternetAvailable; + if(WiFi.status() == WL_CONNECTED){ + updatePacket.wifiStatus = WIFI_CONNECTED; + if(updatePacket.wifiIP[0] == 0){ + updatePacket.wifiIP[0] = WiFi.localIP()[0]; + updatePacket.wifiIP[1] = WiFi.localIP()[1]; + updatePacket.wifiIP[2] = WiFi.localIP()[2]; + updatePacket.wifiIP[3] = WiFi.localIP()[3]; + } + }else{ + updatePacket.wifiStatus = WIFI_DISCONNECTED; + if(updatePacket.wifiIP[0] > 0){ + updatePacket.wifiIP[0] = 0; + updatePacket.wifiIP[1] = 0; + updatePacket.wifiIP[2] = 0; + updatePacket.wifiIP[3] = 0; + } + } + + //update version + if(otaVersion.major() != 0){ + ESP_LOGI(tag, "Updated new version: major=%d, minor=%d, patch=%d", otaVersion.major(), otaVersion.minor(), otaVersion.patch()); + updatePacket.newVersion[0] = otaVersion.major(); + updatePacket.newVersion[1] = otaVersion.minor(); + updatePacket.newVersion[2] = otaVersion.patch(); + } + + pCharacteristic->setValue(reinterpret_cast(&updatePacket), sizeof(updatePacket)); + ESP_LOGI(tag, "Upgrade Char read"); + } +}; + + +void bleUpgrade_send_message(String s){ + if(pUpgradeCharacteristic2){ + if (s != nullptr) { + pUpgradeCharacteristic2->setValue(s); + pUpgradeCharacteristic2->notify(); + } else { + ESP_LOGW(tag, "Null string passed to bleUpgrade_send_message"); + } + } +} + + +void Init_UpgradeBLEService(NimBLEServer *pServer){ + + // Create Upgrade BLE Service + pUpgradeService= pServer->createService( UPGRADE_SERVICE_UUID ); + + pUpgradeCharacteristic1 = pUpgradeService->createCharacteristic( + UPGRADE_CHARACTERISTIC1_UUID, + NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY + ); + + // Register the callback with the characteristic + pUpgradeCharacteristic1->setCallbacks(new UpgradeChar_Callbacks()); + ESP_LOGI(tag, "Upgrade callback registered!"); + + + pUpgradeCharacteristic2 = pUpgradeService->createCharacteristic( + UPGRADE_CHARACTERISTIC2_UUID, + NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY + ); + + // Register the callback with the characteristic + pUpgradeCharacteristic2->setCallbacks(new UpgradeChar_Callbacks()); + ESP_LOGI(tag, "Upgrade callback registered!"); + + pUpgradeService->start(); + + NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); + pAdvertising->addServiceUUID( UPGRADE_SERVICE_UUID ); // Advertise service UUID + + +} + diff --git a/temporary/EventBoxTest.html b/temporary/EventBoxTest.html new file mode 100644 index 0000000..114f15e --- /dev/null +++ b/temporary/EventBoxTest.html @@ -0,0 +1,82 @@ + + + + + + + +Event Container + + + +

+ + + + diff --git a/temporary/Temp/ATALights2.cpp b/temporary/Temp/ATALights2.cpp new file mode 100644 index 0000000..d64a258 --- /dev/null +++ b/temporary/Temp/ATALights2.cpp @@ -0,0 +1,178 @@ +#include "ATALights.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include +#include "Animations.h" +#include +#include + + +static const char* tag = "strips"; + +// Define constants for maximum LEDs +#define MAX_LEDS 300 // Adjust based on your LED requirements + +// Create pointers for NeoPixelBus objects +void* strip1 = nullptr; +void* strip2 = nullptr; + +TaskHandle_t Animation_Task_Handle; + +// Runtime configuration variables +int numLeds1 = 0, numLeds2 = 0; // Number of LEDs for each strip +int dataPin1 = -1, dataPin2 = -1; // Data pins for each strip +String chipType1, chipType2; // Chip types (e.g., WS2812, SK6812, TM1814) +String colorOrder1, colorOrder2; // Color orders (e.g., GRB, RGB, BGR) + + +void Init_Lights_Task(void){ + + xTaskCreatePinnedToCore(Lights_Control_Task, "LumaMaster_Task", 1024*6, NULL, 1, &Animation_Task_Handle, CONFIG_ARDUINO_RUNNING_CORE); + ESP_LOGI(tag, "Lights Task Created..."); + + + // Example runtime configuration for two strips + dataPin1 = 5; // Pin for Strip 1 + numLeds1 = 150; // Number of LEDs on Strip 1 + chipType1 = "WS2812"; // Chip type for Strip 1 + colorOrder1 = "GRB"; // Color order for Strip 1 + + dataPin2 = 18; // Pin for Strip 2 + numLeds2 = 100; // Number of LEDs on Strip 2 + chipType2 = "WS2812"; // Chip type for Strip 2 + colorOrder2 = "RGB"; // Color order for Strip 2 + + // Dynamically initialize the strips + strip1 = initializeStrip(dataPin1, numLeds1, chipType1, colorOrder1); + strip2 = initializeStrip(dataPin2, numLeds2, chipType2, colorOrder2); + + + // Start the strips if initialized + if (strip1) static_cast*>(strip1)->Begin(); + if (strip2) static_cast*>(strip2)->Begin(); +} + + +void Animation_Loop_Exit(void){ + if( Animation_Task_Handle ){ + xTaskNotifyGive( Animation_Task_Handle ); + } +} + + +void LightsON(void){ + FastLED.show(); +} + + +void LightsOff(void){ + FastLED.clear(); + FastLED.show(); +} + + + +inline void setPixel1(int pixelIndex, const CRGB col) { + register uint16_t x = pixelIndex + ledSettings[0].shift; + // If strip.effSize is power of 2, use faster bit masking + if ((ledSettings[0].effSize & (ledSettings[0].effSize - 1)) == 0) { + x = (x < 0) ? ((x + ledSettings[0].effSize) & (ledSettings[0].effSize - 1)) : (x & (ledSettings[0].effSize - 1)); + leds1[(x + ledSettings[0].offset) & (ledSettings[0].effSize - 1)] = col; + } else { + // For non-power-of-2 sizes, still need modulo + x = (x < 0) ? ((x + ledSettings[0].effSize) % ledSettings[0].effSize) : (x % ledSettings[0].effSize); + leds1[(x + ledSettings[0].offset) % ledSettings[0].effSize] = col; + } +} + + +inline void setPixel2(int pixelIndex, const CRGB col) { + register uint16_t x = pixelIndex + ledSettings[1].shift; + // If strip.effSize is power of 2, use faster bit masking + if ((ledSettings[1].effSize & (ledSettings[1].effSize - 1)) == 0) { + x = (x < 0) ? ((x + ledSettings[1].effSize) & (ledSettings[1].effSize - 1)) : (x & (ledSettings[1].effSize - 1)); + leds2[(x + ledSettings[1].offset) & (ledSettings[1].effSize - 1)] = col; + } else { + // For non-power-of-2 sizes, still need modulo + x = (x < 0) ? ((x + ledSettings[1].effSize) % ledSettings[1].effSize) : (x % ledSettings[1].effSize); + leds2[(x + ledSettings[1].offset) % ledSettings[1].effSize] = col; + } +} + + +void Lights_Control_Task_Resume(void){ + vTaskResume(Animation_Task_Handle); +} + +void Lights_Control_Task(void *parameters){ + + ESP_LOGD(tag, "Lights Control Task Entered..."); + vTaskSuspend(NULL); + ESP_LOGD(tag, "Lights Control Task Resumed..."); + vTaskDelay(2000); + fill_solid(leds1, ledSettings[0].size-1, CRGB::Blue); + FastLED.show(); + vTaskDelay(5000); + + while(true){ + Animation_Loop(2000, [&]() { + ESP_LOGD(tag, "Looping...."); + + // Example animation: Alternate colors between strips + setStripColor>(strip1, numLeds1, RgbColor(255, 0, 0)); // Red for Strip 1 + setStripColor>(strip2, numLeds2, RgbColor(0, 255, 0)); // Green for Strip 2 + delay(1000); + + setStripColor>(strip1, numLeds1, RgbColor(0, 0, 255)); // Blue for Strip 1 + setStripColor>(strip2, numLeds2, RgbColor(255, 255, 0)); // Yellow for Strip 2 + delay(1000); + }); + + /* + uint8_t animMode = 1; + switch(animMode){ + case 0: + break; + case 1: + break; + } + */ + } +} + + +void Init_FastLED_Strip(CRGB* leds, uint8_t pin, int size, EOrder rgbOrder, const String& chipType) { + + + +} + +// Function to initialize a strip dynamically (Non-SPI chipsets only) +void* initializeStrip(int dataPin, int numLeds, const String& chipType, const String& colorOrder) { + if (chipType == "WS2812" || chipType == "SK6812") { + if (colorOrder == "GRB") { + return new NeoPixelBus(numLeds, dataPin); + } else if (colorOrder == "RGB") { + return new NeoPixelBus(numLeds, dataPin); + } else if (colorOrder == "BGR") { + return new NeoPixelBus(numLeds, dataPin); + } + } + Serial.println("Unsupported chipset or color order!"); + return nullptr; +} + +// Function to set all LEDs of a strip to a specific color +template +void setStripColor(void* strip, int numLeds, RgbColor color) { + if (strip) { + T* actualStrip = static_cast(strip); + for (int i = 0; i < numLeds; i++) { + actualStrip->SetPixelColor(i, color); + } + actualStrip->Show(); + } +} + + diff --git a/temporary/Temp/BTSerial.cpp b/temporary/Temp/BTSerial.cpp new file mode 100644 index 0000000..4295c4b --- /dev/null +++ b/temporary/Temp/BTSerial.cpp @@ -0,0 +1,240 @@ +#include "BTSerial.h" +#include +#include +#include +#include +#include +#include +#include "command_processor.h" +#include "led_strip.h" +#include "global.h" +#include "JsonConstrain.h" +#include "my_buzzer.h" +#include "common/led_animation.h" + + +static const char* tag = "ble"; +TaskHandle_t BTSerial_Task_Handle; +bool BTDeviceConnected = false; + +BLEServer *pServer = NULL; +BLECharacteristic * pTxCharacteristic; +BLECharacteristic * pRxCharacteristic; +bool oldDeviceConnected = false; +uint8_t txValue = 0; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ +#define SERVICE_UUID_DEF "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID +#define CHARACTERISTIC_UUID_RX_DEF "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" +#define CHARACTERISTIC_UUID_TX_DEF "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" + +String BLEDeviceName; +String BLEKey; +String SERVICE_UUID; +String CHARACTERISTIC_UUID_RX; +String CHARACTERISTIC_UUID_TX; + +#define replyActive true + + +void Init_BTSerial(void) +{ + File file = LittleFS.open("/cfg/ble.json"); + if(!file){ + ESP_LOGE(tag, "Error opening ble.json..."); + } + else{ + JsonDocument doc; + DeserializationError error = deserializeJson(doc, file); + file.close(); + if(error){ ESP_LOGE(tag, "ble.json deserialize error!.."); return;} + + JsonObject bleJson = doc.as(); + + // if(jsonConstrainBool(bleJson, "en", false)){ + SERVICE_UUID = jsonConstrainString(tag, bleJson, "service-uuid", SERVICE_UUID_DEF); + ESP_LOGD(tag, "SERVICE_UUID: %s", SERVICE_UUID.c_str()); + + CHARACTERISTIC_UUID_RX = jsonConstrainString(tag, bleJson, "char-uuid-rx", CHARACTERISTIC_UUID_RX_DEF); + ESP_LOGD(tag, "Char UUID RX: %s", CHARACTERISTIC_UUID_RX.c_str()); + + CHARACTERISTIC_UUID_TX = jsonConstrainString(tag, bleJson, "char-uuid-tx", CHARACTERISTIC_UUID_TX_DEF); + ESP_LOGD(tag, "Char UUID TX: %s", CHARACTERISTIC_UUID_TX.c_str()); + + BLEDeviceName = jsonConstrainString(tag, bleJson, "device-name", "ATA_COMM"); + String hexStr = String(chipInfo.macByte[0], HEX); + hexStr.toUpperCase(); + BLEDeviceName += '_'; + BLEDeviceName += hexStr; + + BLEKey = jsonConstrainString(tag, bleJson, "key", "123456"); + int core = jsonConstrain(tag, bleJson, "core", 0, 1, 0); + ESP_LOGD(tag, "BLE SSID: %s, key: %s, core: %d", BLEDeviceName.c_str(),BLEKey.c_str(), core); + + xTaskCreatePinnedToCore(BTSerial_Task, "BTSerial_Task", 12000, NULL, 1, &BTSerial_Task_Handle, core); + //} + } +} + +/** None of these are required as they will be handled by the library with defaults. ** + ** Remove as you see fit for your needs */ +class MyServerCallbacks: public BLEServerCallbacks { + void onConnect(BLEServer* pServer) { + BTDeviceConnected = true; + //BLEDevice::startAdvertising();//adding this line allows for multiple simultaneous BLE connections + }; + /* + void onConnect(BLEServer* pServer, BLEClient* pClient) { + BTDeviceConnected = true; + BLEAddress connectedAddress = pClient->getPeerAddress(); + Log.traceln("Client connected: %s", connectedAddress.toString().c_str()); + } + */ + + void onDisconnect(BLEServer* pServer) { + BTDeviceConnected = false; + } + /***************** New - Security handled here ******************** + ****** Note: these are the same return values as defaults ********/ + uint32_t onPassKeyRequest(){ + ESP_LOGD(tag, "Server PassKeyRequest"); + return 123456; + } + + bool onConfirmPIN(uint32_t pass_key){ + ESP_LOGD(tag, "The passkey YES/NO number: %d", pass_key); + return true; + } + + void onAuthenticationComplete(ble_gap_conn_desc desc){ + ESP_LOGD(tag, "Starting BLE work!"); + } + /*******************************************************************/ +}; + +#define MAX_PACKET_PARAMS 8 +int packet_data[MAX_PACKET_PARAMS]; +int paramCount = 0; +class MyCallbacks: public BLECharacteristicCallbacks +{ + void onWrite(BLECharacteristic *pCharacteristic) + { + std::string rxValue = pCharacteristic->getValue(); + + ESP_LOGD(tag, "raw: %s", rxValue.c_str()); + if(!rxValue.empty() && ((rxValue[0] == '$') || rxValue[0] == '%')){ + extractCommand(&rxValue[0], packet_data, ¶mCount); // delimited command + + // Call Animation Index Update + if(packet_data[0] == 100){ + int cntDown = 0; + if(packet_data[2]){ + cntDown = packet_data[2]; + } + animProps.event[packet_data[1]].countDown = cntDown; + animProps.event[packet_data[1]].type = EV_NORMAL; + PostNewEvent(animProps.event[packet_data[1]]); + }else{ + // TODO: Process other Bluetooth commands + } + + if(replyActive){ + rxValue[0] = '%'; + pTxCharacteristic->setValue(rxValue); + pTxCharacteristic->notify(); + } + // print params + ESP_LOGD(tag, "packet: %c%d, %d", rxValue[0], packet_data[0], packet_data[1]); + } + } +}; + +void extractCommand(char* packet, int* data, int* count) +{ + int base = 10; + //if(*packet == '#'){ + // base = 16; + //} + packet++; + + char* token = strtok(packet, ",\n"); + int8_t index = 0; + + while (token != NULL && index < MAX_PACKET_PARAMS) { + data[index] = strtol(token, NULL, base); + index++; + token = strtok(NULL, ",\n"); + } + *count = index; +} + +void BTSerial_Task(void *parameters){ + vTaskDelay(1000); + // Extend watchdog timer + esp_task_wdt_init(5, false); + + // Create the BLE Device. + BLEDevice::init(BLEDeviceName.c_str()); + + // Create the BLE Server + pServer = BLEDevice::createServer(); + pServer->setCallbacks(new MyServerCallbacks()); + + // Create the BLE Service + BLEService *pService = pServer->createService((const char*)SERVICE_UUID.c_str()); + + // Create a BLE Characteristic + pTxCharacteristic = pService->createCharacteristic( (const char*)CHARACTERISTIC_UUID_TX.c_str(), NIMBLE_PROPERTY::NOTIFY ); + /*************************************************** + NOTE: DO NOT create a 2902 descriptor, it will be created auto.. if notifications + or indications are enabled on a characteristic. + + pCharacteristic->addDescriptor(new BLE2902()); + ****************************************************/ + pRxCharacteristic = pService->createCharacteristic( (const char*)CHARACTERISTIC_UUID_RX.c_str(), NIMBLE_PROPERTY::WRITE ); + pRxCharacteristic->setCallbacks(new MyCallbacks()); + // Start the service + pService->start(); + + // Start advertising + vTaskDelay(100); + pServer->getAdvertising()->start(); + ESP_LOGV(tag, "Waiting for a client..."); + ANIMATION_EVENT newEvent; + for(;;){ + //if (BTDeviceConnected) { + //pTxCharacteristic->setValue(&txValue, 1); + //pTxCharacteristic->notify(); + //txValue++; + //vTaskDelay(100); // bluetooth stack will go into congestion, if too many packets are sent + //} + + // disconnecting + if (!BTDeviceConnected && oldDeviceConnected) { + vTaskDelay(750); // give the bluetooth stack the chance to get things ready + pServer->startAdvertising(); // restart advertising + oldDeviceConnected = BTDeviceConnected; + newEvent.animIndex = POP_BLE_DISC; + newEvent.type = EV_INJECT; + PostNewEvent(newEvent); + ESP_LOGI(tag, "Client disconnected..."); + ESP_LOGI(tag, "Advertising again..."); + Buzzer_Play_Tune(TUNE_BLE_DISCONNECTED); + } + + // connecting + if (BTDeviceConnected && !oldDeviceConnected) { + // do stuff here on connecting + oldDeviceConnected = BTDeviceConnected; + newEvent.animIndex = POP_BLE_CONN; + newEvent.type = EV_INJECT; + PostNewEvent(newEvent); + ESP_LOGI(tag, "Client connected..."); + Buzzer_Play_Tune(TUNE_BLE_CONNECTED); + } + + vTaskDelay(200); + } + +} \ No newline at end of file diff --git a/temporary/Temp/BTSerial.h b/temporary/Temp/BTSerial.h new file mode 100644 index 0000000..1691151 --- /dev/null +++ b/temporary/Temp/BTSerial.h @@ -0,0 +1,16 @@ +#ifndef _BTSERIAL_H +#define _BTSERIAL_H + +#include + + +extern bool BTDeviceConnected; +extern String BLEDeviceName; + +void Init_BTSerial(void); + +void BTSerial_Task(void *parameters); + +void extractCommand(char* packet, int* data, int* count); + +#endif \ No newline at end of file diff --git a/temporary/Temp/MyNeoPixelBus.cpp b/temporary/Temp/MyNeoPixelBus.cpp new file mode 100644 index 0000000..74a81f6 --- /dev/null +++ b/temporary/Temp/MyNeoPixelBus.cpp @@ -0,0 +1,47 @@ +#include + +// Define constants for maximum LEDs +#define MAX_LEDS 300 // Adjust based on your LED requirements + +// Wrapper class for dynamic LED strips +class DynamicLedStrip { +private: + void* strip; // Pointer to the NeoPixelBus object + int numLeds; // Number of LEDs + String colorOrder; // Color order (for reference) + +public: + DynamicLedStrip() : strip(nullptr), numLeds(0), colorOrder("") {} + + // Initialize the LED strip + void initialize(int dataPin, int numLeds, const String& chipType, const String& colorOrder) { + this->numLeds = numLeds; + this->colorOrder = colorOrder; + + if (chipType == "WS2812" || chipType == "SK6812") { + if (colorOrder == "GRB") { + strip = new NeoPixelBus(numLeds, dataPin); + } else if (colorOrder == "RGB") { + strip = new NeoPixelBus(numLeds, dataPin); + } else if (colorOrder == "BGR") { + strip = new NeoPixelBus(numLeds, dataPin); + } + } + + if (strip) { + static_cast*>(strip)->Begin(); + } else { + Serial.println("Unsupported chipset or color order!"); + } + } + + // Set all LEDs to a specific color + void setColor(const RgbColor& color) { + if (strip) { + for (int i = 0; i < numLeds; i++) { + static_cast*>(strip)->SetPixelColor(i, color); + } + static_cast*>(strip)->Show(); + } + } +}; \ No newline at end of file diff --git a/temporary/Temp/command_processor.cpp b/temporary/Temp/command_processor.cpp new file mode 100644 index 0000000..8f4747c --- /dev/null +++ b/temporary/Temp/command_processor.cpp @@ -0,0 +1,71 @@ +#include "command_processor.h" + +int boothState[16]; + +void process_command(int* data, int paramCount) +{ + bool reply = false; + + switch(*data){ + case COMM_NULL: + break; + case COMM_REBOOT: + break; + case COMM_DIN: + break; + case COMM_DOUT: + break; + case COMM_RELAY: + break; + case COMM_AOUT: + break; + case COMM_AIN: + break; + case COMM_ECHO: + break; + case COMM_LED_STATUS: + break; + case COMM_GET_TEMP: + break; + case COMM_PLAY: + break; + case COMM_RF: + break; + case COMM_NEST: + break; + case COMM_BOOTH_STATE: + break; + case COMM_STRIP1: + + break; + case COMM_STRIP2: + break; + case COMM_OLED: + break; + default:; + } + + + if(reply){ + + } +} + +/* +int AppStates[16]; + +int getEvent(int index, int& ev){ + return ev[index]; + +} +enum BoothStates = { } +void RunAnimation(int stateIndex){ + switch (AppStates[stateIndex]){ + case 0: + break; + case 1: + break; + + } +} +*/ diff --git a/temporary/Temp/command_processor.h b/temporary/Temp/command_processor.h new file mode 100644 index 0000000..2367231 --- /dev/null +++ b/temporary/Temp/command_processor.h @@ -0,0 +1,31 @@ +#ifndef _COMMAND_PROCESSOR_H +#define _COMMAND_PROCESSOR_H + +extern int boothState[16]; + +void process_command(int* data, int paramCount); + +enum COMM_FUNC{ + COMM_NULL = 0, + COMM_REBOOT = 1, + COMM_DIN = 2, + COMM_DOUT = 3, + COMM_RELAY = 4, + COMM_AOUT = 5, + COMM_AIN = 6, + COMM_ECHO = 7, + COMM_LED_STATUS = 8, + COMM_GET_TEMP = 9, + COMM_PLAY = 10, + COMM_RF = 11, + + COMM_NEST = 30, + COMM_BOOTH_STATE = 50, + COMM_STRIP1 = 75, + COMM_STRIP2 = 100, + COMM_OLED = 125 +}; + + + +#endif diff --git a/temporary/Temp/firmware_html.h b/temporary/Temp/firmware_html.h new file mode 100644 index 0000000..7a7440f --- /dev/null +++ b/temporary/Temp/firmware_html.h @@ -0,0 +1,215 @@ +#ifndef _FIRMWARE_HTML_H +#define _FIRMWARE_HTML_H + +#include + +const char firmware_html_page[] PROGMEM = R"rawliteral( + + + + Firmware Update + + + + + +

Firmware Update

+
+
+ Local Update +
+
+ +
+
+ +
+
+ +
+ + + +
+ +
+ +
+
+
+ + + + +)rawliteral"; + + +#endif \ No newline at end of file diff --git a/temporary/Temp/githubCert.h b/temporary/Temp/githubCert.h new file mode 100644 index 0000000..40a476e --- /dev/null +++ b/temporary/Temp/githubCert.h @@ -0,0 +1,30 @@ +#ifndef CERT_H + +#define CERT_H + +const char * github_rootCACertificate = \ + "-----BEGIN CERTIFICATE-----\n" +"MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs\n" +"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" +"d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j\n" +"ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL\n" +"MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3\n" +"LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug\n" +"RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm\n" +"+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW\n" +"PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM\n" +"xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB\n" +"Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3\n" +"hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg\n" +"EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF\n" +"MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA\n" +"FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec\n" +"nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z\n" +"eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF\n" +"hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2\n" +"Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe\n" +"vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep\n" +"+OkuE6N36B9K\n" +"-----END CERTIFICATE-----\n"; + +#endif diff --git a/temporary/Temp/led_animation.cpp b/temporary/Temp/led_animation.cpp new file mode 100644 index 0000000..6b191af --- /dev/null +++ b/temporary/Temp/led_animation.cpp @@ -0,0 +1,1023 @@ +#include "led_animation.h" + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "common/HSVTable.h" +#include "common/neo_colors.h" +#include "common/color_tools.h" + +static const char* tag = "led_anim"; + + +int AnimTestModeCount = 0; + +ANIM_STATUS animStatus; +WHITE_FILL_STATUS whiteStatus; +HUE_PALLET_DISPENSER HuePalletDispenser;// (1, 1, 1); + +/********************************************************************/ + +// Animation Loop Template +EXIT_TYPE Animation_Loop(ANIM_STATUS &stateMon, int msFreq, TickType_t duration, std::function callback){ + stateMon.busy = true; + ulTaskNotifyTake( pdTRUE, 0); + EXIT_TYPE ret = EXIT_NORMAL; + + TickType_t startTicks = xTaskGetTickCount(); + + for(;;){ + TickType_t xLastWakeTime = xTaskGetTickCount(); + if(callback() == EXIT_FINISHED){ return EXIT_FINISHED; } + + if(ulTaskNotifyTake( pdTRUE, msFreq - ( xTaskGetTickCount() - xLastWakeTime ))){ // delay + stateMon.busy = false; + return EXIT_FROM_NOTIFY; + } + + // May have a duration limit + if(duration){ + if((xLastWakeTime - startTicks) > duration){ + return EXIT_TIMEOUT; + } + } + } + + return ret; +} + +int Const_White_Fill(WHITE_FILL_STATUS &status, FRONT_LIGHT light , float delayFactor, int countDown, int msFreq) { //int msRampUpTime, int msRampDownTime, int msHoldTime, int msFreq, float minDuty, float maxDuty){ + status.busy = true; + ulTaskNotifyTake( pdTRUE, 0); + + // Linear brightness rise + if(light.maxDuty > 100) { light.maxDuty = 100; } + if(light.maxDuty < 1){ light.maxDuty = 1; } + + if (light.minDuty >= light.maxDuty) { light.minDuty = light.maxDuty -1; } + if (light.minDuty < 0) { light.minDuty = 0; } + + // Pre Delay + if(delayFactor > 0.0){ + int waitDelay = int((float)countDown * delayFactor); + ESP_LOGD(tag, "Const Light Delay: %d, countDown: %d", waitDelay, countDown); + if(ulTaskNotifyTake( pdTRUE, waitDelay)){ // delay + status.busy = false; + return EXIT_FROM_NOTIFY; + } + } + + int rampSteps = (countDown - ((float)countDown * delayFactor)) / msFreq; + float dutyStep = ( light.maxDuty - pwmOut[animProps.frontLight.relayIndex]->currDuty ) / rampSteps; + if(dutyStep < 0.1) { dutyStep = 0.1; } + + //Log.noticeln(" upTime: %d, hold: %d, downTime: %d, msFreq: %d, minDuty: %F, maxDuty: %F", msRampUpTime, msHoldTime, msRampDownTime, msFreq, minDuty, maxDuty); + //Log.noticeln(" dutyStep: %F", dutyStep); + // Ramp Up + for(;;){ + //calc new newDuty value + float newDuty = pwmOut[animProps.frontLight.relayIndex]->currDuty + dutyStep; + + //check if dslrbooth jumped ahead and catch up + if(delayFactor > 0){ + if(status.dslrCountStatus > newDuty && ((status.dslrCountStatus-10) < 90)){ + newDuty = status.dslrCountStatus; + } + } + + // Set the new Duty + if(newDuty > light.maxDuty){ newDuty = light.maxDuty; } + pwmOut[animProps.frontLight.relayIndex]->setOutput(newDuty); // set duty + + if(newDuty >= light.maxDuty) { break;} // break if complete + + //check if there was a trigger to leave function + if(ulTaskNotifyTake( pdTRUE, msFreq )){ goto done; } // delay + } + + // Hold Steady for msHoldTime + //Serial.printf("Holding at %.1f\n\r", pwmOut[animProps.frontLight.relay]->currDuty ); + pwmOut[animProps.frontLight.relayIndex]->setOutput(light.maxDuty); + if(ulTaskNotifyTake( pdTRUE, light.holdTime )){ goto done; } // delay + + // Linear Ramp Down to Min + rampSteps = light.rampTime / msFreq; + dutyStep = abs(( pwmOut[animProps.frontLight.relayIndex]->currDuty - light.minDuty) / rampSteps ); + if(dutyStep < .1) { dutyStep = .1; } + //Serial.printf("stepDown: %.1f\n\r", dutyStep); + + for(;;){ + pwmOut[animProps.frontLight.relayIndex]->setOutput( pwmOut[animProps.frontLight.relayIndex]->currDuty - dutyStep ); // set duty + if( pwmOut[animProps.frontLight.relayIndex]->currDuty <= light.minDuty ) { break; } // break if complete + + //if(status.repeat){ goto done;} + if(ulTaskNotifyTake( pdTRUE, msFreq )){ break; } // delay + } + + done: + pwmOut[animProps.frontLight.relayIndex]->setOutput(light.minDuty); + status.busy = false; + status.dslrCountStatus = 0; + return 0; +} + + +// TODO Roamer/Helio type White Fill +//int Const_White_Fill_Disc +/****************************************************************/ +int Animation_White_Fill(LEDSTRIP& strip, ANIM_STATUS &stateMon, int msFillTime, rgbpixel_t& col, rgbpixel_t& baseCol) +{ + stateMon.busy = true; + ulTaskNotifyTake( pdTRUE, 0); + + int msDelay = msFillTime / strip.effSize; + + // Fill background first + for(int i = 0; i < strip.effSize; i++){ + strip.pixels[i] = baseCol; // skips index calc + } + + // Linear fill + for(int i = 0; i < strip.effSize; i++){ + TickType_t xLastWakeTime = xTaskGetTickCount(); + strip.setPixel(i, col, 255); + strip.show(true); + if(ulTaskNotifyTake( pdTRUE, msDelay - (xTaskGetTickCount() - xLastWakeTime ) == pdTRUE )){ return 1; } // delay + } + + stateMon.busy = false; + return 0; +} + +/******************************** WHITE FILL MIRRORED *********************************/ +EXIT_TYPE Animation_White_Fill_Mirrored(LEDSTRIP& strip, ANIMATION_EVENT& event) +{ + int msFillTime = 0; + if(event.countDown > 0){ // use countdown if available + msFillTime = event.countDown; + }else{ + msFillTime = map(event.param1, 0, 100, 0, 10000); + if(msFillTime < 1000){ msFillTime = 1000; } + } + + strip.powerDiv = (event.check4) ? 1 : 0; + + // TODO set msFreq from density + int msFreq = 25; + int halfSize = (strip.effSize / 2); + float steps = ((float)msFreq * (float)halfSize) / msFillTime; + strip.fill(col_black, 0, strip.effSize); // clear/off all pixels + + float stepsTotal = steps; + //int startIndex = 0; + int endIndex = stepsTotal; + //uint8_t frac = 0; + ESP_LOGD(tag, "whitefill-> msFillTime: %d, halfsize: %d, steps: %.3f, freq: %d, hue: %d", msFillTime, halfSize, steps, msFreq, event.hue); + + rgbpixel_t mainCol = GetColorFromHue(event.hue); + //rgbpixel_t fracCol; // = event.col1; + + int lastEndIndex = 0; + EXIT_TYPE ret = Animation_Loop( animStatus, msFreq, INFINITE_LOOP, [&](){ + + stepsTotal += steps; + endIndex = (int)stepsTotal; + if(endIndex != lastEndIndex){ + for(int i = lastEndIndex; i < endIndex; i++){ + strip.setPixelMirrored(i, mainCol); + } + lastEndIndex = endIndex; + strip.show(true); + } + + if(endIndex > (halfSize -1)){ return EXIT_FINISHED; } + + /* + frac = (stepsTotal - (int)stepsTotal) * 255; + fracCol = mainCol; + scalePixel(fracCol, frac); + linearizePixel(fracCol); + strip.setPixelMirrored(endIndex + 1, fracCol); + */ + //strip.show(true); + + return EXIT_NORMAL; + }); + + // TODO remove this trace later + //Log.traceln("exited white fill"); + return ret; +} + +float calculateSteps(int msFreq, float LEDCount, int msFillTime) { + return (msFreq * LEDCount) / msFillTime; +} + +void GetOptimizedStepInterval(float &steps, int &interval, int LEDCount, int msFillTime, int startInterval, float tolerance) +{ + steps = calculateSteps(startInterval, LEDCount, msFillTime); + + while (std::abs(steps - std::round(steps)) > tolerance) { + steps = calculateSteps(++startInterval, LEDCount, msFillTime); + } + interval = startInterval; + steps = round(steps); +} + + +EXIT_TYPE Animation_Linear_Brighten(LEDSTRIP& strip, ANIMATION_EVENT& event){ + return EXIT_NORMAL; +} + + +EXIT_TYPE Animation_Tick_Fill(LEDSTRIP& strip, ANIMATION_EVENT& event){ + return EXIT_NORMAL; +} + + + +/******************************** COLORED SNAKES *********************************/ +EXIT_TYPE Animation_Snakes(LEDSTRIP& strip, ANIMATION_EVENT& event, DIR_TYPE dirType, int cycles) +{ + bool mix = event.check1; + bool rotate = event.check2; + int sectors = constrain(event.param1/10, 1, 10); + int colorCount = constrain(event.param2/10, 1, 10); + strip.powerDiv = (event.check4) ? 1 : 0; + int msFreq = CalcEventInterval(event.speed, MIN_LED_UPDATE_INTERVAL, 100); + int snakeSize = trunc(roundf(strip.effSize / (float)sectors)); + + HuePalletDispenser.Initialize(event.hue, event.hueRange, colorCount); + ESP_LOGD(tag, " size: %d, sectors: %d, freq: %d", snakeSize, sectors, msFreq); + + // Create Color Pallet Array + rgbpixel_t col[colorCount]; + if(colorCount == 1){ + col[0] = GetColorFromHue(event.hue); + }else{ + for(int i = 0; i < colorCount; i++){ + float hue = HuePalletDispenser.GetNextPalletHue(); + col[i] = HUEtoRGB( hue ); + //ESP_LOGD(tag, " hue%d: ", hue); + } + } + + + LED_DIR dir = (dirType == DT_FWD)? DIR_FWD : DIR_REV; + int dirVal = (dir==DIR_FWD)? 1 : -1; + int pixIndex = 0, rotateOffset = 0, colIndex = 0; + bool colorCycle = true; + + strip.fill({0,0,0}, 0, strip.effSize); + + EXIT_TYPE ret = Animation_Loop( animStatus, msFreq, INFINITE_LOOP, std::function([&]() -> int { + + if(colorCycle){ + for(int sector = 0; sector < sectors; sector++){ + int pix = (sector * snakeSize) + pixIndex + rotateOffset; + if(mix){ + strip.setPixel(pix * dirVal, (rgbpixel_t)col[(colIndex + sector)%colorCount]); + }else{ + strip.setPixel(pix * dirVal, (rgbpixel_t)col[colIndex]); + } + } + }else{ + for(int sector = 0; sector < sectors; sector++){ + int pix = (sector * snakeSize) + pixIndex + rotateOffset; + strip.setPixel(pix * dirVal, {0,0,0}); + } + } + + if(rotate){ // turn off front pixels + strip.rotatePixels(dir); + rotateOffset = ++rotateOffset % strip.effSize; + } + strip.show(true); + + pixIndex = ++pixIndex % snakeSize; + + if(pixIndex == 0){ + if(colorCycle){ + colorCycle = false; + }else{ + // Check if there's an iteration limit + if(cycles){ + if(--cycles <= 0){ return EXIT_FINISHED; } + } + // Once full cycle here + if(dirType == DT_BOTH){ + dir = (dir == DIR_FWD)? DIR_REV : DIR_FWD; + dirVal = -dirVal; + } + colorCycle = true; + colIndex = ++colIndex % colorCount; // next color + } + } + return EXIT_NORMAL; + })); + + return ret; +} + + +/******************************** COLORED COMETS *********************************/ +#define COMET_SIZE_FACTOR 0.15 +#define COMET_FADE_FACTOR1 128 +#define COMET_FADE_FACTOR2 192 + +EXIT_TYPE Animation_Comets(LEDSTRIP& strip, ANIMATION_EVENT& event, DIR_TYPE dirType, int cycles){ + int sectorCount = constrain(event.param1/10, 1, 10); // cometCount also + int colorCount = constrain(event.param2/10, 1, 10); // color divisions + bool mix = event.check1; + bool rotate = true; + bool randomDecay = event.check2; + bool shorterTail = event.check3; + uint8_t fadeFactor = COMET_FADE_FACTOR1; + if(shorterTail){ fadeFactor = COMET_FADE_FACTOR2; } + strip.powerDiv = (event.check4) ? 1 : 0; + int msFreq = CalcEventInterval(event.speed, MIN_LED_UPDATE_INTERVAL, 100); + int sectorSize = trunc(roundf(strip.effSize / (float)sectorCount)); + + int cometSize = (strip.effSize / sectorCount) * COMET_SIZE_FACTOR; + if(cometSize < 1){ cometSize = 1;} + + HuePalletDispenser.Initialize(event.hue, event.hueRange, colorCount); + ESP_LOGD(tag, " size: %d, sectorCount: %d, freq: %d", sectorSize, sectorCount, msFreq); + + // Create Color Pallet Array + rgbpixel_t col[colorCount]; + if(colorCount == 1){ + col[0] = GetColorFromHue(event.hue); + }else{ + for(int i = 0; i < colorCount; i++){ + float hue = HuePalletDispenser.GetNextPalletHue(); + col[i] = HUEtoRGB( hue ); + //ESP_LOGD(tag, " hue%d: ", hue); + } + } + + LED_DIR dir; + if(dirType == DT_BOTH){ + dir = DIR_FWD; + }else{ + dir = (dirType == DT_FWD)? DIR_FWD : DIR_REV; + } + + strip.fill({0,0,0}, 0, strip.effSize); + + int rotateIndex = 0; + int cycleCount = 0; + int colIndex = 0; + int rotOffset = 0; + EXIT_TYPE ret = Animation_Loop( animStatus, msFreq, INFINITE_LOOP, std::function([&]() -> int { + + // Random Tail Decay here.... + if(randomDecay){ + for (int j = 0; j < strip.effSize; j++){ + if (random(10) > 5){ + fadeToBlackBy(strip.pixels[j], fadeFactor); + } + } + }else{ // Regular Tail Decay + for (int j = 0; j < strip.effSize; j++){ + fadeToBlackBy(strip.pixels[j], fadeFactor); + } + } + + // Draw Comets + if(mix){ + for(int sector = 0; sector < sectorCount; sector++){ + strip.fill((rgbpixel_t)col[(colIndex + sector) % colorCount], rotOffset + (sector * sectorSize), cometSize); + } + }else{ + for(int sector = 0; sector < sectorCount; sector++){ + strip.fill((rgbpixel_t)col[colIndex], rotOffset + (sector * sectorSize), cometSize); + } + } + + rotateIndex = ++rotateIndex % strip.effSize; + + if(dir == DIR_FWD){ rotOffset++; } + else{ rotOffset--; } + + // full rotation + if(rotateIndex == 0){ + // reverse rotation + if(dirType == DT_BOTH){ + dir = (dir == DIR_FWD)? DIR_REV : DIR_FWD; + } + + if(cycles){ + cycleCount++; + if(cycleCount >= cycles){ + return EXIT_FINISHED; + } + } + + colIndex = ++colIndex % colorCount; // Increment color index + } + strip.show(true); + + return EXIT_NORMAL; + })); + + return ret; +} + +void fadeToBlackBy(rgbpixel_t& pixel, uint8_t fadeAmount) { + pixel.red = (pixel.red <= 8) ? 0 : pixel.red - ((pixel.red * fadeAmount) >> 8); + pixel.grn = (pixel.grn <= 8) ? 0 : pixel.grn - ((pixel.grn * fadeAmount) >> 8); + pixel.blu = (pixel.blu <= 8) ? 0 : pixel.blu - ((pixel.blu * fadeAmount) >> 8); +} + +inline uint8_t nscale8(uint8_t i, uint8_t scale) { + return (((int)i * (int)(scale)) >> 8) + ((i && scale) ? 1 : 0); +} + + +/******************************** COLORED SECTORS *********************************/ +EXIT_TYPE Animation_Sectors(LEDSTRIP& strip, ANIMATION_EVENT& event, DIR_TYPE dirType, int cycles){ + bool rotate = event.check2; + int sectorCount = constrain(event.param1/10, 2, 10); + int colorCount = constrain(event.param2/10, 1, 10); + + strip.powerDiv = (event.check4) ? 1 : 0; + int msFreq = CalcEventInterval(event.speed, MIN_LED_UPDATE_INTERVAL, 100); + int sectorSize = trunc(roundf(strip.effSize / (float)sectorCount)); + + ESP_LOGD(tag, "hue: %d, range: %d\n", event.hue, event.hueRange); + HuePalletDispenser.Initialize(event.hue, event.hueRange, colorCount); + ESP_LOGD(tag, "size: %d, sectorCount: %d, colors: %d, freq: %d", sectorSize, sectorCount, colorCount, msFreq); + + // Create Color Pallet Array + rgbpixel_t col[colorCount]; + if(colorCount == 1){ + col[0] = GetColorFromHue(event.hue); + }else{ + for(int i = 0; i < colorCount; i++){ + float hue = HuePalletDispenser.GetNextPalletHue(); + col[i] = HUEtoRGB( hue ); + //ESP_LOGD(tag, "hue%d: ", hue); + } + } + + LED_DIR dir; + if(dirType == DT_BOTH){ + dir = DIR_FWD; + }else{ + dir = (dirType == DT_FWD)? DIR_FWD : DIR_REV; + } + + strip.fill({0,0,0}, 0, strip.effSize); + + bool updateColors = true; + int rotateIndex = 0; + int cycleCount = 0; + int colIndex = 0; + + EXIT_TYPE ret = Animation_Loop( animStatus, msFreq, INFINITE_LOOP, std::function([&]() -> int { + + if(updateColors){ // + for(int sector = 0; sector < sectorCount; sector++){ + strip.fill((rgbpixel_t)col[(colIndex + sector) % colorCount], sector * sectorSize, sectorSize); + } + colIndex = ++colIndex % colorCount; // Increment color index + updateColors = false; + } + + if(rotate){ // turn off front pixels + strip.rotatePixels(dir); + rotateIndex = ++rotateIndex % strip.effSize; + + // full rotation + if(rotateIndex == 0){ + // reverse rotation + if(dirType == DT_BOTH){ + dir = (dir == DIR_FWD)? DIR_REV : DIR_FWD; + } + + if(cycles){ + cycleCount++; + if(cycleCount >= cycles){ + return EXIT_FINISHED; + } + } + + updateColors = true; + } + } + + strip.show(true); + return EXIT_NORMAL; + })); + + return ret; +} + + +/******************************** DASHES SECTORS *********************************/ +EXIT_TYPE Animation_Dashes(LEDSTRIP& strip, ANIMATION_EVENT& event, DIR_TYPE dirType, int cycles){ + int sectorCount = constrain(event.param1/10, 1, 10); + int colorCount = constrain(event.param2/10, 1, 10); + bool mix = event.check1; + bool rotate = event.check2; + + strip.powerDiv = (event.check4) ? 1 : 0; + int msFreq = CalcEventInterval(event.speed, MIN_LED_UPDATE_INTERVAL, 100); + int sectorSize = trunc(roundf(strip.effSize / (float)sectorCount)); + + HuePalletDispenser.Initialize(event.hue, event.hueRange, colorCount); + ESP_LOGD(tag, " size: %d, sectorCount: %d, freq: %d", sectorSize, sectorCount, msFreq); + + // Create Color Pallet Array + rgbpixel_t col[colorCount]; + if(colorCount == 1){ + col[0] = GetColorFromHue(event.hue); + }else{ + for(int i = 0; i < colorCount; i++){ + float hue = HuePalletDispenser.GetNextPalletHue(); + col[i] = HUEtoRGB( hue ); + //ESP_LOGD(tag, " hue%d: ", hue); + } + } + + LED_DIR dir; + if(dirType == DT_BOTH){ + dir = DIR_FWD; + }else{ + dir = (dirType == DT_FWD)? DIR_FWD : DIR_REV; + } + + strip.fill({0,0,0}, 0, strip.effSize); + + bool updateColors = true; + int rotateIndex = 0; + int cycleCount = 0; + int colIndex = 0; + + EXIT_TYPE ret = Animation_Loop( animStatus, msFreq, INFINITE_LOOP, std::function([&]() -> int { + if(updateColors){ // + if(mix){ + for(int sector = 0; sector < sectorCount; sector++){ + strip.fill((rgbpixel_t)col[(colIndex + sector) % colorCount], sector * sectorSize, sectorSize/2); + } + }else{ + for(int sector = 0; sector < sectorCount; sector++){ + strip.fill((rgbpixel_t)col[colIndex], sector * sectorSize, sectorSize/2); + } + } + + colIndex = ++colIndex % colorCount; // Increment color index + updateColors = false; + } + + if(rotate){ // turn off front pixels + strip.rotatePixels(dir); + rotateIndex = ++rotateIndex % strip.effSize; + + // full rotation + if(rotateIndex == 0){ + // reverse rotation + if(dirType == DT_BOTH){ + dir = (dir == DIR_FWD)? DIR_REV : DIR_FWD; + } + + if(cycles){ + cycleCount++; + if(cycleCount >= cycles){ + return EXIT_FINISHED; + } + } + + updateColors = true; + } + } + + strip.show(true); + + return EXIT_NORMAL; + })); + + return ret; +} + + +/****************************************************************/ +void Animation_TransTo_Color(LEDSTRIP& strip, ANIM_STATUS &stateMon, int msTransTime, int msFreq, rgbpixel_t newCol) +{ + stateMon.busy = true; + ulTaskNotifyTake( pdTRUE, 0); + + int numSteps = (msTransTime / msFreq); + if (numSteps == 0) { return; } + + for (uint8_t step = 0; step < numSteps; step++) { + TickType_t xLastWakeTime = xTaskGetTickCount(); + + for (size_t i = 0; i < strip.effSize; i++) { + rgbpixel_t colorDiff = { + .red = static_cast((newCol.red - strip.pixels[i].red) / numSteps), + .grn = static_cast((newCol.grn - strip.pixels[i].grn) / numSteps), + .blu = static_cast((newCol.blu - strip.pixels[i].blu) / numSteps) + }; + strip.pixels[i].red += colorDiff.red; + strip.pixels[i].grn += colorDiff.grn; + strip.pixels[i].blu += colorDiff.blu; + } + + strip.show(true); + if(ulTaskNotifyTake( pdTRUE, msFreq - (xTaskGetTickCount() - xLastWakeTime ) == pdTRUE )){ return; } // delay + } + stateMon.busy = false; +} + + +/******************************** HUE SPECTRUM MIRRORED *********************************/ + +EXIT_TYPE Animation_Hue_Spectrum_Mirrored(LEDSTRIP& strip, ANIMATION_EVENT& event, TickType_t duration){ + int msFreq = CalcEventInterval(event.speed, MIN_LED_UPDATE_INTERVAL, 100); + strip.powerDiv = (event.check4) ? 1 : 0; + Fill_Hue_Spectrum_Mirrored(strip, event.hue, event.hueRange); + + EXIT_TYPE ret = Animation_Loop( animStatus, msFreq, duration, std::function([&]() -> int { + strip.show(true); + strip.rotatePixels(DIR_FWD); + return EXIT_NORMAL; + })); + + return ret; +} + +void Fill_Hue_Spectrum(LEDSTRIP& strip, float hue, float range) +{ + HuePalletDispenser.Initialize(hue, range, strip.effSize); + for(int i = 0; i < strip.effSize; i++){ + strip.setPixel(i, HUEtoRGB( HuePalletDispenser.GetNextPalletHue() )); + } +} + +void Fill_Hue_Spectrum_Mirrored(LEDSTRIP& strip, float hue1, float hue2) +{ + HuePalletDispenser.Initialize(hue1, hue2, strip.effSize / 2.0); + + for(int i = 0; i < strip.effSize/2; i++){ + strip.setPixelMirrored(i, HUEtoRGB( HuePalletDispenser.GetNextPalletHue() )); + } +} + + +/******************************** PULSE COLOR CYCLING *********************************/ +//TODO Add Linearizing for smoother transitions +#define PWR_STEP 8 +EXIT_TYPE Animation_Pulse_Color_Cycling(LEDSTRIP& strip, ANIMATION_EVENT& event){ + int colorSteps = event.param1/10.0; + if(colorSteps < 3){ colorSteps = 2; } + + strip.powerDiv = (event.check4) ? 1 : 0; + int msFreq = CalcEventInterval(event.speed, MIN_LED_UPDATE_INTERVAL, 100); + + HuePalletDispenser.Initialize(event.hue, event.hueRange, colorSteps); + + int pwrStep = PWR_STEP; + int pwr = pwrStep; + + rgbpixel_t col = HUEtoRGB(HuePalletDispenser.GetNextPalletHue()); + + EXIT_TYPE ret = Animation_Loop( animStatus, msFreq, INFINITE_LOOP, std::function([&]() -> int { + // ramp up/down brightness + rgbpixel_t col_pwr = col; + scalePixel(col_pwr, (uint8_t)pwr); + linearizePixel(col_pwr); + strip.fill(col_pwr, 0, strip.effSize); + strip.show(true); + + pwr += pwrStep; + if(pwr >= (256-PWR_STEP) || pwr <= PWR_STEP){ + pwrStep = -pwrStep; // switch brightness direction + + // Change color + if(pwrStep > 0){ + col = HUEtoRGB(HuePalletDispenser.GetNextPalletHue()); + } + } + + return EXIT_NORMAL; + })); + + return ret; +} + + +/******************************** RAINBOW *********************************/ +// TODO Need more Red for rainboow +EXIT_TYPE Animation_Rainbow(LEDSTRIP& strip, ANIMATION_EVENT& event, TickType_t duration){ + int msFreq = CalcEventInterval(event.speed, MIN_LED_UPDATE_INTERVAL, 100); + double hueStep = 359.2 / (strip.effSize-1); + + strip.powerDiv = (event.check4) ? 1 : 0; + + for(int i = 0; i < strip.effSize; i++){ + strip.setPixel(i, HUEtoRGB((int)round(i*hueStep))); + } + + EXIT_TYPE ret = Animation_Loop( animStatus, msFreq, duration, std::function([&]() -> int { + strip.rotatePixels(DIR_FWD); + strip.show(true); + return EXIT_NORMAL; + })); + + return ret; +} + + +/******************************** FIRE ANIMATION *********************************/ +//TODO Get these fire params from json +#define FIRE_SPARK_PIXEL_PERCENT 15 +#define MIN_FIRE_COOLING 20 +#define MAX_FIRE_COOLING 100 +#define MIN_FIRE_SPARKING 50 +#define MAX_FIRE_SPARKING 200 +uint8_t *heat; +uint8_t sparkCount; + +EXIT_TYPE Animation_Fire(LEDSTRIP& strip, ANIMATION_EVENT& event){ + int msFreq = CalcEventInterval(event.speed, MIN_LED_UPDATE_INTERVAL, 100); + bool cycleColors = event.check1; + uint8_t sparking = map(event.param1, 0, 100, MIN_FIRE_SPARKING, MAX_FIRE_SPARKING); // input, input max, output min, out max + uint8_t cooling = map(event.param2, 0, 100, MIN_FIRE_COOLING, MAX_FIRE_COOLING); + + strip.powerDiv = (event.check4) ? 1 : 0; + + int duration = map(event.hueRange, 0, 360, 0, 60000 ); // upto 1 minute + if(cycleColors && duration == 0) { duration = 10000; } + if(!cycleColors && duration > 0) { duration = 0; } + + FIRE_COLOR fireColor = RED_FIRE; + + // Choose which Color comes closest + if(event.hue = -1 || event.hue == -2) { event.hue == 0;} + rgbpixel_t col = HUEtoRGB(event.hue); + if (col.red >= col.grn && col.red >= col.blu) { + fireColor = RED_FIRE; + } else if (col.grn >= col.red && col.grn >= col.blu) { + fireColor = GREEN_FIRE; + } else { + fireColor = BLUE_FIRE; + } + + EXIT_TYPE ret; + while(1){ + ret = Animation_Loop( animStatus, msFreq, duration, std::function([&]() -> int { + Fire_Update( strip, fireColor, cooling, sparking); + strip.show(true); + return EXIT_NORMAL; + })); + + if(ret == EXIT_TIMEOUT){ + fireColor = (FIRE_COLOR)(((int)fireColor + 1) % 3); + continue; + } + break; + } + + return ret; +} + +void LEDStrip_FireInit(LEDSTRIP& strip){ + sparkCount = (strip.effSize/2 * FIRE_SPARK_PIXEL_PERCENT) / 100; + heat = new uint8_t[strip.effSize/2]; + for(int i = 0; i < (strip.effSize/2); i++){ + heat[i] = 0; + } + + srand(70); +} + +void Fire_Update( LEDSTRIP& strip, FIRE_COLOR fire, int Cooling, int Sparking) { + int cooldown; + + // Step 1. Cool down every cell a little + for( int i = 0; i < (strip.effSize/2); i++) { + cooldown = rand() % ((Cooling * 10) / (strip.effSize/2) + 2); + + if(cooldown > heat[i]) { + heat[i] = 0; + }else { + heat[i] -= cooldown; + } + } + + // Step 2. Heat from each cell drifts 'up' and diffuses a little + // Starts from farthest and work down to the source + for( int k = (strip.effSize/2) - 1; k >= 2; k--) { + heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2]) / 3; + } + + // Step 3. Randomly ignite new 'sparks' near the bottom + if( (rand() % 255) < Sparking ) { + int y = (rand() % sparkCount); + heat[y] += 160 + (rand() % 95); + } + + // Step 4. Convert heat to LED colors + for( int j = 0; j < strip.effSize/2; j++) { + strip.setPixelMirrored(j, GetPixelHeatColor(fire, heat[j])); + } +} + +rgbpixel_t GetPixelHeatColor ( FIRE_COLOR fire, uint8_t temperature){ + rgbpixel_t led; + + // Scale 'heat' down from 0-255 to 0-191 + //uint8_t T = ((uint16_t)(temperature*191))>>8; + uint8_t T = (uint16_t)(temperature*120) >> 8; + + // calculate ramp up from (heat ramp) + int hr = T & 0x3F; // 0..63 + hr <<= 2; // scale up to 0..252 + + // Choose Fire Color + switch(fire){ + case RED_FIRE:{ + //if((hr + 32) & 255){ hr = 255; } + if((hr + 32) > 255){ hr = 255; } + // figure out which third of the spectrum we're in: + if( T > 117) { // hottest + led = (rgbpixel_t){ .red=120, .grn=120, .blu=(uint8_t)hr }; // spark color + } else if( T > 48 ) { // middle + led = (rgbpixel_t){ .red=255, .grn=(uint8_t)hr, .blu=0 }; // flame color + } else{ // coolest + led = (rgbpixel_t){ .red=(uint8_t)hr, .grn=0, .blu=0 }; // cooling color + } + break; + } + case BLUE_FIRE:{ + if( T > 117) { // hottest + led = (rgbpixel_t){ .red=120, .grn=(uint8_t)hr, .blu=120 }; + } else if( T > 48 ) { // middle + led = (rgbpixel_t){ .red=(uint8_t)hr, .grn=0, .blu=255 }; + } else { // coolest + led = (rgbpixel_t){ .red=0, .grn=0,.blu=(uint8_t)hr }; + } + break; + } + default:{ // GRN_FIRE + if( T > 117) { // hottest + led = (rgbpixel_t){ .red=(uint8_t)hr, .grn=120, .blu=120 }; + }else if ( T > 48 ) { // middle + led = (rgbpixel_t){ .red=0, .grn=255, .blu=(uint8_t)hr }; + }else { // coolest + led = (rgbpixel_t){ .red=0, .grn=(uint8_t)hr, .blu=0 }; + } + break; + } + } + + return led; +} + + + +/******************************** Serial Out Toggle *********************************/ +EXIT_TYPE Animation_Serial_Test2(LEDSTRIP& strip, int msFreq, const char* s1, const char* s2){ + //Animation_Loop( animStatus, msFreq, [&](){ + EXIT_TYPE ret = Animation_Loop( animStatus, msFreq, INFINITE_LOOP, std::function([&]() -> int { + static bool x = false; + + if(x){ + Serial.print("[ "); Serial.print( s1 ); Serial.print(" ]\r"); + }else{ + Serial.print("[ "); Serial.print( s2 ); Serial.print(" ]\r"); + } + x = !x; + + strip.show(true); + return EXIT_NORMAL; + })); + + return ret; +} + + +/******************************** *********************************/ + +void Animation_SparkleHue(LEDSTRIP& strip, ANIM_STATUS &stateMon, int msFreq, uint8_t hueRange, uint8_t hue, uint8_t sat, uint8_t val) +{ + stateMon.busy = true; + ulTaskNotifyTake( pdTRUE, 0); + + for(;;){ + TickType_t xLastWakeTime = xTaskGetTickCount(); + + //decay all leds + for(int i = 0; i < strip.effSize; i++){ + // strip.scale(strip.pixels[i], 0.5 * 0xFF); + } + + // Random Pixel + //int index = random(strip.effSize); + //uint16_t h = (random(hueRange * 2) - hueRange) % HUE_MAX; + //strip.setPixel(index, h, sat, val); + + strip.show(true); + if(ulTaskNotifyTake( pdTRUE, msFreq - (xTaskGetTickCount() - xLastWakeTime ) == pdTRUE )){ return; } // delay + } + + stateMon.busy = false; +} + +void Animation_SparkleColor(LEDSTRIP& strip, ANIM_STATUS &stateMon, int msFreq, rgbpixel_t col) +{ + stateMon.busy = true; + ulTaskNotifyTake( pdTRUE, 0); + + for(;;){ + TickType_t xLastWakeTime = xTaskGetTickCount(); + + //decay all leds + for(int i = 0; i < strip.effSize; i++){ + // strip.scale(strip.pixels[i], 0.5 * 0xFF); + } + + // Random Pixel + int index = random(strip.effSize); + strip.pixels[index] = col; + + strip.show(true); + if(ulTaskNotifyTake( pdTRUE, msFreq - (xTaskGetTickCount() - xLastWakeTime ) == pdTRUE )){ return; } // delay + } + + stateMon.busy = false; +} + + + +/******************************** test Functions *********************************/ +EXIT_TYPE Animation_SetPixel(LEDSTRIP& strip, ANIMATION_EVENT& event){ + return EXIT_NORMAL; +} + +EXIT_TYPE Animation_Clear(LEDSTRIP& strip, ANIMATION_EVENT& event){ + return EXIT_NORMAL; +} + + + + + + + + +/******************************** *********************************/ + +// This takes into account that the web color picker hue is reversed +float GetHueRangeWeb(float hue1, float hue2){ + float rng; + if (hue1 > hue2) { rng = hue1 - hue2; } + else { rng = 360.0 + hue1 - hue2; } + + if (rng < 0.0) { rng += 360.0; } + else if (rng > 360.0) { rng -= 360.0; } + + return rng; +} + +float GetHueRange(float hue1, float hue2){ + float range = hue2 - hue1; + if(hue2 < hue1){ range += 360.0; } + //Log.noticeln(" hue range: %d", hueRange); + return range; +} + + + +/******************************** SPEED CALC HELPER FUNCTIONS *********************************/ + +void Init_Speed_Range(float startSpeed, float endSpeed, int steps){ + +} + +float GetNextSpeed(void){ + return 0.0; +} + + + +int CalcEventInterval(float speed, int min, int max){ + int range = max - min; + return (max - ((speed/100.0) * range)); +} + +rgbpixel_t GetColorFromHue(int hue){ + if(hue == -1){ + return col_white; + }else if(hue == -2){ + return col_black; + }else if(hue >= 0 && hue <= 360){ + return HUEtoRGB(hue); + } + return col_black; +} + + + \ No newline at end of file diff --git a/temporary/Temp/led_animation.h b/temporary/Temp/led_animation.h new file mode 100644 index 0000000..21ed576 --- /dev/null +++ b/temporary/Temp/led_animation.h @@ -0,0 +1,205 @@ +#ifndef _LED_ANIMATION_H +#define _LED_ANIMATION_H + +#include "common/LEDStrip.h" +#include "led_strip.h" +#include "my_board.h" +#include +#include "common/HSVTable.h" + +#define HUE_MAX 767 + +enum EXIT_TYPE { EXIT_NORMAL, EXIT_FROM_NOTIFY, EXIT_FINISHED, EXIT_TIMEOUT }; + + +#define HUE_CALC_METHOD 0 +// TODO Fix hue range... 0-359 or 0-360; +#if HUE_CALC_METHOD == 0 + #define HUEtoRGB(x) trueHSV(x) +#elif HUE_CALC_METHOD == 1 + #define HUEtoRGB(x) sineHSV(x) +#elif HUE_CALC_METHOD == 2 + #define HUEtoRGB(x) pwrHSV(x) +#endif + +#define RGBtoHUE(x) RGBToHSV(x) + +#define INFINITE_LOOP 0 + + +enum DIR_TYPE { DT_REV, DT_FWD, DT_BOTH }; + +typedef struct { + int RampUpTime; + int msRampDownTime; + int msHoldTime; + int msFreq; + float minDuty; + float maxDuty; + int interval; + uint8_t pwr; +}params_whiteFill; + +typedef struct { + int hue; + int cooling; + int sparking; + int interval; + uint8_t pwr; +}params_fire_hue; + +typedef struct { + rgbpixel_t col1; + rgbpixel_t col2; + uint8_t range; /* hue range 0-127*/ + int density; + uint8_t step; /* range step inteval*/ + int interval; + uint8_t pwr; +}params_rain; + +#define ANIMTESTMODE_TIMEOUT (20 * 1000) // 20 secs + +typedef struct { + int EventTestCountdown = 0; + int EventsIndex; // upto 8 events based on anim-events.json + int EventsCount; // upto 8 events based on anim-events.json + int PopupAnimIndex; + bool busy; + bool repeat; + int countStatus; +}ANIM_STATUS; + +typedef struct { + bool busy; + int dslrCountStatus; +}WHITE_FILL_STATUS; + +extern ANIM_STATUS animStatus; +extern WHITE_FILL_STATUS whiteStatus; + +float calculateSteps(int msFreq, float LEDCount, int msFillTime); +void GetOptimizedStepInterval(float &steps, int &interval, int LEDCount, int msFillTime, int startInterval, float tolerance); + +float GetNextPalletHue(void); +void Init_Hue_Range_Pallet(float hue1, float hue2, int colSteps); + +float GetHueRange(float hue1, float hue2); +float GetHueRangeWeb(float hue1, float hue2); + +int CalcEventInterval(float speed, int min, int max); + +/******************************** ANIMATION LOOP HELPER ********************************* + * All Animations should use this lopp helper. + * Logic for monitoring exit notifications + * Timeout in mSec can be set to exit +**************************************************************************************/ +EXIT_TYPE Animation_Loop(ANIM_STATUS &stateMon, int msFreq, TickType_t duration, std::function callback=NULL); + +// min:0-100, max:0-100 +//int Const_White_Fill(WHITE_FILL_STATUS &status, int msRampUpTime, int msHoldTime, int msRampDownTime, int msFreq, float minDuty, float maxDuty); +int Const_White_Fill(WHITE_FILL_STATUS &status, FRONT_LIGHT light , float delayFactor, int countDown, int msFreq); +//int Const_White_Fill_HelioType(ANIM_STATUS &stateMon, float startPoint, int msRampUpTime, int msRampDownTime, int msHoldTime, int msFreq, float minDuty, float maxDuty); + +EXIT_TYPE Animation_White_Fill_Mirrored(LEDSTRIP& strip, ANIMATION_EVENT& event); + +EXIT_TYPE Animation_Linear_Brighten(LEDSTRIP& strip, ANIMATION_EVENT& event); +EXIT_TYPE Animation_Tick_Fill(LEDSTRIP& strip, ANIMATION_EVENT& event); + + +/******************************** COLORED SNAKES ********************************* + * cycles=0=infinite + * (check1) "mix"=false, all appearing sectors will be the same color until the next iteration + * "mix"=true, each meteor color will be different + * (check2) "roate", add rotation to animation + * (hue range) hue range (0-360) + * (param1) sectors + * (param2) colorCount, number of color divisions +**************************************************************************************/ +EXIT_TYPE Animation_Snakes(LEDSTRIP& strip, ANIMATION_EVENT& event, DIR_TYPE dir=DT_BOTH, int cycles=0); +void drawSnakeMix(LEDSTRIP& strip, int sectors, float hue1, float hue2, int colorCount); + + +/******************************** COLORED COMETS ********************************* + *(check1) "mix"=false, all appearing comets will be the same color until the next iteration + * (check2) "mix"=true, each comets color will be different + * (param1) hue range (0-360) + * (param2) colorCount, number of color divisions +**************************************************************************************/ +EXIT_TYPE Animation_Comets(LEDSTRIP& strip, ANIMATION_EVENT& event, DIR_TYPE dir=DT_BOTH, int cycles=0); + +void fadeToBlackBy(rgbpixel_t& pixel, uint8_t fadeAmount); +inline uint8_t nscale8(uint8_t i, uint8_t scale); + +/******************************** COLORED SECTORS ********************************* + * (check1) "mix"=false, all appearing sectors will be the same color until the next iteration + * (check2) "mix"=true, each meteor color will be different + * (param1) hue range (0-360) + * (param2) colorCount, number of color divisions +**************************************************************************************/ +EXIT_TYPE Animation_Sectors(LEDSTRIP& strip, ANIMATION_EVENT& event, DIR_TYPE dir=DT_BOTH, int cycles=0); + + +/******************************** COLORED DASHES ********************************* + * (check1) "mix"=false, all appearing dashes will be the same color until the next iteration + * (check2) "mix"=true, each dash color will be different + * (param1) hue range (0-360) + * (param2) colorCount, number of color divisions +**************************************************************************************/ +EXIT_TYPE Animation_Dashes(LEDSTRIP& strip, ANIMATION_EVENT& event, DIR_TYPE dirType=DT_BOTH, int cycles=0); + + +/******************************** HUE SPECTRUM ********************************* + * (check1) "mix"=false, all appearing dashes will be the same color until the next iteration + * (check2) "mix"=true, each dash color will be different + * (param1) hue range (0-360) + * (param2) colorCount, number of color divisions +**************************************************************************************/ +EXIT_TYPE Animation_Hue_Spectrum(LEDSTRIP& strip, ANIMATION_EVENT& event, TickType_t duration); +EXIT_TYPE Animation_Hue_Spectrum_Mirrored(LEDSTRIP& strip, ANIMATION_EVENT& event, TickType_t duration); +void Fill_Hue_Spectrum(LEDSTRIP& strip, float hue1, float hue2); +void Fill_Hue_Spectrum_Mirrored(LEDSTRIP& strip, float hue1, float hue2); + + +/************************ RAINBOW *********************/ +EXIT_TYPE Animation_Rainbow(LEDSTRIP& strip, ANIMATION_EVENT& event, TickType_t duration); + +/************************ PULSE COLOR *********************/ +EXIT_TYPE Animation_Pulse_Color_Cycling(LEDSTRIP& strip, ANIMATION_EVENT& event); + + +/**************** FIRE ****************/ +enum FIRE_COLOR { RED_FIRE, GREEN_FIRE, BLUE_FIRE}; +//void Animation_Fire(LEDSTRIP& strip, ANIMATION_EVENT& event, FIRE_COLOR fire, TickType_t duration); +EXIT_TYPE Animation_Fire(LEDSTRIP& strip, ANIMATION_EVENT& event); +void LEDStrip_FireInit(LEDSTRIP& strip); +void Fire_Update( LEDSTRIP& strip, FIRE_COLOR fire, int Cooling, int Sparking); +rgbpixel_t GetPixelHeatColor ( FIRE_COLOR fire, uint8_t temperature); + + + + +/************************ *********************/ +void Animation_Splash(LEDSTRIP& strip); +void Animation_SparkleColor(LEDSTRIP& strip, int msFreq); +void Animation_SparkleHue(LEDSTRIP& strip, ANIMATION_EVENT& event); + +EXIT_TYPE Animation_Serial_Test2(LEDSTRIP& strip, int msFreq, const char* s1, const char* s2); +// msTransTime(Duration), msFreq(update interval) +void Animation_TransTo_Color(LEDSTRIP& strip, ANIM_STATUS &stateMon, int msTransTime, int msFreq, rgbpixel_t col); + + +void drawSectorsHueSingle(LEDSTRIP& strip, rgbpixel_t col, float hue1, float hue2, int sectors) ; +void drawSectorsHueMix(LEDSTRIP& strip, float hue1, float hue2, int sectors, int colorCount); + + + +/************************ TEST ANIMATIONS *********************/ +EXIT_TYPE Animation_SetPixel(LEDSTRIP& strip, ANIMATION_EVENT& event); +EXIT_TYPE Animation_Clear(LEDSTRIP& strip, ANIMATION_EVENT& event); + + +rgbpixel_t GetColorFromHue(int hue); + + +#endif \ No newline at end of file diff --git a/temporary/Temp/led_strip.cpp b/temporary/Temp/led_strip.cpp new file mode 100644 index 0000000..328c197 --- /dev/null +++ b/temporary/Temp/led_strip.cpp @@ -0,0 +1,564 @@ +#include "led_strip.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include + +#include +#include +#include "common/led_animation.h" +#include "common/LEDStrip.h" +#include "global.h" +#include "common/neo_colors.h" +#include "common/HSVTable.h" +#include "JsonConstrain.h" +#include "my_board.h" +#include "luma_master.h" +#include "common/luma-stiks.h" + +#define STRIP1_PIN RGBLED1_Pin +#define STRIP2_PIN RGBLED2_Pin + +#define POWERUP_TIME 3000 + +static const char* tag = "led_strip"; + +ANIMATION_PROPS animProps; + +LEDSTRIP *strip1; +LEDSTRIP *strip2; +TaskHandle_t Strip1_Task_Handle; +TaskHandle_t Strip2_Task_Handle; +TaskHandle_t FrontLight_Task_Handle; + +QueueHandle_t eventQueue = xQueueCreate( 4, sizeof( ANIMATION_EVENT ) ); +QueueHandle_t whiteQueue = xQueueCreate( 4, sizeof( ANIMATION_EVENT ) ); + +ANIMATION_EVENT lastNormalEventMsg; +ANIMATION_EVENT eventMsg; + +LUMA_PACKET TempLumaPacket; + +int CurrentRunningEventIndex = -1; + + +void Init_LED_Devices(BOARD_PINS boardPins) +{ + ESP_LOGD(tag, "Strips Initialization entered..."); + File file = LittleFS.open("/cfg/led-devices.json"); + + if(!file){ + ESP_LOGE(tag,"Error opening led-devices.json!"); + file.close(); + }else{ + JsonDocument doc; + DeserializationError error = deserializeJson(doc, file); + file.close(); + if(error){ ESP_LOGE(tag, "led-devices.json deserialize error!.."); return;} + + // ********** STRIP1 ******************************** + JsonObject stripJson = doc["strip1"]; + if(jsonConstrainBool(tag, stripJson, "en", false)){ + int port = jsonConstrain(tag, stripJson, "i2s-ch", 0, 1, 0); + int size = jsonConstrain(tag, stripJson, "size", 1, 200, 10); + //int pin = jsonConstrainInt(stripJson, "pin", 0, 48, RGBLED1_Pin); + LED_ORDER order= getRGBOrder( jsonConstrainString(tag, stripJson, "rgb-order", "grb").c_str() ); + int shift = jsonConstrain(tag, stripJson, "shift", -size, size, 0); + int offset = jsonConstrain(tag, stripJson, "offset", -size, size, 0); + int core = jsonConstrain(tag, stripJson, "core", 0, 1, 0); + int powerDiv = jsonConstrain(tag, stripJson, "power-div", 0, 3, 0); + + strip1 = new LEDSTRIP(port, size, boardPins.rgb1, order, shift, offset); + strip1->setPowerDiv(powerDiv); + ESP_LOGI(tag,"Strip1 initialized."); + ESP_LOGD(tag," Size: %d, port: %d, shift: %d, offset: %d", size, port, shift, offset); + ESP_LOGD(tag," Strip1 Task Creating...Core: %d", core); + xTaskCreatePinnedToCore(Strip1_Control_Task, "LED_Strip1_Task", 1024*10, NULL, 1, &Strip1_Task_Handle, core); + } + + // ********** STRIP2 ******************************** + stripJson.clear(); + stripJson = doc["strip2"]; + if(jsonConstrainBool(tag, stripJson, "en", false)){ + int port = jsonConstrain(tag, stripJson, "i2s-ch", 0, 1, 0); + int size = jsonConstrain(tag, stripJson, "size", 1, 200, 10); + //int pin = jsonConstrainInt(stripJson, "pin", 0, 48, RGBLED2_Pin); + LED_ORDER order= getRGBOrder( jsonConstrainString(tag, stripJson, "rgb-order", "grb").c_str() ); + int shift = jsonConstrain(tag, stripJson, "shift", -size, size, 0); + int offset = jsonConstrain(tag, stripJson, "offset", -size, size, 0); + int core2 = jsonConstrain(tag, stripJson, "core", 0, 1, 0); + int powerDiv = jsonConstrain(tag, stripJson, "power-div", 0, 3, 0); + + strip2 = new LEDSTRIP(port, size, boardPins.rgb2, order, shift, offset); + strip2->setPowerDiv(powerDiv); + ESP_LOGI(tag,"Strip2 initialized."); + ESP_LOGD(tag," Size: %d, port: %d, shift: %d, offset: %d", size, port, shift, offset); + ESP_LOGD(tag,"Strip2 Task Creating...Core: %d", core2); + xTaskCreatePinnedToCore(Strip2_Control_Task, "LED_Strip2_Task", 1024*8, NULL, 1, &Strip2_Task_Handle, core2); + }else{ + ESP_LOGE(tag,"Strip2 disabled"); + } + + JsonObject flightJson = doc["front-light"]; + animProps.frontLight.enabled = jsonConstrainBool(tag, flightJson, "en", false); + if(animProps.frontLight.enabled){ + animProps.frontLight.relayIndex = jsonConstrain(tag, flightJson, "relay", 0, 3, 0); + animProps.frontLight.core = jsonConstrain(tag, flightJson, "core", 0, 1, 1); + animProps.frontLight.style = (FRONT_LIGHT_STYLE)(flightJson, "style", 0, 1, 0); + ESP_LOGI(tag, "Front Light initialized.."); + Init_FrontLight(); + } + + JsonObject rlightJson = doc["rear-light"]; + animProps.rearLight.enabled = jsonConstrainBool(tag, rlightJson, "en", false); + if(animProps.rearLight.enabled){ + animProps.rearLight.relayIndex = jsonConstrain(tag, rlightJson, "relay", 0, 3, 1); + animProps.rearLight.buttonIndex = jsonConstrain(tag, rlightJson, "button", 0, 2, 2); + animProps.rearLight.rampTime = jsonConstrain(tag, rlightJson, "ramp", 1000, 20000, 3000); + animProps.rearLight.rampSteps = jsonConstrain(tag, rlightJson, "steps", 2, 20, 8); + animProps.rearLight.min = jsonConstrain(tag, rlightJson, "min", 0.0f, 50.0f, 0.0f); + animProps.rearLight.max = jsonConstrain(tag, rlightJson, "max", animProps.rearLight.min, 100.0f, 100.0f); + ESP_LOGD(tag, "relay: %d, btn: %d, ramp: %d, steps: %d, min: %.2f, max: %.2f", animProps.rearLight.relayIndex, animProps.rearLight.buttonIndex, animProps.rearLight.rampTime, animProps.rearLight.rampSteps, animProps.rearLight.min, animProps.rearLight.max); + + Initialize_Rear_Control(animProps.rearLight.relayIndex, animProps.rearLight.buttonIndex, animProps.rearLight.rampTime, animProps.rearLight.rampSteps , animProps.rearLight.min, animProps.rearLight.max); + ESP_LOGI(tag, "Rear Light initialized.."); + } + + } + +} + +void Init_FrontLight(void) +{ + xTaskCreatePinnedToCore(FrontLight_Task, "FrontLight_Task", 5000, NULL, 1, &FrontLight_Task_Handle, animProps.frontLight.core); +} + +void Init_RearLight(void) +{ + //if(jsonOb.isNull()){ Serial.println(" rear_light json is Null.."); return; } +} + +void Test_Animations(void){ + ANIMATION_EVENT event; + + strip1->fill({0,0,0}, 0, strip1->effSize); + strip1->show(true); + vTaskDelay(100); + + event.hue = RGBtoHUE(col_blue); + event.hueRange = 180; + event.param1 = 11; + event.speed = 70; + + LEDStrip_FireInit(*strip1); + + while(1){ + event.hue = 0; + event.hueRange = 359; + event.param1 = 22; + event.speed = 70; + //Animation_Sectors(*strip1, event, true, DT_BOTH, true, 6, 2); + event.param1 = 30; + //Animation_Sectors(*strip1, event, true, DT_BOTH, true, 6, 2); + event.param1 = 40; + //Animation_Sectors(*strip1, event, true, DT_BOTH, true, 6, 2); + event.param1 = 50; + Animation_Sectors(*strip1, event, DT_BOTH, 2); + + event.param1 = 50; + Animation_Dashes(*strip1, event, DT_BOTH, 3); + + event.param1 = 50; + Animation_Dashes(*strip1, event, DT_BOTH, 3); + /* + Animation_Fire(*strip1, event, RED_FIRE, 5000); + Animation_Fire(*strip1, event, GREEN_FIRE, 5000); + Animation_Fire(*strip1, event, BLUE_FIRE, 5000); + + event.col1 = col_blue; + event.col2 = {255,136,0}; + event.density = 11; + event.speed = 100; + Animation_Snakes(*strip1, event, false, DT_FWD, 2, 1); + + event.col1 = HUEtoRGB(359); + event.col2 = HUEtoRGB(0); + event.density = 22; + event.speed = 70; + Animation_Snakes(*strip1, event, true, DT_BOTH, 12, 4); + + event.col1 = HUEtoRGB(359); + event.col2 = HUEtoRGB(0); + event.density = 56; + event.speed = 50; + Animation_Snakes(*strip1, event, true, DT_BOTH, 12, 4); + */ + } +} + +void Strip1_Control_Task(void *parameters) +{ + vTaskDelay(1000); // small start delay + ESP_LOGD(tag, "Strip1 Task Entered...."); + //Test_Animations(); + + LEDStrip_FireInit(*strip1); // must be initialized before calling fire animation + + // Set default event 1 animation base on profile values + ANIMATION_EVENT ev; // empty event + ev.animIndex = 0; + ev.countDown = 6000; + ev.param2 = 75; + ev.type = EV_NON_INJECT; + PostNewEvent(ev); // Start Animation (White Fill) + + animProps.event[1].type = EV_NORMAL; + PostNewEvent(animProps.event[1]); // default attraction animation + + while(true){ + xQueueReceive( eventQueue, &eventMsg, portMAX_DELAY ); + ESP_LOGD(tag, "Queue waiting: %d", uxQueueMessagesWaiting(eventQueue)); + + if(eventMsg.type == EV_NORMAL ) { // EV_NORMAL + ESP_LOGD(tag, "Running event: type: EV_NORMAL, event: %d, anim: %d", eventMsg.selfIndex, eventMsg.animIndex); + lastNormalEventMsg = eventMsg; //save last Normal EventMsg + if( eventMsg.selfIndex >= 0 ) { + CurrentRunningEventIndex = eventMsg.selfIndex; + } + + if(Luma_PeerCount() > 0){ + TempLumaPacket.type = LPT_ANIM; + memcpy(&TempLumaPacket.data, &eventMsg, sizeof(ANIMATION_EVENT)); + Luma_Master_Broadcast(TempLumaPacket); + } + + if( eventMsg.selfIndex == 0 ){ + RunWhitefillAnimation( eventMsg ); + }else{ + RunAnimation( eventMsg ); + } + } + else if( eventMsg.type == EV_INJECT ){ // insert and return to previous animation + ESP_LOGD(tag, "EV_INJECT"); + xQueueSend( eventQueue, &lastNormalEventMsg, 100 ); // requeue previous normal event + RunPopUpAnimations( eventMsg ); + } + else if( eventMsg.type == EV_NON_INJECT ){ + ESP_LOGD(tag, "EV_NON_INJECT"); + RunPopUpAnimations( eventMsg ); + } + else if( eventMsg.type == EV_TEST ){ + ESP_LOGD(tag, "EV_TEST"); + animStatus.EventTestCountdown = EVENT_TESTMODE_TIMEOUT; // start timeout countdown + // Should not re-post last event from here in case repeated tests are started. + // that would cause a backlog of the same previous event. + if( eventMsg.selfIndex == 0 ){ + RunWhitefillAnimation( eventMsg); + }else{ + RunAnimation( eventMsg ); + } + } + } +} + +void PostLastNormalEvent(){ + xQueueSend( eventQueue, &lastNormalEventMsg, 100 ); // requeue previous normal event + ESP_LOGD(tag, "RePosted Last Normal Event Index: %d", lastNormalEventMsg.selfIndex); +} + +// Used to cycle to the next event primarity when usinging buttons +int IncrementEventIndex(){ + int x = (CurrentRunningEventIndex + 1) % animStatus.EventsCount; + ESP_LOGD(tag, "Increment Event Index from %d to %d", CurrentRunningEventIndex, x); + + animProps.event[x].type = EV_NORMAL; + PostNewEvent(animProps.event[x]); + return animStatus.EventsIndex; +} + +//EVENT_MSG newEvent; +void PostNewEvent( ANIMATION_EVENT &animEvent) { + //newEvent.type = type; + //newEvent.event = animEvent; + xQueueSend(eventQueue, &animEvent, 100); // queue new event + + // if(type == EV_NORMAL){ + // If whitefill index ( could be restarted ) + // if(animEvent.animIndex == ANIM_WHITEFILL_INDEX){ + //if( countDown == 0 ) { animStatus.countDown = 1000; } // TODO Countdown calc maybe wrong...double check + //if( animStatus.busy ) { animStatus.repeat = true; } + // if( FrontLight_Task_Handle ) { xTaskNotifyGive( FrontLight_Task_Handle ); } + // } + // } + + if( Strip1_Task_Handle ){ xTaskNotifyGive( Strip1_Task_Handle ); } +} + +void Strip2_Control_Task(void *parameters) +{ + vTaskDelay(100); + ESP_LOGD(tag, "Strip2 Task Entered....\n"); + + for(;;){ + vTaskDelay(10000); + } +} + +void FrontLight_Task(void *parameters){ + animStatus.busy = false; + ANIMATION_EVENT whiteMsg; + for(;;){ + xQueueReceive( whiteQueue, &whiteMsg, portMAX_DELAY ); + + int msFillTime = 0; + if(whiteMsg.countDown > 0){ // use countdown if available + msFillTime = whiteMsg.countDown; + }else{ + msFillTime = map(whiteMsg.param1, 0, 100, 0, 10000); + if(msFillTime < 1000){ msFillTime = 1000; } + } + float delayFactor = (float)map(whiteMsg.param2, 0, 100, 0, 100) / 100.0; + Const_White_Fill(whiteStatus, animProps.frontLight, delayFactor, msFillTime, 50); + } +} + +void load_animation_profile(void){ + File file = LittleFS.open("/cfg/anim-profile-common.json"); + + if(!file){ + ESP_LOGE(tag, "Error opening anim-profile-common.json..."); + file.close(); + }else{ + JsonDocument doc; + DeserializationError error = deserializeJson(doc, file); + file.close(); + if(error){ ESP_LOGE(tag, "anim-profile-common.json deserialize error!.."); return;} + + JsonObject frontJson = doc["countdown"]; + animProps.frontLight.minDuty = jsonConstrain(tag, frontJson, "min", 0.0f, 99.0f, 20.0f); + animProps.frontLight.maxDuty = jsonConstrain(tag, frontJson, "max", 1.0f, 100.0f, 99.0f); + animProps.frontLight.holdTime = jsonConstrain(tag, frontJson, "hold", 100, 5000, 1000); + animProps.frontLight.rampTime = jsonConstrain(tag, frontJson, "ramp", 100, 5000, 500); + + animProps.profileIndex = jsonConstrain(tag, doc.as(), "profile-index", 0, 7, 0); + + ESP_LOGI(tag, "anim-profile-common.json settings loaded."); + } +} + +void load_animation_profileByIndex(int index){ + + //for(int index = 1; index <= 8; index++){ + String strFileName = "/cfg/anim-profile" + String(index + 1) + ".json"; + File file = LittleFS.open(strFileName); + + if(!file){ + ESP_LOGE(tag, "%s", "Error opening " + strFileName); + file.close(); + }else{ + JsonDocument doc; + DeserializationError error = deserializeJson(doc, file); + file.close(); + if(error){ ESP_LOGE(tag, "%s deserialize error!..", strFileName.c_str()); return;} + + JsonObject profJson = doc.as(); + animProps.profileName = jsonConstrainString(tag, profJson, "name", "").c_str(); + ESP_LOGD(tag, "Profile Index: %d, Name: %s", index, animProps.profileName); + + // + JsonArray eventsJson = profJson["events"]; + for(int i = 0; i < animStatus.EventsCount; i++){ + animProps.event[i].selfIndex = i; // Self Index + + if(i == 0){ + animProps.event[i].animIndex = jsonConstrain(tag, eventsJson[i], "anim", 0, animProps.whitefillsCount-1, 0); + }else{ + animProps.event[i].animIndex = jsonConstrain(tag, eventsJson[i], "anim", 0, animProps.animationsCount-1, 0); + } + + animProps.event[i].hue = jsonConstrain(tag, eventsJson[i], "hue", -2, 360, 0); + animProps.event[i].hueRange = jsonConstrain(tag, eventsJson[i], "hue-range", 0, 360, 1); + animProps.event[i].speed = jsonConstrain(tag, eventsJson[i], "speed", 0, 100, 50); + animProps.event[i].param1 = jsonConstrain(tag, eventsJson[i], "param1", 0, 100, 50); + animProps.event[i].param2 = jsonConstrain(tag, eventsJson[i], "param2", 0, 100, 50); + animProps.event[i].check1 = jsonConstrainBool(tag, eventsJson[i], "check1", false); + animProps.event[i].check2 = jsonConstrainBool(tag, eventsJson[i], "check2", false); + animProps.event[i].check3 = jsonConstrainBool(tag, eventsJson[i], "check3", false); + animProps.event[i].check4 = jsonConstrainBool(tag, eventsJson[i], "check4", false); + + ESP_LOGD(tag, " self: %d, anim: %d, param1: %d, speed: %d", animProps.event[i].selfIndex, animProps.event[i].animIndex, animProps.event[i].param1, animProps.event[i].speed); + } + ESP_LOGI(tag, "%s settings loaded.", strFileName.c_str()); + } + //} +} + +void RunWhitefillAnimation(ANIMATION_EVENT &event){ + //Start Constant Light Task if available + if( FrontLight_Task_Handle ){ + xQueueSend(whiteQueue, &event, 100); // queue new event + xTaskNotifyGive( FrontLight_Task_Handle ); + } + + int ret; + switch(event.animIndex){ + case 0: // Bottom/Up Fill + ESP_LOGD(tag, " Running WhiteFill: %d , bottom/up", event.animIndex); + ret = Animation_White_Fill_Mirrored(*strip1, event); + break; + case 1: // Snake Fill + ESP_LOGD(tag, " Running WhiteFill: %d , Snake fill", event.animIndex); + ret = Animation_White_Fill_Mirrored(*strip1, event); + break; + case 2: // Tick Fill + ESP_LOGD(tag, " Running WhiteFill: %d , Tick fill", event.animIndex); + ret = Animation_White_Fill_Mirrored(*strip1, event); + break; + case 3: // Smooth Brighten + ESP_LOGD(tag, " Running WhiteFill: %d , Smooth Brighten", event.animIndex); + ret = Animation_White_Fill_Mirrored(*strip1, event); + break; + case 4: // Cycle All + ESP_LOGD(tag, " Running WhiteFill: %d , Cycle All", event.animIndex); + ret = Animation_White_Fill_Mirrored(*strip1, event); + break; + default: // Random Select + ESP_LOGD(tag, " Running WhiteFill: default , bottom/up"); + ret = Animation_White_Fill_Mirrored(*strip1, event); + break; + } + + // if exited loop naturally... not from a notification so keep waiting + if(ret == EXIT_FINISHED || ret == EXIT_TIMEOUT){ + ESP_LOGD(tag, "Whitefill normal exit... waiting"); + ulTaskNotifyTake( pdTRUE, portMAX_DELAY ); + } +} + +void RunAnimation(ANIMATION_EVENT &event){ + if(event.animIndex < 80){ + switch(event.animIndex){ // AnimationEventIndex (0-X) + case 0: // Rainbow + ESP_LOGD(tag, " Running Anim: %d , Rainbow", event.animIndex); + Animation_Rainbow(*strip1, event, INFINITE_LOOP); + break; + case 1: // Hue Spectrum Mirrored + ESP_LOGD(tag, " Running Anim: %d , Hue Spectrum Mirrored", event.animIndex); + Animation_Hue_Spectrum_Mirrored(*strip1, event, INFINITE_LOOP); + break; + case 2: // Color Pulse Cycle + ESP_LOGD(tag, " Running Anim: %d , Color Pulse Cycle", event.animIndex); + Animation_Pulse_Color_Cycling(*strip1, event); + break; + case 3: // Comets Mixed Colors + ESP_LOGD(tag, " Running Anim: %d , Comets", event.animIndex); + Animation_Comets(*strip1, event, DT_BOTH, INFINITE_LOOP); + break; + case 4: // Dashes Single and Mixed Colors + ESP_LOGD(tag, " Running Anim: %d , Dashes", event.animIndex); + Animation_Dashes(*strip1, event, DT_BOTH, INFINITE_LOOP); + break; + case 5: // Snakes Single and Mixed Colors + ESP_LOGD(tag, " Running Anim: %d , Snakes", event.animIndex); + Animation_Snakes(*strip1, event, DT_BOTH, INFINITE_LOOP); + break; + case 6: // Sectors Mixed Colors (No Sigle Colors because pointless) + ESP_LOGD(tag, " Running Anim: %d , Sectors", event.animIndex); + Animation_Sectors(*strip1, event, DT_BOTH, INFINITE_LOOP); + break; + case 7: // Fire (Red) + ESP_LOGD(tag, " Running Anim: %d , Fire", event.animIndex); + Animation_Fire(*strip1, event); + break; + case 8: // + break; + case 9: // Color Strobing + break; + case 10: // Random Color Twinkle + break; + case 11: // Stacking + break; + case 12: // Rain + break; + case 13: // Stacking + break; + case 14: // RED/WHITE/BLU Flag (density selects color pallet) (sector/comets,dashes, snakes) + break; + case 15: + break; + default: + break; + } + }else{ // Non Looping Functions + switch(event.animIndex){ // AnimationEventIndex (0-X) + case 80: // clear strip + strip1->fill(col_black, 0, strip1->effSize); + strip1->show(true); + ESP_LOGD(tag, "Animation Strip1 Clear"); + ulTaskNotifyTake( pdTRUE, portMAX_DELAY ); + break; + case 81: // set pixel + strip1->setPixel(event.param1, GetColorFromHue(event.hue)); + strip1->show(true); + ESP_LOGD(tag, "Animation Set Pixel: %d", event.param1); + ulTaskNotifyTake( pdTRUE, portMAX_DELAY ); + break; + case 82: // + break; + default: + break; + } + } + +} + +// These animations should be quick and not infinite +void RunPopUpAnimations(ANIMATION_EVENT &event){ + + switch(event.animIndex){ // AnimationEventIndex (0-X) + case 0: // Start/Boot/Power Up Sequence + ANIMATION_EVENT ev; + ev.animIndex = 0; + ev.hue = -1; //{200, 200, 200}; //col_white; + ev.hueRange = 1; //col_black; + ev.countDown = 6000; // 6 seconds + ev.param2 = 75; + if( FrontLight_Task_Handle ){ + xQueueSend(whiteQueue, &ev, 100); // queue new event + xTaskNotifyGive( FrontLight_Task_Handle ); + } + Animation_White_Fill_Mirrored(*strip1, ev); + + break; + case 1: // BLE Connected + break; + case 2: // BLE Disconnected + break; + case 3: // WiFi Connected + break; + case 4: // WiFi Disconnected + break; + case 5: // Luma Stick Connected + break; + default: + break; + } + +} + + + +// json color to rgbPixel conversion +// Unused now +rgbpixel_t hexToRGB(const char* hexColor) { + long hexValue = strtol(hexColor + 1, nullptr, 16); // Skip the '#' character + rgbpixel_t pix; + pix.red = (hexValue >> 16) & 0xFF; + pix.grn = (hexValue >> 8) & 0xFF; + pix.blu = hexValue & 0xFF; + return pix; +} + + + + diff --git a/temporary/Temp/led_strip.h b/temporary/Temp/led_strip.h new file mode 100644 index 0000000..3f363fa --- /dev/null +++ b/temporary/Temp/led_strip.h @@ -0,0 +1,128 @@ +#ifndef _LED_STRIP_H +#define _LED_STRIP_H + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "common/LEDStrip.h" +#include +#include "my_board.h" + +extern LEDSTRIP *strip1; +extern LEDSTRIP *strip2; +extern TaskHandle_t Strip1_Task_Handle; +extern TaskHandle_t Strip2_Task_Handle; +extern TaskHandle_t FrontLight_Task_Handle; + +#define ANIM_WHITEFILL_INDEX 0 +#define ANIM_DEFAULT_INDEX 1 +#define ANIM_EVENT_SIZE 12 +#define PROFILES_SIZE 8 + +#define MIN_LED_UPDATE_INTERVAL 13 + +enum OtherANIMS { OA_SET_PIXEL=100, OA_CLEAR, }; + +enum FRONT_LIGHT_STYLE { FRONT_LIKE_LUMIA, FRONT_LIKE_ROAMER }; + +enum EVENT_TYPE { EV_NORMAL, EV_INJECT, EV_NON_INJECT, EV_TEST,}; + +// Single web page event (upto 8) +typedef struct{ + EVENT_TYPE type; + int selfIndex; + int animIndex; + int hue; + int hueRange; + int speed; + int param1; + int param2; + bool check1; + bool check2; + bool check3; + bool check4; + int countDown; +}ANIMATION_EVENT; + +typedef struct{ + bool enabled; + uint8_t relayIndex; + uint8_t core; + int holdTime; + int rampTime; + float minDuty; + float maxDuty; + FRONT_LIGHT_STYLE style; + //float delayFactor; +}FRONT_LIGHT; + +typedef struct{ + bool enabled; + uint8_t relayIndex; + uint8_t buttonIndex; + uint8_t core; + int rampTime; + uint8_t rampSteps; + float min; + float max; +}REAR_LIGHT; + + +typedef struct { + EVENT_TYPE type; + ANIMATION_EVENT event; +}EVENT_MSG; + +// Properties from webpage +typedef struct { + int profileIndex; + const char* profileName; + int whitefillsCount; + int animationsCount; + FRONT_LIGHT frontLight; + REAR_LIGHT rearLight; + ANIMATION_EVENT event[ANIM_EVENT_SIZE]; +}ANIMATION_PROPS; + +extern ANIMATION_PROPS animProps; + +enum POPUP_ANIM { POP_STATUP, POP_BLE_CONN, POP_BLE_DISC, POP_WIFI_CONN, POP_WIFI_DISC, POP_STICK_CONN, POP_STICK_DISC }; + +void RunWhitefillAnimation(ANIMATION_EVENT &event); + +void RunAnimation(ANIMATION_EVENT &event); + +void RunPopUpAnimations(ANIMATION_EVENT &event); + +//void SetEventIndex(int index, int countdown=0, bool test=false); + +//void PostNewEvent( int countDown, EVENT_TYPE type, ANIMATION_EVENT &animEvent); +void PostNewEvent( ANIMATION_EVENT &animEvent); + +int IncrementEventIndex(void); + +void PostLastNormalEvent(void); + +void load_animation_profile(void); // cfg/anim-profiles.json +void load_animation_profileByIndex(int); // cfg/anim-profileX.json + +void Init_LED_Devices(BOARD_PINS); // cfg/led-devices.json + +rgbpixel_t hexToRGB(const char* hexColor); + +//void Init_LEDStrip2(void); + +void Init_FrontLight(void); + +//void Init_RearLight(void); + +void Strip1_Control_Task(void *parameters); + +void Strip2_Control_Task(void *parameters); + +void FrontLight_Task(void *parameters); + + + + + +#endif diff --git a/temporary/Temp/luma_master.cpp b/temporary/Temp/luma_master.cpp new file mode 100644 index 0000000..7ec642a --- /dev/null +++ b/temporary/Temp/luma_master.cpp @@ -0,0 +1,111 @@ +#include "luma_master.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include +#include +#include "JsonConstrain.h" +#include +#include +#include "global.h" + +static const char* tag = "lumaM"; +uint8_t broadcastAddress[] = {0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF}; +uint8_t availableStiks = 0; +LUMA_NODE lumaStation[LUMA_NODE_MAX_COUNT]; +bool LUMASTIKS_READY = false; + +TaskHandle_t LumaMaster_Task_Handle; + +bool SendRegistrationPacket = false; +LUMA_PACKET RegistrationPacket; +esp_now_peer_info_t TempNode; + + + +QueueHandle_t lumaQueue = xQueueCreate( 4, sizeof( LUMA_PACKET ) ); + +void Init_Luma_Master(void){ + + if (esp_now_init() != ESP_OK) { + ESP_LOGD(tag, "Error initializing ESP-NOW\n"); + return; + } + + xTaskCreatePinnedToCore(LumaMaster_Task, "LumaMaster_Task", 1024*6, NULL, 1, &LumaMaster_Task_Handle, CONFIG_ARDUINO_RUNNING_CORE); + ESP_LOGV(tag, "Initialized LumaMaster task created..."); + + esp_now_register_recv_cb(Luma_Master_Data_Received); + esp_now_register_send_cb(Luma_Master_Data_Sent); +} + +void LumaMaster_Task(void *parameters){ + + while(1){ + vTaskSuspend(NULL); + + if(SendRegistrationPacket){ + Luma_Send_Packet(TempNode.peer_addr, RegistrationPacket); + } + } + + + LUMA_PACKET lumaPacket; + + while(1){ + xQueueReceive( lumaQueue, &lumaPacket, portMAX_DELAY ); + + switch(lumaPacket.type){ + case LPT_RAW: + break; + case LPT_REG: + Luma_Send_Packet(TempNode.peer_addr, lumaPacket); + break; + case LPT_ANIM: + Luma_Master_Broadcast(lumaPacket); + break; + default: + break; + } + } +} + +void Luma_Master_Data_Sent(const uint8_t *mac_addr, esp_now_send_status_t status){ + +} + +void Luma_Master_Data_Received(const uint8_t *mac, const uint8_t *data, int len){ + + LUMA_PACKET *packet = (LUMA_PACKET*)data; + + printf("** Data Received **\n\n"); + printf("Received from MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + printf("Length: %d byte(s)\n", len); + printf("Data: %d\n\n", data[0]); + + switch(packet->type){ + case LPT_RAW: + break; + case LPT_REG: + LUMA_REGISTER_PACKET *reg = (LUMA_REGISTER_PACKET*)&packet->data; + ESP_LOGD(tag, "Node SSID: %s", reg->ssid); + + // Register + memcpy(TempNode.peer_addr, mac, 6); + TempNode.channel = WiFi.channel(); + Luma_Add_Peer(TempNode); + + // Notify Peer + RegistrationPacket.type = LPT_REG; + xQueueSend( lumaQueue, &RegistrationPacket, 100 ); + break; + //default: + // break; + } +} + +void Luma_Master_Broadcast(LUMA_PACKET packet){ + +} + + diff --git a/temporary/Temp/luma_master.h b/temporary/Temp/luma_master.h new file mode 100644 index 0000000..5808f26 --- /dev/null +++ b/temporary/Temp/luma_master.h @@ -0,0 +1,24 @@ +#ifndef _LUMA_MASTER_H +#define _LUMA_MASTER_H + +#include +#include +#include "common/luma-stiks.h" + + +extern bool LUMASTIKS_READY; + +#define LUMA_NODE_MAX_COUNT 16 +//extern LUMA_NODE lumaStation[]; + +void LumaMaster_Task(void *parameters); + +void Init_Luma_Master(void); + +void Luma_Master_Data_Received(const uint8_t *mac, const uint8_t *data, int len); + +void Luma_Master_Data_Sent(const uint8_t *mac_addr, esp_now_send_status_t status); + +void Luma_Master_Broadcast(LUMA_PACKET packet); + +#endif \ No newline at end of file diff --git a/temporary/Temp/my_wifi.cpp b/temporary/Temp/my_wifi.cpp new file mode 100644 index 0000000..12ec314 --- /dev/null +++ b/temporary/Temp/my_wifi.cpp @@ -0,0 +1,1451 @@ + +/* + wifi is started in STATION mode and tries to connected to AP saved + in the creds.json file. It will keep trying to connect forever. If + it connects then the board LEDS will toggle, the buzzer will play a tune + and the IP address will be sent to the serial port. Also the device will + be discoverable as "http://atadev.local/" + + If credentials need to be changed then double reset can be done to + trigger the AP mode and a wifi config page will be available on address + 192.168.4.1. Credentials can be entered and applied then you can reset + the device so it connects with the correct credentials. + +*/ +#include "my_wifi.h" + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "global.h" +#include +#include "my_buzzer.h" +#include "my_board.h" +#include "common/led_animation.h" +#include "led_strip.h" +#include "common/fileSystem.h" +#include "JsonConstrain.h" +#include "led_strip.h" +#include "my_oled.h" +#include "BTSerial.h" +#include "esp_log.h" + +#include "firmware_html.h" + +int RebootSystem = 0; +#define WIFI_TIMEOUT_MS 10000 + +static const char* tag = "wifi"; + +bool WifiClientConnected = false; +AsyncWebServer webServer(80); +DNSServer *dnsServer; +#define DNS_PORT 53 + +#define StartDelayedRebooth RebootSystem = 1000/BUTTON_UPDATE_PERIOD; // start reboot countdown for 1sec + +String ssid; +String passPhrase; + +String AP_SSID; +String AP_Pass; +String mDnsName; +String HostName; + +IPAddress local_IP(192,168,10,1); +IPAddress gateway(192,168,10,200); +IPAddress subnet(255,255,255,0); + +// for file manager page +String filesDropdownOptions((char*)0); +String dirDropdownOptions((char*)0); +String savePath((char*)0); // needed for storing file when editing a file +String savePathInput((char*)0); +const char* http_username = "admin"; +const char* http_password = "admin"; +const char* param_delete_path = "delete-path"; +const char* param_edit_path = "edit-path"; +const char* param_dir_pad = "dir-path"; +const char* param_edit_textarea = "edit-textarea"; +const char* param_save_path = "save-path"; +String allowedExtensionsForEdit = "txt, h, htm, html, css, cpp, js, json, ini, cfg"; +//const char* jquery = "/jquery-3.6.3.min.js"; + +void Init_Wifi_Task(void) +{ + File file = LittleFS.open("/cfg/wifi.json", "r"); + if(!file){ + ESP_LOGE(tag, "Failed to open wifi.json!"); + file.close(); + return; + } + + // Parse the JSON file + JsonDocument doc; + DeserializationError error = deserializeJson(doc, file); + if(!error){ + JsonObject wifiJson = doc["wifi"]; + file.close(); + mDnsName = jsonConstrainString(tag, wifiJson,"mdns-name", "atadev"); + ESP_LOGD(tag, "mDnsName: %s", mDnsName.c_str()); + + if(jsonConstrainBool(tag, wifiJson, "en", false)){ + JsonObject j = doc["cred1"]; + Wifi_Read_Credentials( j ); + xTaskCreatePinnedToCore(Wifi_Task, "Wifi_Task", 1024*10, NULL, 1, NULL, CONFIG_ARDUINO_RUNNING_CORE); + ESP_LOGD(tag, "Initialized WiFi (task created)..."); + } + + JsonObject ap = doc["ap"]; + AP_SSID = jsonConstrainString(tag, ap, "ssid", "ATA_AP"); + if(jsonConstrainBool(tag, ap, "append-id", true)){ + String macHex = String(chipInfo.macByte[1], HEX) + String(chipInfo.macByte[0], HEX); + AP_SSID += "_"; + macHex.toUpperCase(); + AP_SSID += macHex; + } + AP_Pass = jsonConstrainString(tag, ap, "pass", "12345678"); + }else{ + ESP_LOGE(tag, "Deserialization error for wifi.json"); + } +} + +void onWiFiEvent(WiFiEvent_t event) +{ + //Serial.printf("[WiFi-event] event: %d\n", event); + switch (event) { + case ARDUINO_EVENT_WIFI_READY: + //Serial.println("WiFi interface ready"); + break; + case ARDUINO_EVENT_WIFI_SCAN_DONE: + //Serial.println("Completed scan for access points"); + break; + case ARDUINO_EVENT_WIFI_STA_START: + //Serial.println("WiFi client started"); + break; + case ARDUINO_EVENT_WIFI_STA_STOP: + //Serial.println("WiFi clients stopped"); + break; + case ARDUINO_EVENT_WIFI_STA_CONNECTED: + WifiClientConnected = true; + //Serial.println("Connected to access point"); + break; + case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: + WifiClientConnected = false; + ESP_LOGI(tag,"WiFi Disconnected"); + //Buzzer_Play_Tune(TUNE_WIFI_DISCONNECTED); + break; + case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE: + //Serial.println("Authentication mode of access point has changed"); + break; + case ARDUINO_EVENT_WIFI_STA_GOT_IP: + ESP_LOGI(tag,"My IP: %s", WiFi.localIP().toString()); + Wifi_Start_MDNS(); + Buzzer_Play_Tune(TUNE_WIFI_CONNECTED); + break; + case ARDUINO_EVENT_WIFI_STA_LOST_IP: + //Serial.println("Lost IP address and IP address is reset to 0"); + break; + case ARDUINO_EVENT_WPS_ER_SUCCESS: + //Serial.println("WiFi Protected Setup (WPS): succeeded in enrollee mode"); + break; + case ARDUINO_EVENT_WPS_ER_FAILED: + //Serial.println("WiFi Protected Setup (WPS): failed in enrollee mode"); + break; + case ARDUINO_EVENT_WPS_ER_TIMEOUT: + // Serial.println("WiFi Protected Setup (WPS): timeout in enrollee mode"); + break; + case ARDUINO_EVENT_WPS_ER_PIN: + //Serial.println("WiFi Protected Setup (WPS): pin code in enrollee mode"); + break; + case ARDUINO_EVENT_WIFI_AP_START: + //Serial.println("WiFi access point started"); + break; + case ARDUINO_EVENT_WIFI_AP_STOP: + //Serial.println("WiFi access point stopped"); + break; + case ARDUINO_EVENT_WIFI_AP_STACONNECTED: + //Serial.println("Client connected"); + break; + case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED: + ESP_LOGI(tag,"SoftAP Client Disconnected"); + //Buzzer_Play_Tune(TUNE_WIFI_DISCONNECTED); + break; + case ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED: + ESP_LOGI(tag,"SoftAP Client Connected"); + Buzzer_Play_Tune(TUNE_WIFI_CONNECTED); + break; + case ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED: + //Serial.println("Received probe request"); + break; + case ARDUINO_EVENT_WIFI_AP_GOT_IP6: + //Serial.println("AP IPv6 is preferred"); + break; + case ARDUINO_EVENT_WIFI_STA_GOT_IP6: + //Serial.println("STA IPv6 is preferred"); + break; + case ARDUINO_EVENT_ETH_GOT_IP6: + //Serial.println("Ethernet IPv6 is preferred"); + break; + case ARDUINO_EVENT_ETH_START: + //Serial.println("Ethernet started"); + break; + case ARDUINO_EVENT_ETH_STOP: + //Serial.println("Ethernet stopped"); + break; + case ARDUINO_EVENT_ETH_CONNECTED: + //Serial.println("Ethernet connected"); + break; + case ARDUINO_EVENT_ETH_DISCONNECTED: + //Serial.println("Ethernet disconnected"); + break; + case ARDUINO_EVENT_ETH_GOT_IP: + // Serial.println("Obtained IP address"); + break; + default: break; + } +} + +void Wifi_Task(void *parameters) +{ + // Extend watchdog timer + esp_task_wdt_init(2, false); + + Setup_WebServer_Handlers(&webServer); + WiFi.onEvent(onWiFiEvent); + WiFi.setHostname(mDnsName.c_str()); + WiFi.softAPConfig(local_IP, gateway, subnet); + WiFi.softAP(AP_SSID.c_str(), AP_Pass.c_str()); + Wifi_Start_MDNS(); + vTaskDelay(100); + webServer.begin(); + + // Choose WIFI Mode + if(commMode == COMM_WIFI_AP_BLE){ // AP Only + WiFi.mode(WIFI_AP); + while(1){ vTaskDelay(500 / portTICK_PERIOD_MS); } + }else{ // AP & Client (dslrbooth) + esp_wifi_set_ps(WIFI_PS_NONE); + WiFi.mode(WIFI_AP_STA); + Wifi_Client_Loop(); + } +} + +void Wifi_Client_Loop(){ + while(true){ + // Wifi status check loop + if(WiFi.status() == WL_CONNECTED){ + vTaskDelay(250); + continue; + } + + // Start WiFi Client + ESP_LOGD(tag, "Wifi trying stored creds: %s..%s",ssid.c_str(), passPhrase.c_str()); + WiFi.begin(ssid.c_str(), passPhrase.c_str()); + + //Wait until connected or timeout + ESP_LOGV(tag, "WiFi Connecting..."); + unsigned long startAttemptTime = millis(); + while(WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < WIFI_TIMEOUT_MS){ + vTaskDelay(100 / portTICK_PERIOD_MS); + } + + // Delay if wifi connection fails and continue + if(WiFi.status() != WL_CONNECTED){ + ESP_LOGW(tag, "WIFI Connection Failed!"); + for(int i = 0; i < WIFI_TIMEOUT_MS/100; i++){ + vTaskDelay(100); + } + continue; + } + } +} + +void Wifi_Start_MDNS(void) +{ + ESP_LOGV(tag, "Initializing MDNS: %s", mDnsName.c_str()); + if (!MDNS.begin(mDnsName.c_str())) { + ESP_LOGE(tag, "Error setting up MDNS responder!"); + }else{ + ESP_LOGV(tag, "You can access device via http://%s.local", mDnsName); + } +} + +void Wifi_Read_Credentials(const JsonObject &credJson){ + if(credJson.isNull()){ // set generic defaults on error + ESP_LOGE(tag, "Failed to find wifi key"); + + ssid = "Generic"; + passPhrase = "password"; + //strncpy(ssid, "Generic", sizeof(ssid)-1); + //strncpy(passPhrase, "password", sizeof(passPhrase)-1); + return; + } + else{ + // TODO Restore String + ssid = jsonConstrainString(tag, credJson, "ssid", "default"); + passPhrase = jsonConstrainString(tag, credJson, "pass", "password"); + //strncpy(ssid, jsonConstrainString(tag, credJson, "ssid", "default").c_str(), sizeof(ssid)-1); + //strncpy(passPhrase, jsonConstrainString(tag, credJson, "pass", "password").c_str(), sizeof(passPhrase)-1); + } +} + +void Wifi_Save_Credentials(String newSSID, String newPass) +{ + // Open the credentials file writing + File credsFile = LittleFS.open("/cfg/wifi.json", "r+"); + if(!credsFile){ + ESP_LOGE(tag, "Failed to open wifi.json\n"); + return; + } + + // Parse the JSON file + JsonDocument doc; + DeserializationError error = deserializeJson(doc, credsFile); + if(!error){ + JsonObject cred1Json = doc["cred1"].to(); + + if(cred1Json){ + cred1Json["ssid"] = newSSID; + cred1Json["pass"] = newPass; + + // Clear file contents before saving the modified JSON + credsFile.seek(0); + + int written = serializeJson(doc, credsFile); // save to file + ESP_LOGD(tag, "Credentials saved... ssid: %s, pass: %s, size written: %d", newSSID, newPass, written); + } + credsFile.close(); + }else{ + ESP_LOGE(tag, "Deserialization error for wifi.json"); + } +} + +void Setup_WebServer_Handlers(AsyncWebServer* serv) +{ + serv->on("/dslrbooth", HTTP_GET, handleGET_DSLRBooth); + + serv->on("/", HTTP_GET, [](AsyncWebServerRequest *request){ + if(WiFi.getMode() == WIFI_AP && !WifiClientConnected){ + request->redirect("/wifi"); + }else{ + request->redirect("/home"); + } + }); + serv->on("/home", HTTP_GET, [](AsyncWebServerRequest *request){ + sendHtmlFile("/www/home.html", request, HomeHtmlProcessor); + }); + serv->on("/lights", HTTP_GET, [](AsyncWebServerRequest *request){ + sendHtmlFile("/www/lights.html", request, htmlProcessor); + }); + serv->on("/setup", HTTP_GET, [](AsyncWebServerRequest *request){ + if(!request->authenticate(http_username, http_password)){ + return request->requestAuthentication(); + } + sendHtmlFile("/www/setup.html", request, htmlProcessor); + }); + serv->on("/wifi", HTTP_GET, [](AsyncWebServerRequest *request){ + if(WiFi.getMode() == WIFI_MODE_APSTA){ + // TODO Disable navigation bar + } + sendHtmlFile("/www/wifi.html", request, htmlProcessor); + }); + serv->on("/files", HTTP_GET, [](AsyncWebServerRequest *request){ + if(!request->authenticate(http_username, http_password)){ + return request->requestAuthentication(); + } + sendHtmlFile("/www/files.html", request, htmlProcessor); + }); + serv->on("/upload", HTTP_POST, [](AsyncWebServerRequest *request) { request->send(200); }, handlePOST_Upload); + + serv->on("/download", HTTP_GET, [](AsyncWebServerRequest *request){ + if (request->hasParam("file")) { + String filename = request->getParam("file")->value(); + if (LittleFS.exists(filename)) { + fs::File file = LittleFS.open(filename, "r"); + if (file) { + request->send(LittleFS, filename, "application/octet-stream"); + file.close(); + return; + } + } + } + request->send(404, "text/plain", "File not found!"); + }); + serv->on("/delete", HTTP_GET, [](AsyncWebServerRequest *request){ + if(!request->authenticate(http_username, http_password)){ + return request->requestAuthentication(); + } + + String filename = request->getParam(param_delete_path)->value(); + if(filename !="choose"){ + LittleFS.remove(filename.c_str()); + ESP_LOGD(tag, "Deleted file: %s", filename.c_str()); + } + + request->redirect("/files"); + }); + serv->on("/edit", HTTP_GET, [](AsyncWebServerRequest *request){ + if(!request->authenticate(http_username, http_password)){ + return request->requestAuthentication(); + } + + String fileName = request->getParam(param_edit_path)->value(); + if(fileName =="new"){ + savePath = "/new.txt"; + } + else{ + savePath = fileName; + } + + ESP_LOGD(tag, "Edit file: %s", savePath.c_str()); + sendHtmlFile("/www/edit.html", request, htmlProcessor); + }); + serv->on("/save", HTTP_GET, [](AsyncWebServerRequest *request){ + if(!request->authenticate(http_username, http_password)){ + return request->requestAuthentication(); + } + String inputMessage((char*)0); + if (request->hasParam(param_edit_textarea)) { + inputMessage = request->getParam(param_edit_textarea)->value(); + } + if (request->hasParam(param_save_path)) { + savePath = request->getParam(param_save_path)->value(); + } + writeFile(LittleFS, savePath.c_str(), inputMessage.c_str()); + + request->redirect("/files"); + }); + serv->on("/update", HTTP_POST, handlePOST_Update, updateCallback); + + // Request Data + serv->on("/get", HTTP_GET, handleGET_Get); + // Post Data + serv->on("/post", HTTP_POST, handlePOST_Post, postFileUpload, postBody); + + serv->on("/generate_204", HTTP_GET, [](AsyncWebServerRequest *request){ + ESP_LOGD(tag, "/generate_204 ... redirect to /wifi"); + request->redirect("/wifi"); + }); + + serv->on("/hotspot-detect.html", HTTP_GET, [](AsyncWebServerRequest *request){ + ESP_LOGD(tag, "/hotspot-detect.html ... redirect to /wifi"); + request->redirect("/wifi"); + }); + + serv->on("/msftconnecttest.txt", HTTP_GET, [](AsyncWebServerRequest *request){ + ESP_LOGD(tag, "/msftconnecttest.txt ... redirect to /wifi"); + request->redirect("/wifi"); + }); + + serv->on("/reg-stick", HTTP_POST, handlePOST_RegStick); + + serv->on("/firmware", HTTP_GET, [](AsyncWebServerRequest *request){ + request->send_P(200, "text/html", firmware_html_page); + }); + + serv->onNotFound([](AsyncWebServerRequest *request){ + if(WiFi.getMode() == WIFI_MODE_APSTA){ + ESP_LOGD(tag, "OnNotFound Redirect to /wifi"); + request->redirect("/wifi"); + }else if(WiFi.getMode() == WIFI_MODE_AP){ + ESP_LOGD(tag, "OnNotFound Redirect to /home"); + request->redirect("/home"); + }else{ + request->send(404); + } + }); + + serv->on("/*", HTTP_GET, handleGET_SendFile); // send any file + +} + +void handlePOST_RegStick(AsyncWebServerRequest *request){ + +} + +bool getpostSuccess = false; +// +void handleGET_Get(AsyncWebServerRequest *request){ + if(request->hasParam("type", false, false)) { + const char* dataType = request->getParam("type", false, false)->value().c_str(); + + if(!strcmp(dataType, "app-events")){ // send json file + request->send(LittleFS, "/cfg/app-events.json", "application/json"); + return; + } + + if(!strcmp(dataType, "anim-profiles")){ // send json file + request->send(LittleFS, "/cfg/anim-profiles.json", "application/json"); + return; + } + + if(!strcmp(dataType, "anim-list")){ + request->send(LittleFS, "/cfg/anim-list.json", "application/json"); + return; + } + + if(!strcmp(dataType, "sys-summary")){ + JsonDocument doc; + CreateSysSummmaryPacket(doc); + String summary; + serializeJson(doc, summary); + request->send(200, "application/json", summary); + return; + } + + if(!strcmp(dataType, "wifi")){ + JsonDocument jsdoc; + jsdoc["ssid"] = ssid; + //jsdoc["pass"] = (int)strlen(passPhrase); + jsdoc["pass"] = passPhrase; + + String jsStr; + serializeJson(jsdoc, jsStr); + + request->send(200, "application/json", jsStr); + return; + } + } + + request->send(400, "text/plain", "Failed"); +} + +void CreateSysSummmaryPacket(JsonDocument &doc){ + JsonObject booth = doc["booth"].to(); + booth["mode"] = ""; + booth["app"] = sysProps.appName; + booth["strip1"] = (strip1) ? true : false, + booth["strip2"] = (strip2) ? true : false, + booth["front-light"] = true; + booth["rear-light"] = false; + booth["oled"] = (oled) ? true : false; + + //ESP.getHeapSize(), ESP.getFreeHeap(), ESP.getHeapSize()-ESP.getFreeHeap()); + + JsonObject Temperature = doc["temperature"].to(); + Temperature["temp"] = sysProps.t_sensor.temperature; + Temperature["sp1"] = sysProps.t_sensor.Setpoint1; + doc["system"]["firm-ver"] = FIRMWARE_VER; + + JsonObject chip = doc["chip"].to(); + chip["flash-size"] = ""; + chip["flash-free"] = ""; + chip["ram-size"] = ESP.getHeapSize(); + chip["ram-free"] = ESP.getFreeHeap(); + + + + // Get the Wi-Fi RSSI + wifi_ap_record_t wifi_ap_info; + esp_err_t rssi_result = esp_wifi_sta_get_ap_info(&wifi_ap_info); + int rssi = 0; int wifi_ch = 0; + String encryp = ""; + if (rssi_result == ESP_OK) { + rssi = wifi_ap_info.rssi; + wifi_ch = wifi_ap_info.primary; + + wifi_auth_mode_t auth_mode = wifi_ap_info.authmode; + switch (auth_mode) { + case WIFI_AUTH_OPEN: + encryp = "Open"; + break; + case WIFI_AUTH_WEP: + encryp = "WEP"; + break; + case WIFI_AUTH_WPA_PSK: + encryp = "WPA_PSK"; + break; + case WIFI_AUTH_WPA2_PSK: + encryp = "WPA2_PSK"; + break; + case WIFI_AUTH_WPA_WPA2_PSK: + encryp = "WPA_WPA2_PSK"; + break; + case WIFI_AUTH_WPA2_ENTERPRISE: + encryp = "WPA2_ENT"; + break; + default: + encryp = "UNKNOWN"; + break; + } + } + + JsonObject wifi = doc["wifi"].to(); + wifi["client-ip"] = "XXX.XXX.XXX.XXX"; + wifi["mac-addr"] = chipInfo.macStr; + wifi["ch"] = wifi_ch; + wifi["rssi"] = rssi; + wifi["encryp"] = encryp; + + JsonObject ble = doc["ble"].to(); + ble["en"] = (sysProps.appIndex == DSLRBOOTH_INDEX)? false : true; + ble["clients"] = (BTDeviceConnected)? 1 : 0; + ble["ssid"] = BLEDeviceName; + +} + +void handlePOST_Post(AsyncWebServerRequest *request){ + ESP_LOGD(tag, "posts request.."); + if(!getpostSuccess) { request->send(400, "text/plain", "Error"); } + + request->send(200, "text/plain", "Ok"); +} + +void postFileUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) { + getpostSuccess = false; + Serial.println("post file"); +} + + +// POST Requests +int packets, postTotal; +char postType[24]; +char *postData; +void postBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){ + if (!index){ + packets = 0; + int sectors = (total + 1023 + 64) / 1024; + postData = new char[1024 * sectors]; + } + + // Accumulate Data Chunks + memcpy(postData + index, data, len); + packets++; + + if((index + len) >= total){ + ESP_LOGD(tag, "index: %d, len: %d, total: %d", index, len, total); + ESP_LOGD(tag, "packets: %d", packets); + getpostSuccess = false; + + // Check if the request contains the necessary parameters + if (request->hasParam("type", false, false)) { + const char* dataType = request->getParam("type", false, false)->value().c_str(); + ESP_LOGD(tag, "data type: %s", dataType); + + if(!strcmp(dataType, "anim-profile-common") || !strcmp(dataType, "set-countdown")){ + JsonDocument profJson; + DeserializationError error = deserializeJson(profJson, postData, total); + + if(!error){ + animProps.profileIndex = profJson["profile-index"].as(); + + JsonObject countdownJson = profJson["countdown"]; + animProps.frontLight.minDuty = countdownJson["min"].as(); + animProps.frontLight.maxDuty = countdownJson["max"].as(); + animProps.frontLight.holdTime = countdownJson["hold"].as(); + animProps.frontLight.rampTime = countdownJson["ramp"].as(); + + int indexChanged = animProps.profileIndex - countdownJson["profile-index"].as(); + animProps.profileIndex = countdownJson["profile-index"].as(); + if(indexChanged){ + load_animation_profileByIndex(animProps.profileIndex); + } + + // Save to disk/flash + if(dataType[0] == 'a'){ + if(updateJsonDocument(profJson, "/cfg/anim-profile-common.json")){ + Buzzer_Beep(150, 3000); + } + } + else{ + ESP_LOGD(tag, "Post:set-countdown, index: %d, min: %.1f, max: %.1f, hold: %d, ramp: %d", animProps.profileIndex, animProps.frontLight.minDuty, animProps.frontLight.maxDuty, animProps.frontLight.holdTime, animProps.frontLight.rampTime); + Buzzer_Beep(150, 3000); + } + }else{ + ESP_LOGE(tag, "Deserialization error for wifi.json"); + } + + getpostSuccess = true; + //goto done; + } + else if(!strncmp(dataType, "anim-profile", 12)){ + if (strlen(dataType) > 12) { + int profile_index = (dataType[12] - '0' - 1) % 8; // extract index, assuming dataType has enough characters + JsonDocument profJson; + DeserializationError error = deserializeJson(profJson, postData); + + if(!error){ + JsonArray eventsArray = profJson["events"]; + for(int i = 0; i < eventsArray.size(); i++ ){ + animProps.event[i].selfIndex = i; + animProps.event[i].animIndex = eventsArray[i]["anim"].as(); + animProps.event[i].hue = eventsArray[i]["hue"].as(); + animProps.event[i].hueRange = eventsArray[i]["hue-range"].as(); + animProps.event[i].speed = eventsArray[i]["speed"].as(); + animProps.event[i].param1 = eventsArray[i]["param1"].as(); + animProps.event[i].param2 = eventsArray[i]["param2"].as(); + animProps.event[i].check1 = eventsArray[i]["check1"].as(); + animProps.event[i].check2 = eventsArray[i]["check2"].as(); + animProps.event[i].check3 = eventsArray[i]["check3"].as(); + animProps.event[i].check4 = eventsArray[i]["check4"].as(); + animProps.event[i].countDown = 0; + } + + ESP_LOGD(tag, "Extracted/Updated active profile to animProps."); + + // Save to disk/flash + String filePath = "/cfg/anim-profile" + String(profile_index + 1) + ".json"; + if(updateJsonDocument(profJson, filePath.c_str())){ + Buzzer_Beep(150, 3000); + } + + }else{ + ESP_LOGE(tag, "Deserialization error for wifi.json"); + } + + getpostSuccess = true; + } + } + else if(!strcmp(dataType, "play-anim")){ + JsonDocument animData; + DeserializationError error = deserializeJson(animData, postData); + if(!error){ + ANIMATION_EVENT testEvent; + testEvent.selfIndex = animData["index"].as(); + testEvent.animIndex = animData["anim"].as(); + testEvent.hue = animData["hue"].as(); + testEvent.hueRange = animData["hue-range"].as(); + testEvent.speed = animData["speed"].as(); + testEvent.param1 = animData["param1"].as(); + testEvent.param2 = animData["param2"].as(); + testEvent.check1 = animData["check1"].as(); + testEvent.check2 = animData["check2"].as(); + testEvent.check3 = animData["check3"].as(); + testEvent.check4 = animData["check4"].as(); + + if(testEvent.selfIndex != 0){ + testEvent.selfIndex = 1; + } + ESP_LOGI(tag, "Post:play-anim, index: %d, anim: %d, hue: %d, rng: %d, speed: %d, par1: %d, par2: %d, chk1: %d, chk2: %d, chk3: %d, chk4: %d", testEvent.selfIndex, testEvent.animIndex, testEvent.hue, testEvent.hueRange, testEvent.speed, testEvent.param1, testEvent.param2, testEvent.check1, testEvent.check2, testEvent.check3, testEvent.check4); + testEvent.countDown = 0; // set to zero so param1 determines countdown time + testEvent.type = EV_TEST; + PostNewEvent(testEvent); // eventIndex always = 1 + Buzzer_Beep(150, 3000); + }else{ + ESP_LOGE(tag, "Deserialization error for play-anim"); + } + + getpostSuccess = true; + } + else if(!strcmp(dataType, "wifi")){ + JsonDocument wifiCreds; + DeserializationError error = deserializeJson(wifiCreds, postData, total); + if(!error){ + //read credentials + //const char* new_ssid = wifiCreds["ssid"]; + //const char* new_pass = wifiCreds["pass"]; + + String new_ssid = wifiCreds["ssid"]; + String new_pass = wifiCreds["pass"]; + ESP_LOGV(tag, "SSID: %s PASS: %s", new_ssid, new_pass); + // Save Credentials if different + //if(strcmp(new_ssid, ssid)!=0 || strcmp(new_pass, passPhrase)!=0){ + if(new_ssid != ssid || new_pass != passPhrase){ + Wifi_Save_Credentials(new_ssid, new_pass); + StartDelayedRebooth; + } + }else{ + ESP_LOGE(tag, "Deserialization error for wifi"); + } + + getpostSuccess = true; + } + else if(!strcmp(dataType, "set-pixel")){ + JsonDocument js; + DeserializationError error = deserializeJson(js, postData); + if(!error){ + ANIMATION_EVENT testEvent; + testEvent.selfIndex = 1; + testEvent.animIndex = 81; + testEvent.hue = js["hue"].as(); + testEvent.param1 = js["index"].as(); + testEvent.type = EV_TEST; + PostNewEvent(testEvent); // eventIndex always = 1 + ESP_LOGD(tag, "Post: set-pixel, hue: %d, pixel: %d", testEvent.hue, testEvent.param1); + Buzzer_Beep(150, 3000); + }else{ + ESP_LOGE(tag, "Deserialization error for set-pixel"); + } + + Buzzer_Beep(50, 3000); + getpostSuccess = true; + } + else if(!strcmp(dataType, "clear-strip")){ + ANIMATION_EVENT testEvent; + testEvent.selfIndex = 1; + testEvent.animIndex = 80; + ESP_LOGD(tag, "Post: clear-strip"); + testEvent.type = EV_TEST; + PostNewEvent(testEvent); // eventIndex always = 1 + + getpostSuccess = true; + Buzzer_Beep(50, 3000); + } + else if(!strcmp(dataType, "setup-save")){ + JsonDocument js; + DeserializationError error = deserializeJson(js, postData); + if(!error){ + // If app index is different open app-events.json and update + if(sysProps.appIndex != js["appindex"].as()){ + File file = LittleFS.open("/cfg/app-events.json", "r+"); + if(!file){ + ESP_LOGE(tag, "Failed to open app-events.json"); + file.close(); + return; + } + + JsonDocument doc; + DeserializationError error = deserializeJson(doc, file); + file.close(); + + if(!error){ + // Update index value + doc["index"] = js["appindex"].as(); + ESP_LOGD(tag, "New App Index = %d", doc["index"].as()); + + if(updateJsonDocument(doc, "/cfg/app-events.json")){ + Buzzer_Beep(150, 3000); + } + }else{ + ESP_LOGE(tag, "Deserialization error for app-events.json"); + } + } + }else{ + ESP_LOGE(tag, "Deserialization error for seteup-save"); + } + + // if any of the values are diiferent then open led-devices.json and update + //if app index is different open app-events.json and update + bool editJson = false; + //if(js["en1"].as() != strip1->size) { editJson = true; } + if(js["count1"].as() != strip1->size) { editJson = true; } + else if(js["shift1"].as() != strip1->shift) { editJson = true; } + else if(js["offset1"].as() != strip1->offset) { editJson = true; } + else if(js["rgb1"].as() != strip1->ledOrder) { editJson = true; } + else if(js["power1"].as() != strip1->powerDiv) { editJson = true; } + + if(editJson){ + File file = LittleFS.open("/cfg/led-devices.json", "r+"); + if(!file){ + ESP_LOGE(tag, "Failed to open led-devices.json"); + file.close(); + return; + } + + JsonDocument doc; + DeserializationError error = deserializeJson(doc, file); + file.close(); + + if(!error){ + JsonObject jsStrip1 = doc["strip1"].to(); // nested so it doesn't create a copy + + jsStrip1["en"] = true; + jsStrip1["size"] = js["count1"].as(); + jsStrip1["shift"] = js["shift1"].as(); + jsStrip1["offset"] = js["offset1"].as(); + jsStrip1["power-div"] = js["power1"].as(); + jsStrip1["rgb-order"] = js["rgb1"]; + + // Save Json File + if(updateJsonDocument(doc, "/cfg/led-devices.json")){ + Buzzer_Beep(150, 3000); + } + }else{ + ESP_LOGE(tag, "Deserialization error for led-devices.json"); + } + } + + ESP_LOGD(tag, "Post: setup-save"); + getpostSuccess = true; + Buzzer_Beep(150, 3000); + } + else if(!strcmp(dataType, "restart")){ + StartDelayedRebooth; + + ESP_LOGD(tag, "Post: restart"); + getpostSuccess = true; + Buzzer_Beep(150, 3000); + } + } + + // Delete the allocated buffer (owned by the request->_tempObject) + delete[] postData; + } +} + +bool isInternetConnected() +{ + if (WiFi.status() == WL_CONNECTED) { // check if WiFi is connected + WiFiClient client; + const int httpPort = 80; + if (client.connect("www.google.com", httpPort)) { // try to connect to Google + client.stop(); + return true; // Internet access available + } + } + return false; // Internet access not available +} + +void handlePOST_Upload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) +{ + if (!index && request->hasParam("dir-path", true, false)) { + AsyncWebParameter* p = request->getParam("dir-path",true, false); + String path = p->value() + "/" + filename; + ESP_LOGD(tag, "upload path: %s", path.c_str()); + + request->_tempFile= LittleFS.open(path, "w"); + //fs::File file = LittleFS.open(path, "w"); + if (!request->_tempFile) { + ESP_LOGE(tag, "Failed to create file!"); + return; + } + + }else{ + ESP_LOGE(tag, "dir-path Param not found.."); + } + + if(len && request->_tempFile){ + request->_tempFile.write(data, len); + } + + if(final){ + request->_tempFile.close(); + request->redirect("/files"); + ESP_LOGD(tag, "UploadEnd: %s, %u B", filename.c_str(), index+len); + } +} + +void handleGET_DSLRBooth(AsyncWebServerRequest *request) +{ + static int lastCountdown = 1000; + request->send(200, "text/plain"); // send this right away to avoid comm delays + + if(request->args() >= 1){ + int x = 0; + const char* event_type = request->arg( x ).c_str(); + + if(strcmp("countdown", event_type) == 0){ + int p = request->arg(1).toInt(); + if(p > 0 && p <= 100){ + animStatus.countStatus = p; + } + + // in case "coundown_start" was missed + if(animStatus.EventsIndex != ANIM_WHITEFILL_INDEX){ + animProps.event[0].countDown = lastCountdown; + animProps.event[0].type = EV_NORMAL; + PostNewEvent(animProps.event[0]); + } + } + else if(strcmp("countdown_start", event_type) == 0){ // index=0 + int count = request->arg(1).toInt(); // retrieve countdown value + //Log.traceln(" countdown = %d", count); + if(count < 1){count = 1;} + count *= 1000; + lastCountdown = count - 500; + animProps.event[0].countDown = lastCountdown; + animProps.event[0].type = EV_NORMAL; + PostNewEvent(animProps.event[0]); + } + else if(strcmp("sharing_screen", event_type) == 0){ + animProps.event[2].type = EV_NORMAL; + PostNewEvent(animProps.event[2]); + } + else if(strcmp("session_end", event_type) == 0){ //index=1 + animProps.event[1].type = EV_NORMAL; + PostNewEvent(animProps.event[1]); + } + else{ + // else if(strcmp_P("session_start", event_type) == 0){ + // TODO determine if ramp down is active depending on session type + + /* + const char* param1 = request->arg(1).c_str(); + if(strcmp("PrintOnly", param1)){ + animStatus.Animation = ANIM_START_PRINTONLY; + xTaskNotifyGive( Strip1_Task_Handle ); + //Serial.println("ses_print..\n\r"); + }else if(strcmp("PrintAndGIF", param1)){ + animStatus.Animation = ANIM_START_PRINTGIF; + xTaskNotifyGive( Strip1_Task_Handle ); + //Serial.println("ses_prnt_gif..\n\r"); + }else if(strcmp("OnlyGIF", param1)){ + animStatus.Animation = ANIM_START_GIFONLY; + xTaskNotifyGive( Strip1_Task_Handle ); + //Serial.println("ses_gif..\n\r"); + }else if(strcmp("Boomerang", param1)){ + animStatus.Animation = ANIM_START_BOOM; + xTaskNotifyGive( Strip1_Task_Handle ); + //Serial.println("ses_boom..\n\r"); + }else if(strcmp("Video", param1)){ + animStatus.Animation = ANIM_START_VIDEO; + xTaskNotifyGive( Strip1_Task_Handle ); + //Serial.println("ses_vid..\n\r"); + } + */ + } + } +} + +bool isFirmware = true; +bool updateSuccessful = false; +void handlePOST_Update(AsyncWebServerRequest *request) +{ + bool hasError = Update.hasError(); + String responseContent = hasError ? "failed" : "success"; + String responseType = hasError ? "text/plain" : "text/html"; + int responseCode = hasError ? 500 : 200; + + AsyncWebServerResponse *response = request->beginResponse(responseCode, responseType, responseContent); + response->addHeader("status", responseContent); + request->send(response); +} + +// handlePOST_Update Callback function to install the firmware or file system bin files +void updateCallback(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) +{ + // Do Once Code + if (!index) { + size_t fileSize = UPDATE_SIZE_UNKNOWN; + // Retrieve file-size parameter + if(request->hasParam("file-size", true, false)) { // make sure param is from POST request or it will not find it + fileSize = request->getParam("file-size", true, false)->value().toInt(); + } + + // register progress callback function + Update.onProgress(updateFirmwareProgress); + updateSuccessful = false; + + //file name checks + if (filename.startsWith("ata_fw")) { + isFirmware = true; + if (!Update.begin(fileSize, U_FLASH, BoardLED2)) { + ESP_LOGD(tag, "Firmware update failed to start"); + return; + } + ESP_LOGD(tag, "Updating Firmware with: %s Size:%d ", filename.c_str(), fileSize); + } + else if (filename.startsWith("ata_fs")) { + isFirmware = false; + if (!Update.begin(fileSize, U_SPIFFS, BoardLED2)) { + ESP_LOGD(tag, "LittleFS update failed to start"); + return; + } + ESP_LOGD(tag, "Updating File System with: %s Size:%d ", filename.c_str(), fileSize); + } + else { + ESP_LOGD(tag, "Unsupported file type"); + return; + } + } + + // Writing... + if (!Update.hasError()) { + if (Update.write(data, len) != len) { + Update.printError(Serial); + } + } + + // All Done... + if (final) { + vTaskDelay(100); + if(isFirmware) { // firmware update + if (Update.end(true)) { + RebootSystem = true; + updateSuccessful = true; + ESP_LOGD(tag, "firmware update successful: %d", index + len); + } + else { + Update.printError(Serial); + } + } + else{ // file system update + if(Update.end(true)) { + updateSuccessful = true; + ESP_LOGD(tag, "file system update successful!"); + } + else { + Update.printError(Serial); + } + } + } +} + +// Server requested files that aren't template processed +void handleGET_SendFile(AsyncWebServerRequest *request)// try this later "^/(img|favicon).*" +{ + String filePath = request->url(); + const char* ext = getFileExtension(filePath.c_str()); + const char* contentType = getFileType(ext); + + if( contentType == NULL ){ + request->send(404); + return; + } + + if(filePath.c_str()[0] != '/'){ + filePath = '/' + filePath; + } + + if (!strcmp(ext,"html") || !strcmp(ext, "htm") || !strcmp(ext, "css") || !strcmp(ext, "js")) { + if(!filePath.startsWith("/www/")){ + filePath = "/www" + filePath; + } + } + + ESP_LOGD(tag, "Sent: %s", filePath.c_str()); + request->send(LittleFS, filePath, contentType); +} + +String listDir(String directoryList[], int count) +{ + String listedFiles; + + for (int i = 0; i < count; i++) { + // directory html + listedFiles += "Dir: "; + listedFiles += directoryList[i]; + listedFiles += "/-\n"; + + filesDropdownOptions += "\n"; + + dirDropdownOptions += "\n"; + + File dir = LittleFS.open(directoryList[i]); + File file = dir.openNextFile(); + while (file) { + String fileName = file.name(); + if (!file.isDirectory()) { + //Serial.println(" File: " + String(file.name())); + listedFiles += "  "; + listedFiles += fileName; + listedFiles += ""; + listedFiles += convertFileSize(file.size()); + listedFiles += "\n"; + + filesDropdownOptions += "\n"; + } + file = dir.openNextFile(); + } + dir.close(); + } + + return listedFiles; +} + +char* readFile(fs::FS &fs, const char* path) { + File file = fs.open(path, "r"); + if (!file || file.isDirectory()) { + return nullptr; + } + + size_t fileSize = file.size(); + char* fileContent = new char[fileSize + 1]; // +1 for null-terminator + + size_t bytesRead = file.readBytes(fileContent, fileSize); + file.close(); + + fileContent[bytesRead] = '\0'; // Set null-terminating character + + if (bytesRead != fileSize) { + delete[] fileContent; + return nullptr; + } + + return fileContent; +} + +void writeFile(fs::FS &fs, const char * path, const char * message) +{ + File file = fs.open(path, "w"); + if(!file){ + return; + } + file.print(message); + file.close(); +} + +// Send html file with template processing {{VAR}} +void sendHtmlFile(const char* filePath, AsyncWebServerRequest *request, String (*callback)(const String&)) +{ + ESP_LOGD(tag, "Sent file: %s", filePath); + File file = LittleFS.open(filePath, "r"); + const char* htmlFile = readFile(LittleFS, filePath); + + //String processedData = varReplace(htmlFile, htmlProcessor); + String processedData = varReplace(htmlFile, callback); + request->send(200, "text/html", processedData); +} + +const char* convertFileSize(const size_t bytes) { + static char fileSizeBuffer[16]; // PreAllocated buffer for the file size + + if (bytes < 1024) { + snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%d B", bytes); + } else if (bytes < 1024*1024) { + snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%.2f kB", static_cast(bytes) / 1024.0); + } else if (bytes < 1024*1024*1024) { + snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%.2f MB", static_cast(bytes) / 1048576.0); + } else { + fileSizeBuffer[0] = '\0'; // Empty string if the file size is too large + } + + return fileSizeBuffer; +} + +// Callback template processor +String htmlProcessor(const String& var) +{ + if(var == "NAVBAR"){ return String("\n"); } + + if(var == "ALLOWED_EXTENSIONS_EDIT"){ return allowedExtensionsForEdit; } + + if(var == "FS_FREE_BYTES"){ return convertFileSize(LittleFS.totalBytes() - LittleFS.usedBytes()); } + + if(var == "FS_USED_BYTES"){ return convertFileSize(LittleFS.usedBytes()); } + + if(var == "FS_TOTAL_BYTES"){ return convertFileSize(LittleFS.totalBytes()); } + + if(var == "LISTED_FILES"){ + filesDropdownOptions = ""; // clear out + dirDropdownOptions = ""; // clear out + String directories[8]; + int dirCount = 0; + getAllDirectories(directories, dirCount); + + return listDir(directories, dirCount); + } + + if(var == "EDIT-DEL_FILES"){ return filesDropdownOptions; } + + if(var == "DIR_LIST"){ return dirDropdownOptions; } + + if(var == "SAVE_PATH_INPUT"){ + if(savePath == "/new.txt"){ + return String(""; + } + else{ + return String(""; + } + } + + if(var == "FIRM_VER"){ return FIRMWARE_VER; } + + return var; +} + +String HomeHtmlProcessor(const String& var) { + if(var == "NAVBAR"){ return String("\n"); } + + if (var == "APP_NAME") { + return sysProps.appName; + } + if (var == "OLED") { + return "No"; + } + if (var == "STRIP1") { + return (strip1) ? "Yes" : "No"; + } + if (var == "STRIP2") { + return (strip2) ? "Yes" : "No"; + } + if (var == "FRONT_LIGHT") { + return (animProps.frontLight.enabled) ? "Yes" : "No"; + } + if (var == "REAR_LIGHT") { + return (animProps.rearLight.enabled) ? "Yes" : "No"; + } + if (var == "FIRMWARE") { + return FIRMWARE_VER; + } + if (var == "BOOTH_T") { + return String(sysProps.t_sensor.temperature) + "F"; + } + if (var == "SETPOINT") { + return String(sysProps.t_sensor.Setpoint1) + "F"; + } + if (var == "FLASH_SIZE") { + return convertFileSize(ESP.getSketchSize()); + } + if (var == "FLASH_FREE") { + return convertFileSize(ESP.getFreeSketchSpace()); + } + if (var == "HEAP_SIZE") { + return convertFileSize(ESP.getHeapSize()); + } + if (var == "HEAP_FREE") { + return convertFileSize(ESP.getFreeHeap()); + } + if (var == "CPU_FREQ") { + return String(ESP.getCpuFreqMHz()) + "Mhz"; + } + if (var == "IP") { + return WiFi.localIP().toString(); + } + if (var == "MAC") { + return chipInfo.macStr; + } + if (var == "SSID") { + return WiFi.SSID(); + } + if (var == "RSSI") { + return String(WiFi.RSSI()); + } + if (var == "WIFI_CH") { + return String(WiFi.channel()); + } + if (var == "ENCRYP") { + return String(WiFi.encryptionType(0)); + } + if (var == "AP_SSID") { + return WiFi.softAPSSID(); + } + if (var == "AP_CLIENTS") { + return String(WiFi.softAPgetStationNum()); + } + if (var == "BLE") { + return (commMode == COMM_WIFI_AP_BLE) ? "Yes" : "No"; + } + if (var == "BLE_SSID") { + return (commMode == COMM_WIFI_AP_BLE) ? BLEDeviceName : ""; + } + if (var == "BLE_CLIENTS") { + return (BTDeviceConnected) ? "1" : "0"; + } + if (var == "AP_MAC") { + return getSoftAPMacAddress(); + } + + + // Return an empty string if the variable is not recognized + return var; +} + +String getSoftAPMacAddress() { + uint8_t mac[6]; + WiFi.softAPmacAddress(mac); + + String macString = ""; + for (int i = 0; i < 6; i++) { + macString += String(mac[i], HEX); + if (i < 5) macString += ":"; + } + return macString; +} + +// Finds segments between {{VAR}} and calls a callback function to replace VAR with new content +String varReplace(const String& input, String (*callback)(const String&)) { + if (input.isEmpty()) { + return input; + } + + String result; + int startPos = 0; + int start = input.indexOf("{{", startPos); + + while (start != -1) { + result += input.substring(startPos, start); + + int end = input.indexOf("}}", start + 2); + if (end == -1) { + break; + } + + String segment = input.substring(start + 2, end); + int segmentLength = segment.length(); + if (segmentLength <= 32) { + String replacement = callback(segment); + result += replacement; + } else { + result += input.substring(start, end + 2); // Include the original segment if it exceeds the limit + } + + startPos = end + 2; + start = input.indexOf("{{", startPos); + } + + result += input.substring(startPos); + return result; +} + +const char* getFileExtension(const char* filename) { + size_t dotPos = strlen(filename); + while (dotPos > 0 && filename[dotPos - 1] != '.') { + dotPos--; + } + + if (dotPos != 0 && dotPos < strlen(filename) - 1) { + return &filename[dotPos]; + } + + return ""; // Empty string if no extension found +} + +const char* getFileType(const char* ext) { + if (strcmp(ext, "png") == 0) { + return "image/png"; + } else if (strcmp(ext, "jpg") == 0 || strcmp(ext, "jpeg") == 0) { + return "image/jpeg"; + } else if (strcmp(ext, "gif") == 0) { + return "image/gif"; + } else if (strcmp(ext, "ico") == 0) { + return "image/x-icon"; + } else if (strcmp(ext, "txt") == 0) { + return "text/plain"; + } else if (strcmp(ext, "css") == 0) { + return "text/css"; + } else if (strcmp(ext, "htm") == 0 || strcmp(ext, "html") == 0) { + return "text/html"; + } else if (strcmp(ext, "js") == 0) { + return "text/javascript"; + } else if (strcmp(ext, "json") == 0) { + return "application/json"; + } else { + return ""; + } +} + +// Serial print firmware or file system update progress +void updateFirmwareProgress(size_t progress, size_t total) +{ + static int lastProg = -1; + int firmwareProgress = (progress * 100)/ total; + + if(firmwareProgress != lastProg){ + Serial.printf("Progress: %u%%\r", firmwareProgress); + lastProg = firmwareProgress; + //TODO Add buzzer tune while uploading firmware + //Buzzer_Play_Tune(TUNE_DOWNLOADING,1,true,false); + } +} + + diff --git a/temporary/Temp/my_wifi.h b/temporary/Temp/my_wifi.h new file mode 100644 index 0000000..67f0773 --- /dev/null +++ b/temporary/Temp/my_wifi.h @@ -0,0 +1,66 @@ +#ifndef _MY_WIFI_H +#define _MY_WIFI_H + +#include +#include +#include + +extern int RebootSystem; + +void Wifi_Start_WebServer(void); +void Init_Wifi_Task(void); +void Wifi_Start_MDNS(void); +void Wifi_Task(void *parameters); +String getSoftAPMacAddress(void); +//void onWiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info); +void onWiFiEvent(WiFiEvent_t event); +void Wifi_Request_Task(void *parameters); +void Setup_WebServer_Handlers(AsyncWebServer* serv); + +void Wifi_Read_Credentials(const JsonObject &credJson); +void Wifi_Save_Credentials(String newSSID, String newPass); +bool isInternetConnected(void); +void Wifi_Client_Loop(void); + +// Handlers +void handlePOST_RegStick(AsyncWebServerRequest *request); + +void handleGET_DSLRBooth(AsyncWebServerRequest *request); +void handlePOST_Upload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); +void handlePOST_Update(AsyncWebServerRequest *request); +void updateCallback(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); +void updateFirmwareProgress(size_t progress, size_t total); + +void handleGET_SendFile(AsyncWebServerRequest *request); +void onFileUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); + +void handleGET_Get(AsyncWebServerRequest *request); +void handlePOST_Post(AsyncWebServerRequest *request); +void postFileUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final); +void postBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total); + + +void sendHtmlFile(const char* filePath, AsyncWebServerRequest *request, String (*callback)(const String&)); +String htmlProcessor(const String& var); +String HomeHtmlProcessor(const String& var); +//const char* htmlProcessor(const char* var); +String listDir(fs::FS &fs, const char *dirname, bool isSubdirectory); + +const char* getFileExtension(const char* filename); +const char* getFileType(const char* ext); +const char* convertFileSize(const size_t bytes); +char* readFile(fs::FS &fs, const char* path); +void writeFile(fs::FS &fs, const char * path, const char * message); + +String varReplace(const String& input, String (*callback)(const String&)); +//const char* varReplace(const char* input, const char* (*callback)(const char*)); + +void CreateSysSummmaryPacket(JsonDocument &doc); + + + + + + + +#endif \ No newline at end of file diff --git a/temporary/Temp/otaupdate.cpp b/temporary/Temp/otaupdate.cpp new file mode 100644 index 0000000..e18a99c --- /dev/null +++ b/temporary/Temp/otaupdate.cpp @@ -0,0 +1,96 @@ +#include "otaupdate.h" +#include "LittleFS.h" +#include +#include +#include "githubCert.h" +#include + + +String binFile = "manifest.json"; +String rootUrl = "https://raw.githubusercontent.com/ataindustries/atacontrolboardV1/main/"; +String github_token = "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"; + +const float FirmwareVer = 1.0; + +JsonDocument doc; + +int otaupdate_check(void) +{/* + String payload; + int httpCode; + WiFiClientSecure * client = new WiFiClientSecure; + HTTPClient https; + + String manifest_url = rootUrl + binFile + "?token=" + github_token; + https.begin(manifest_url); + httpCode = https.GET(); + + if (httpCode == HTTP_CODE_OK) // if version received + { + String payload = https.getString(); + JsonDocument doc; + deserializeJson(doc, payload); + + float firmver = doc["firmver"].as(); + const char* firmfile = doc["firmfile"]; + const char* fsfile = doc["fsfile"]; + + Serial.printf("From manifest: %.1f, %s, $s\n\r", firmver, firmfile, fsfile); + + if(firmver > FirmwareVer){ + Serial.println("New firmware available!"); + return 1; + }else{ + Serial.println("firmware is upto date!"); + return 0; + } + } + */ + return 0; +} + +void otaupdate_perform(void) +{ + + // setup events + ota_events(); + + + // Pause unnecessary tasks + + //check for update +} + +void ota_events(void) +{ + /* + fota->setUpdateEndCb( [](int partition) + { + //Serial.printf("Update could not finish with %s partition\n", partition==U_SPIFFS ? "spiffs" : "firmware" ); + }); + + fota->setProgressCb( [](size_t progress, size_t size) + { + //if( progress == size || progress == 0 ) Serial.println(); + //Serial.print("."); + }); + + fota->setUpdateCheckFailCb( [](int partition, int error_code) + { + //Serial.printf("Update could validate %s partition (error %d)\n", partition==U_SPIFFS ? "spiffs" : "firmware", error_code ); + // error codes: + // -1 : partition not found + // -2 : validation (signature check) failed + }); + + fota->setUpdateFinishedCb( [](int partition, bool restart_after) + { + //Serial.printf("Update could not begin with %s partition\n", partition==U_SPIFFS ? "spiffs" : "firmware" ); + + if( restart_after ) { + ESP.restart(); + } + }); + */ +} + diff --git a/temporary/Temp/otaupdate.h b/temporary/Temp/otaupdate.h new file mode 100644 index 0000000..0667dea --- /dev/null +++ b/temporary/Temp/otaupdate.h @@ -0,0 +1,14 @@ +#ifndef _OTAUPDATE_H +#define _OTAUPDATE_H + +#include + + +int otaupdate_check(float myVer); + +void otaupdate_perform(String filePath); + +void ota_events(void); + + +#endif \ No newline at end of file diff --git a/temporary/Temp/rf_transceiver.cpp b/temporary/Temp/rf_transceiver.cpp new file mode 100644 index 0000000..1ff5e67 --- /dev/null +++ b/temporary/Temp/rf_transceiver.cpp @@ -0,0 +1,44 @@ + +#include "rf_transceiver.h" + +static const char* tag = "trx433"; +TaskHandle_t RF_Task_Handle; + +const int txChannel = RMT_CHANNEL_0; +const int rxChannel = RMT_CHANNEL_1; + +void Init_RF_Receiver(void) +{ + /* + // Configure RMT receiver + rmt_config_t rxConfig; + rxConfig.channel = (rmt_channel_t)rxChannel; + rxConfig.gpio_num = static_cast(RMT_RX_PIN); + // Set other reception settings as needed + rmt_config(&rxConfig); + rmt_driver_install(rxConfig.channel, 1000, 0); + + xTaskCreatePinnedToCore(RF_Control_Task, "RF_Task", 8000, NULL, 1, &RF_Task_Handle, 0); + */ +} + +void Init_RF_Transmitter(void) +{ + /* + rmt_config_t txConfig; + txConfig.channel = (rmt_channel_t)txChannel; + txConfig.gpio_num = static_cast(RMT_TX_PIN); + // Set other transmission settings as needed + rmt_config(&txConfig); + rmt_driver_install(txConfig.channel, 0, 0); + */ +} + +void RF_Control_Task(void *parameters) +{ + for(;;){ + vTaskDelay(100); + } +} + + diff --git a/temporary/Temp/rf_transceiver.h b/temporary/Temp/rf_transceiver.h new file mode 100644 index 0000000..091368f --- /dev/null +++ b/temporary/Temp/rf_transceiver.h @@ -0,0 +1,23 @@ +#ifndef _RF_TRANSCEIVER_H +#define _RF_TRANSCEIVER_H + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" + +#include +#include +#include "my_board.h" + +#define RMT_RX_PIN RX433_Pin +#define RMT_TX_PIN TX433_Pin +#define RTM_RX_CHANNEL 0 +#define RTM_TX_CHANNEL 1 + +void Init_RF_Receiver(void); + +void Init_RF_Transmitter(void); + +void RF_Control_Task(void *parameters); + +#endif \ No newline at end of file diff --git a/temporary/appcontrol old.html b/temporary/appcontrol old.html new file mode 100644 index 0000000..3033a2a --- /dev/null +++ b/temporary/appcontrol old.html @@ -0,0 +1,623 @@ +{{NAVBAR}} + + + + App Control Configuration + + + + + + +

App Control Configuration

+ + +
+ Saved Animation Profiles + + + + +
+ + +    + + +     + +
+
+ + +
+ + +
+ Countdown Animation ( White Fill ) + + + + + + + + + + + + + +
+
+ +
+
+ +
+ +
+
+
+
+ +
+
+ +
+
+ +
+ +
+ Event 1 + + + + + + + + + +
+
+ +
+
+   +
+
+   +
+
+ +
+
+ +
+ +
+
+ +
+ +
+ Event 2 + + + + + + + + + +
+
+ +
+
+   +
+
+   +
+
+ +
+
+ +
+ +
+
+ +
+ +
+ Event 3 + + + + + + + + + +
+
+ +
+
+   +
+
+   +
+
+ +
+
+ +
+ +
+
+ +
+ +
+ Event 4 + + + + + + + + + +
+
+ +
+
+   +
+
+   +
+
+ +
+
+ +
+ +
+
+ +
+ +
+ Event 5 + + + + + + + + + +
+
+ +
+
+   +
+
+   +
+
+ +
+
+ +
+ +
+
+ +
+ +
+ Event 6 + + + + + + + + + +
+
+ +
+
+   +
+
+   +
+
+ +
+
+ +
+ +
+
+ +
+ +
+ Event 7 + + + + + + + + + +
+
+ +
+
+   +
+
+   +
+
+ +
+
+ +
+ +
+
+ +
+ +
+ Event 8 + + + + + + + + + +
+
+ +
+
+   +
+
+   +
+
+ +
+
+ +
+ +
+
+ + + + diff --git a/temporary/bak/cfg/!instructions.txt b/temporary/bak/cfg/!instructions.txt new file mode 100644 index 0000000..1e97b38 --- /dev/null +++ b/temporary/bak/cfg/!instructions.txt @@ -0,0 +1,13 @@ + +Choose Control App: + 1) Open app-events.json + 2) Change index to corresponding app + a. 0=DSLRBooth, 1=...., 2=..... + +Front Constant Light: ( to enable the light) + 1) Open led-devices.json + 2) Change "front-light", "en" to true + +Rear Constant Light: + 1) Open led-devices.json + 2) Change "rear-light", "en" to true \ No newline at end of file diff --git a/temporary/bak/cfg/anim-list.json b/temporary/bak/cfg/anim-list.json new file mode 100644 index 0000000..51dbbc4 --- /dev/null +++ b/temporary/bak/cfg/anim-list.json @@ -0,0 +1,105 @@ +{ + "whitefills":[ + { + "name":"Bottom/Up Fill", + "speed":"Speed: ", + "hue-range":"", + "param1":"Time: ", + "param2":"Const Light Delay: ", + "check1":"", + "check2":"", + "check3":"", + "check4":"50% Lum" + } + ], + "animations":[ + { + "name":"Rainbow", + "speed":"Speed: ", + "hue-range":"", + "param1":"", + "param2":"", + "check1":"", + "check2":"", + "check3":"", + "check4":"50% Lum" + }, + { + "name":"Hue Spectrum", + "speed":"Speed: ", + "hue-range":"Hue Range: ", + "param1":"", + "param2":"", + "check1":"Dir Rev ", + "check2":"", + "check3":"", + "check4":"50% Lum" + }, + { + "name":"Color Pulse Cycle", + "speed":"Speed: ", + "hue-range":"Hue Range: ", + "param1":"", + "param2":"Colors: ", + "check1":"", + "check2":"", + "check3":"", + "check4":"50% Lum" + }, + { + "name":"Comets", + "speed":"Speed: ", + "hue-range":"Hue Range: ", + "param1":"Sectors: ", + "param2":"Colors: ", + "check1":"Mixed Colors ", + "check2":"Random Decay", + "check3":"Shorter Tail ", + "check4":"50% Lum" + }, + { + "name":"Dashes", + "speed":"Speed: ", + "hue-range":"Hue Range: ", + "param1":"Sectors: ", + "param2":"Colors: ", + "check1":"Mixed Colors ", + "check2":"Rotate ", + "check3":"", + "check4":"50% Lum" + }, + { + "name":"Snakes", + "speed":"Speed: ", + "hue-range":"Hue Range: ", + "param1":"Sectors: ", + "param2":"Colors: ", + "check1":"Mixed Colors ", + "check2":"Rotate ", + "check3":"", + "check4":"50% Lum" + }, + { + "name":"Sectors", + "speed":"Speed: ", + "hue-range":"Hue Range: ", + "param1":"Sectors: ", + "param2":"Colors: ", + "check1":"", + "check2":"Rotate ", + "check3":"", + "check4":"50% Lum" + }, + { + "name":"Fire", + "speed":"Speed: ", + "hue-range":"Run-time per: ", + "param1":"Cooling: ", + "param2":"Sparking: ", + "check1":"Cycle Colors ", + "check2":"", + "check3":"", + "check4":"50% Lum" + } + ] +} \ No newline at end of file diff --git a/temporary/bak/cfg/anim-profiles.json b/temporary/bak/cfg/anim-profiles.json new file mode 100644 index 0000000..e50c0e4 --- /dev/null +++ b/temporary/bak/cfg/anim-profiles.json @@ -0,0 +1,1203 @@ +{ + "countdown": { + "min": 0, + "max": 98, + "hold": 1000, + "ramp": 500 + }, + "profile-index": 3, + "profiles": [ + { + "name": "Profile 1", + "events": [ + { + "anim": 0, + "hue": -1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 0, + "check1": false, + "check2": true, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": true, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + } + ] + }, + { + "name": "Profile 2", + "events": [ + { + "anim": 0, + "hue": -1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 0, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + } + ] + }, + { + "name": "Profile 3", + "events": [ + { + "anim": 0, + "hue": -1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 0, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + } + ] + }, + { + "name": "Profile 4", + "events": [ + { + "anim": 0, + "hue": -1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 75, + "check1": false, + "check2": true, + "check3": false, + "check4": false + }, + { + "anim": 1, + "hue": 0, + "hue-range": 340, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 3, + "hue": 0, + "hue-range": 340, + "speed": 75, + "param1": 30, + "param2": 60, + "check1": false, + "check2": true, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 40, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + } + ] + }, + { + "name": "Profile 5", + "events": [ + { + "anim": 0, + "hue": -1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 0, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + } + ] + }, + { + "name": "Profile 5", + "events": [ + { + "anim": 0, + "hue": -1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 0, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + } + ] + }, + { + "name": "Wedding 7", + "events": [ + { + "anim": 0, + "hue": -1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 0, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + } + ] + }, + { + "name": "Wedding 8", + "events": [ + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 0, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + } + ] + } + ] +} \ No newline at end of file diff --git a/temporary/bak/cfg/anim-settings.json b/temporary/bak/cfg/anim-settings.json new file mode 100644 index 0000000..3889b0e --- /dev/null +++ b/temporary/bak/cfg/anim-settings.json @@ -0,0 +1,97 @@ +{ + "twinkle":{ + "col1": "#000000", + "col2": "#000000", + "density": 1, + "speed": 25, + "pwr": 255 + }, + "Strobe":{ + "col1": "#000000", + "col2": "#000000", + "density": 1, + "speed": 25, + "pwr": 255 + }, + "Solid":{ + "col1": "#000000", + "col2": "#000000", + "density": 1, + "pwr": 255 + }, + "Fade":{ + "col1": "#000000", + "range": 128, + "speed": 25, + "pwr": 255 + }, + "HueSwirl":{ + "col1": "#000000", + "range": 128, + "speed": 25, + "pwr": 255 + }, + "Meteors":{ + "col1": "#000000", + "col2": "#000000", + "range": 1, + "density": 1, + "speed": 25, + "pwr": 255 + }, + "Dashes":{ + "col1": "#000000", + "col2": "#000000", + "col3": "#000000", + "segments": 1, + "density": 1, + "speed": 25, + "pwr": 255 + }, + "DashesRange":{ + "col1": "#000000", + "col2": "#000000", + "range": 128, + "segments": 1, + "density": 1, + "speed": 25, + "pwr": 255 + }, + "Fire":{ + "col1": "#000000", + "cool": 25, + "spark": 25, + "speed": 25, + "pwr": 255 + }, + "Rain":{ + "col1": "#000000", + "col2": "#000000", + "range": 128, + "density": 1, + "speed": 25, + "pwr": 255 + }, + "Stacking":{ + "col1": "#000000", + "col2": "#000000", + "range": 128, + "speed": 25, + "pwr": 255 + }, + "Snake":{ + "col1": "#000000", + "col2": "#000000", + "range": 128, + "speed": 25, + "pwr": 255 + }, + "Theater":{ + "col1": "#000000", + "col2": "#000000", + "range": 128, + "speed": 25, + "step": 20, + "pwr": 255 + } +} \ No newline at end of file diff --git a/temporary/bak/cfg/app-events.json b/temporary/bak/cfg/app-events.json new file mode 100644 index 0000000..fa7b393 --- /dev/null +++ b/temporary/bak/cfg/app-events.json @@ -0,0 +1,90 @@ +{ + "index": 0, + "apps":[ + { + "name": "DSLR-Booth Configuration", + "events":[ + "White Fill / Countdown", + "Experience Selection", + "Select Sharing Screen", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + { + "name": "FotoFliqs Configuration", + "events":[ + "White Fill / Countdown", + "Experience Selection", + "Preview", + "Notice!", + "Idle / Pause", + "Advertisement", + "Printing", + "Phone Input / QR Code / Apple Drop", + "", + "", + "", + "" + ] + }, + { + "name": "Touchpix Configuration", + "events":[ + "White Fill / Countdown", + "Experience Selection", + "Preview", + "Notice!", + "Idle / Pause", + "Advertisement", + "Printing", + "Phone Input / QR Code / Apple Drop", + "", + "", + "", + "" + ] + }, + { + "name": "FotoZap Configuration", + "events":[ + "White Fill / Countdown", + "Experience Selection", + "Select Sharing Screen", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + { + "name": "Twineit Configuration", + "events":[ + "White Fill / Countdown", + "Experience Selection", + "Select Sharing Screen", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + } + ] +} \ No newline at end of file diff --git a/temporary/bak/cfg/ble.json b/temporary/bak/cfg/ble.json new file mode 100644 index 0000000..8cd73ca --- /dev/null +++ b/temporary/bak/cfg/ble.json @@ -0,0 +1,9 @@ +{ + "en": "true", + "core": 1, + "device-name": "ATA_COMM", + "key": "123456", + "service-uuid": "6E400001-B5A3-F393-E0A9-E50E24DCCA9E", + "char-uuid-rx": "6E400002-B5A3-F393-E0A9-E50E24DCCA9E", + "char-uuid-tx": "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" +} \ No newline at end of file diff --git a/temporary/bak/cfg/buzzer.json b/temporary/bak/cfg/buzzer.json new file mode 100644 index 0000000..fa2cffd --- /dev/null +++ b/temporary/bak/cfg/buzzer.json @@ -0,0 +1,73 @@ +{ + "en":true, + "boot":{ + "cycles": 1, + "pause": 0, + "tune": "Boot:d=16,o=5,b=112:f,g,a,b,b#" + }, + "restart":{ + "cycles": 1, + "pause": 0, + "tune": "Restart:d=16,o=5,b=112:f,g,a,b,b#" + }, + "wifi-conn":{ + "cycles": 1, + "pause": 0, + "tune": "WifiConn:d=16,o=5,b=112:f,a#,c6,f,a#,c6,f,a#,c6,f,a#,c6,f,a#,c6,f,a#,c6" + }, + "wifi-disc":{ + "cycles": 1, + "pause": 0, + "tune": "WifiDisc:d=16,o=5,b=112:f,g,a,b,b#" + }, + "ble-conn":{ + "cycles": 1, + "pause":0, + "tune": "BleConn:d=16,o=5,b=112:f,g,a,b,b#" + }, + "ble-disc":{ + "cycles": 1, + "pause": 0, + "tune": "BleDisc:d=16,o=5,b=112:f,g,a,b,b#" + }, + "click":{ + "cycles": 1, + "pause": 0, + "tune": "Click:d=16,o=5,b=112:f,g,a,b,b#" + }, + "error":{ + "cycles": 1, + "pause": 0, + "tune": "Error:d=16,o=5,b=112:32p,f,g,a,b,b#" + }, + "success":{ + "cycles": 1, + "pause": 0, + "tune": "Success:d=16,o=5,b=112:f,g,a,b,b#" + }, + "download":{ + "cycles": 1, + "pause": 0, + "tune": "Download:d=16,o=5,b=112:f,g,a,b,b#" + }, + "waiting":{ + "cycles": 1, + "pause": 0, + "tune": "Waiting:d=16,o=5,b=112:b" + }, + "beep":{ + "cycles": 1, + "pause": 0, + "tune": "Beep:d=16,o=5,b=112:b" + }, + "test":{ + "cycles": 1, + "pause": 0, + "tune": "Test:d=16,o=5,b=112:f,g,a,b,b#" + }, + "ack":{ + "cycles": 1, + "pause": 0, + "tune": "Ack:d=16,o=5,b=112:b,b#" + } +} \ No newline at end of file diff --git a/temporary/bak/cfg/firmware.json b/temporary/bak/cfg/firmware.json new file mode 100644 index 0000000..d691b5d --- /dev/null +++ b/temporary/bak/cfg/firmware.json @@ -0,0 +1,8 @@ +{ + "firm-index": 0, + "firm-locations":[ + "http://mylocation1", + "http://mylocation2", + "http://mylocation3" + ] +} \ No newline at end of file diff --git a/temporary/bak/cfg/led-devices.json b/temporary/bak/cfg/led-devices.json new file mode 100644 index 0000000..2935910 --- /dev/null +++ b/temporary/bak/cfg/led-devices.json @@ -0,0 +1,42 @@ +{ + "strip1": { + "en": true, + "size": 138, + "chip": "SK6812", + "rgb-order": "rgb", + "shift":0, + "offset": 0, + "power-div": 0, + "pin": 3, + "i2s-ch": 0, + "core": 0 + }, + "strip2": { + "en": false, + "size": 20, + "chip": "SK6812", + "rgb-order": "grb", + "shift":0, + "offset": 0, + "power-div": 0, + "pin": 46, + "i2s-ch": 1, + "core": 0 + }, + "front-light": { + "en": true, + "relay": 0, + "core": 1, + "style": 0, + "delay": 0.75 + }, + "rear-light": { + "en": true, + "relay": 1, + "button": 2, + "min": 0, + "max": 100, + "ramp":3000, + "steps":8 + } +} \ No newline at end of file diff --git a/temporary/bak/cfg/luma-stiks.json b/temporary/bak/cfg/luma-stiks.json new file mode 100644 index 0000000..724076b --- /dev/null +++ b/temporary/bak/cfg/luma-stiks.json @@ -0,0 +1,14 @@ +{ + "stiks":[ + { + "en": true, + "ssid": "lumastikXX", + "mac": 5 + }, + { + "en": true, + "ssid": "lumastikXX", + "mac": 6 + } + ] +} \ No newline at end of file diff --git a/temporary/bak/cfg/relays.json b/temporary/bak/cfg/relays.json new file mode 100644 index 0000000..1ecb9fa --- /dev/null +++ b/temporary/bak/cfg/relays.json @@ -0,0 +1,41 @@ +{ +"relays": + [ + { + "pin": 45, + "freq": 500, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "pin": 48, + "freq": 500, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "pin": 47, + "freq": 500, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + }, + { + "pin": 21, + "freq": 500, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + } + ] +} \ No newline at end of file diff --git a/temporary/bak/cfg/system.json b/temporary/bak/cfg/system.json new file mode 100644 index 0000000..429a075 --- /dev/null +++ b/temporary/bak/cfg/system.json @@ -0,0 +1,37 @@ +{ + "buttons": [ + { + "en": true, + "pin": 8 + }, + { + "en": true, + "pin": 19 + }, + { + "en": true, + "pin": 0 + } + ], + "oled": { + "en": false, + "height": 64, + "width": 128 + }, + "fan": { + "en": true, + "relay": 3 + }, + "t-sensor": { + "en": true, + "addr": 72, + "sp1": 85, + "fan-pwr1": 50, + "sp2": 90, + "fan-pwr2": 100, + "hyst": 1 + }, + "adc": { + "ain1_factor": 1.0 + } +} diff --git a/temporary/bak/cfg/touch-pins.json b/temporary/bak/cfg/touch-pins.json new file mode 100644 index 0000000..d556439 --- /dev/null +++ b/temporary/bak/cfg/touch-pins.json @@ -0,0 +1,30 @@ +{ + "touchpins": + [ + { + "en": true, + "pin": 23, + "min": 100 + }, + { + "en": true, + "pin": 23, + "min": 100 + }, + { + "en": true, + "pin": 23, + "min": 100 + }, + { + "en": true, + "pin": 23, + "min": 100 + }, + { + "en": true, + "pin": 23, + "min": 100 + } + ] +} \ No newline at end of file diff --git a/temporary/bak/cfg/trx433.json b/temporary/bak/cfg/trx433.json new file mode 100644 index 0000000..6f9cac0 --- /dev/null +++ b/temporary/bak/cfg/trx433.json @@ -0,0 +1,10 @@ +{ +"rx433": { + "en": "true", + "pin": 38 + }, + "tx433": { + "en": "true", + "pin": 16 + } +} \ No newline at end of file diff --git a/temporary/bak/cfg/wifi.json b/temporary/bak/cfg/wifi.json new file mode 100644 index 0000000..550020c --- /dev/null +++ b/temporary/bak/cfg/wifi.json @@ -0,0 +1,26 @@ +{ + "wifi": + { + "en": true, + "mdns-name": "atadev", + "host-name": "ATADeviceXX" + }, + "cred1": + { + "ssid": "DPWifi", + "pass": "dave3.14159" + }, + "cred2": + { + "ssid": "Default", + "pass": "password" + }, + "ap": + { + "ssid": "ATA_AP", + "pass": "12345678", + "ip": [192,168,10.1], + "gateway": [192,168,10,200], + "subnet": [255,255,255,0] + } +} \ No newline at end of file diff --git a/temporary/bak/www/edit.html b/temporary/bak/www/edit.html new file mode 100644 index 0000000..af21eb6 --- /dev/null +++ b/temporary/bak/www/edit.html @@ -0,0 +1,144 @@ +{{NAVBAR}} + + + + Edit file + + + + + +

Edit file

+
+ Editing file: {{SAVE_PATH_INPUT}} +
+
+
+ +
+
+ {{SAVE_PATH_INPUT}} + + + + +
+
+
+ + + + + + \ No newline at end of file diff --git a/temporary/bak/www/edit2.html b/temporary/bak/www/edit2.html new file mode 100644 index 0000000..a4edd4a --- /dev/null +++ b/temporary/bak/www/edit2.html @@ -0,0 +1,170 @@ +{{NAVBAR}} + + + + Edit file + + + + + +

Edit file

+ +
+
+
+
+ +
+
+
+ + +
+
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/temporary/bak/www/event-box.js b/temporary/bak/www/event-box.js new file mode 100644 index 0000000..f587666 --- /dev/null +++ b/temporary/bak/www/event-box.js @@ -0,0 +1,420 @@ +class EventBox extends HTMLElement { + constructor() { + super(); + this.attachShadow({ mode: 'open' }); + this.shadowRoot.innerHTML = ` + +
+
+ Event0
+
+
+ + +
+
+ +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ +
+
+ +
+
+ `; + this.Title = this.shadowRoot.querySelector('#center-text'); + this.hueSelect = this.shadowRoot.querySelector('#hue-selector'); + this.AnimationList = this.shadowRoot.getElementById('animation-list'); + this.Speed = this.shadowRoot.getElementById('speed'); + this.HueRange = this.shadowRoot.querySelector('#huerange'); + this.Param1 = this.shadowRoot.querySelector('#param1'); + this.Param2 = this.shadowRoot.querySelector('#param2'); + this.Check1 = this.shadowRoot.querySelector('#check1'); + this.Check2 = this.shadowRoot.querySelector('#check2'); + this.Check3 = this.shadowRoot.querySelector('#check3'); + + this.AnimationListLabel = this.shadowRoot.querySelector('#list-label'); + this.SpeedLabel = this.shadowRoot.querySelector('#speed-label'); + this.HueRangeLabel = this.shadowRoot.querySelector('#huerange-label'); + this.Param1Label = this.shadowRoot.getElementById('param1-label'); + this.Param2Label = this.shadowRoot.getElementById('param2-label'); + this.Check1Label = this.shadowRoot.getElementById('check1-label'); + this.Check2Label = this.shadowRoot.getElementById('check2-label'); + this.Check3Label = this.shadowRoot.getElementById('check3-label'); + + this.AnimationPropsJson; + + this.SpeedCaption = ""; + this.HueRangeCaption = ""; + this.Param1Caption = ""; + this.Param2Caption = ""; + this.Check1Caption = ""; + this.Check2Caption = ""; + this.Check3Caption = ""; + + this.setSpeedCaption(`Speed: `); + this.Speed.min = 0; + this.Speed.max = 100; + + this.setHueRangeCaption(`Hue Range: `); + this.HueRange.min = 0; + this.HueRange.max = 360; + + this.setParam1Caption(`Param1: `); + this.Param1.min = 0; + this.Param1.max = 100; + + this.setParam2Caption(`Param2: `); + this.Param2.min = 0; + this.Param2.max = 100; + + this.setCheck1Caption(`Check1`); + this.setCheck2Caption(`Check2`); + this.setCheck2Caption(`Check3`); + + this.Index = 0; + + this.Speed.addEventListener('input', () => { this.updateSpeedLabel(); }); + this.HueRange.addEventListener('input', () => { this.updateHueRangeLabel(); }); + this.Param1.addEventListener('input', () => { this.updateParam1Label(); }); + this.Param2.addEventListener('input', () => { this.updateParam2Label(); }); + + this.TryButton = this.shadowRoot.querySelector('#try-button'); + this.TryButton.addEventListener('click', () => { + this.handleTryButtonClick(); + }); + + this.AnimationList.addEventListener('change', this.handleAnimationListChange.bind(this)); + } + + setIndex(i){ this.Index = i; } + getIndex(){ return this.Index; } + updateSpeedLabel(){ + if(!this.Speed.hidden){ + this.SpeedLabel.innerHTML = this.SpeedCaption + this.getSpeedValue(); + } + } + updateHueRangeLabel(){ + if(!this.HueRange.hidden){ + this.HueRangeLabel.innerHTML = this.HueRangeCaption + this.getHueRangeValue(); + } + } + updateParam1Label(){ + if(!this.Param1.hidden){ + this.Param1Label.innerHTML = this.Param1Caption + this.getParam1Value(); + } + } + updateParam2Label(){ + if(!this.Param2.hidden){ + this.Param2Label.innerHTML = this.Param2Caption + this.getParam2Value(); + } + } + setTitle(text){ + this.Title.textContent = text; + } + addOptionToList(value, text){ + const optionElement = document.createElement('option'); + optionElement.value = value; + optionElement.textContent = text; + this.AnimationList.appendChild(optionElement); + } + setHidden(hidden){ this.hidden = hidden; } + getHidden(){ return this.hidden; } + setHueValue(value){ this.hueSelect.setHue(value); } + getHueValue(){ return this.hueSelect.getSelectedHue(); } + setSpeedValue(value){ + this.Speed.value = value; + this.updateSpeedLabel(); + } + setSpeedCaption(caption){ + let hid = false; + if(caption.trim() === ""){ + hid = true; + } + this.SpeedCaption = caption; + this.SpeedLabel.innerHTML = caption; + this.Speed.hidden = hid; + this.SpeedLabel.hidden = hid; + } + getSpeedValue(){ return this.Speed.value; } + setHueRangeValue(value){ + this.HueRange.value = value; + this.updateHueRangeLabel() + } + setHueRangeCaption(caption){ + let hid = false; + if(caption.trim() === ""){ + hid = true; + } + this.HueRangeCaption = caption; + this.HueRangeLabel.innerHTML = caption; + this.HueRange.hidden = hid; + this.HueRangeLabel.hidden = hid; + } + getHueRangeValue(){ return this.HueRange.value; } + setParam1Value(value){ + this.Param1.value = value; + this.updateParam1Label() + } + setParam1Caption(caption){ + let hid = false; + if(caption.trim() === ""){ + hid = true; + } + this.Param1Caption = caption; + this.Param1Label.innerHTML = caption; + this.Param1.hidden = hid; + this.Param1Label.hidden = hid; + } + getParam1Value(){ return this.Param1.value; } + setParam2Value(value){ + this.Param2.value = value; + this.updateParam2Label() + } + setParam2Caption(caption){ + let hid = false; + if(caption.trim() === ""){ + hid = true; + } + this.Param2Caption = caption; + this.Param2Label.innerHTML = caption; + this.Param2.hidden = hid; + this.Param2Label.hidden = hid; + } + getParam2Value(){ return this.Param2.value; } + + setCheck1Value(value){ this.Check1.checked = value; } + setCheck1Caption(caption){ + let hid = false; + if(caption.trim() === ""){ + hid = true; + } + this.Check1Label.innerHTML = caption; + this.Check1.hidden = hid; + this.Check1Label.hidden = hid; + } + getCheck1Value(){ return this.Check1.checked; } + + setCheck2Value(value){ this.Check2.checked = value; } + setCheck2Caption(caption){ + let hid = false; + if(caption.trim() === ""){ + hid = true; + } + this.Check2Label.innerHTML = caption; + this.Check2.hidden = hid; + this.Check2Label.hidden = hid; + } + getCheck2Value(){ return this.Check2.checked; } + + setCheck3Value(value){ this.Check3.checked = value; } + setCheck3Caption(caption){ + let hid = false; + if(caption.trim() === ""){ + hid = true; + } + this.Check3Label.innerHTML = caption; + this.Check3.hidden = hid; + this.Check3Label.hidden = hid; + } + getCheck3Value(){ return this.Check3.checked; } + + setAnimationIndex(index){ + this.AnimationList.selectedIndex = index; + const changeEvent = new Event('change'); + this.AnimationList.dispatchEvent(changeEvent); + } + getAnimationIndex(){ + return this.AnimationList.selectedIndex; + } + + handleTryButtonClick() { + const eventIndexValue = this.getIndex(); + const animIndexValue = this.getAnimationIndex(); + const hueValue = this.getHueValue(); + const speedValue = this.getSpeedValue(); + const colorRangeValue = this.getHueRangeValue(); + const param1Value = this.getParam1Value(); + const param2Value = this.getParam2Value(); + const check1Value = this.getCheck1Value(); + const check2Value = this.getCheck2Value(); + const check3Value = this.getCheck3Value(); + + this.dispatchEvent(new CustomEvent('tryClick', { + detail: { + eventIndex: eventIndexValue, + animIndex: animIndexValue, + hue: hueValue, + speed: speedValue, + colorRange: colorRangeValue, + param1: param1Value, + param2: param2Value, + check1: check1Value, + check2: check2Value, + check3: check2Value + } + })); + } + + setAnimationCaptions(propsJson){ + this.AnimationPropsJson = propsJson; + + //add options to list + let x = 0; + this.AnimationPropsJson.forEach(props => { + this.addOptionToList(x, props.name); + x++; + }); + + } + + handleAnimationListChange(){ + const selectedIndex = this.getAnimationIndex(); + const selectedProps = this.AnimationPropsJson[selectedIndex]; + + this.updateControlProps(selectedProps); + } + + updateControlProps(props){ + this.setSpeedCaption(props.speed); + let s = props['hue-range']; + this.setHueRangeCaption(s || ""); + this.setParam1Caption(props.param1 || ""); + this.setParam2Caption(props.param2 || ""); + this.setCheck1Caption(props.check1 || ""); + this.setCheck2Caption(props.check2 || ""); + this.setCheck3Caption(props.check3 || ""); + + this.updateSpeedLabel(); + this.updateHueRangeLabel(); + this.updateParam1Label(); + this.updateParam2Label(); + } + +} + +customElements.define('event-box', EventBox); + \ No newline at end of file diff --git a/temporary/bak/www/failed.html b/temporary/bak/www/failed.html new file mode 100644 index 0000000..a092d0c --- /dev/null +++ b/temporary/bak/www/failed.html @@ -0,0 +1,24 @@ +{{NAVBAR}} + + + + Update Failed + + + + + +
+

The update has failed.

+
+ +
+ + \ No newline at end of file diff --git a/temporary/bak/www/files.html b/temporary/bak/www/files.html new file mode 100644 index 0000000..b0f7f69 --- /dev/null +++ b/temporary/bak/www/files.html @@ -0,0 +1,311 @@ +{{NAVBAR}} + + + + File Manager + + + + + + + +

File Manager

+ +
+ File list +
+

  Total: {{FS_TOTAL_BYTES}}, Used: {{FS_USED_BYTES}}, Available: {{FS_FREE_BYTES}}

+
+ + {{LISTED_FILES}} +
Listing: Size:
+
+
+ +
+ +
+ File upload +
+
+ + + + + + + + + + + +
+
+    + +
+
+
+ +
+
+
+ +
+ +
+ Edit file +
+
+ + + +
+
+
+
+ +
+ +
+ Delete file +
+
+ + + +
+
+
+
+ +
+ + + +

Firmware/File System Update

+ +
+ Firmware Update (Local) | Firmware Ver: {{FIRM_VER}}
File name fwataVxxx.bin or lfsataVxxx.bin
+
+
+ + + + + +
+ + + +
+ +
+ + +
+ + + +
+
+
+ +
+ + +
+
+
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/temporary/bak/www/global-style.css b/temporary/bak/www/global-style.css new file mode 100644 index 0000000..442f988 --- /dev/null +++ b/temporary/bak/www/global-style.css @@ -0,0 +1,74 @@ +body { + /*background-color: #f7f7f7;*/ + font-family: Tahoma, Arial, sans-serif; + font-size: small; + margin: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; +} +h1 { + margin-top: 0; +} +input{ + cursor:pointer; +} +input[type="number"]{ + width: 100px; +} +#submit { + width:120px; +} +select{ + width:160px; +} +#select-path { + width:250px; +} +#select-dir { + width:90px; +} +#spacer-50 { + height: 50px; +} +#spacer-20 { + height: 20px; +} +#spacer-10 { + height: 10px; +} +table { + /*background-color: #dddddd;*/ + border-collapse: collapse; + width:600px; + margin: 0 auto; + overflow: visible; +} +td, th { + /*border: 1px solid #dddddd;*/ + text-align: left; + padding: 2px; +} +#first_td_th { + width:400px; +} +fieldset { + width:620px; + /*background-color: #f7f7f7;*/ + border-radius: 10px; + border-color: blue; +} +#format-notice { + color: #ff0000; +} +legend { + display: flex; + justify-content: center; + background-color:white; + background-blend-mode: darken; + border-radius: 10px; + padding: 1px 8px 2px 8px; + border-style: solid; + border-width: 1.0; +} \ No newline at end of file diff --git a/temporary/bak/www/home.html b/temporary/bak/www/home.html new file mode 100644 index 0000000..86a0873 --- /dev/null +++ b/temporary/bak/www/home.html @@ -0,0 +1,271 @@ +{{NAVBAR}} + + + + + + + Welcome + + + + +

ATA Booth Summary

+ +
+ Booth Overview +
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + +
+
+
+ ... + + ... + + +
+
+
+ +
+ +
+ System Info: +
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + +
+
+
+ + + ... + + +
+
+
+ +
+ +
+ Network / WiFi +
+ + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + +
+
+
+ +
+ +
+ Bluetooth LE +
+ + + + + + + +
+ + + + + +
+
+
+ + + + \ No newline at end of file diff --git a/temporary/bak/www/hue-select.js b/temporary/bak/www/hue-select.js new file mode 100644 index 0000000..bcc8132 --- /dev/null +++ b/temporary/bak/www/hue-select.js @@ -0,0 +1,275 @@ + +// Define the HueSelect custom element +class HueSelect extends HTMLElement { + constructor() { + super(); + this.attachShadow({ mode: 'open' }); + this.shadowRoot.innerHTML = ` + + + `; + + this.currentHue = 0; + this.colorList; + this.hueLabel = this.shadowRoot.getElementById('hue-label'); + + const selectedColorDiv = this.shadowRoot.getElementById('selectedColor'); + const colorPatch = selectedColorDiv.querySelector('.color-patch'); + + /* colorPatch.addEventListener('mouseenter', () => { + const dropdownContent = this.shadowRoot.querySelector('.dropdown-content'); + dropdownContent.style.display = 'block'; + }); */ + + // Add the global click event listener to hide the color picker dropdown + /* + window.addEventListener('click', (event) => { + const dropdownContent = this.shadowRoot.querySelector('.dropdown-content'); + if (!event.target.closest('.dropdown')) { + dropdownContent.style.display = 'none'; + } + }); */ + + selectedColorDiv.addEventListener('click', () => { + const dropdownContent = this.shadowRoot.querySelector('.dropdown-content'); + dropdownContent.style.display = dropdownContent.style.display === 'block' ? 'none' : 'block'; + }); + + // Generate the color options and add them to the dropdown + this.createColorOptions(); + this.setHue(0); + } + + generateColors() { + const colors = []; + for (let hue = 0; hue <= 360; hue += 10) { + if (hue == 360) { hue = 359; } + colors.push(hue); + } + + colors.push(-1); + colors.push(-2); + return colors; + } + + createColorOption(hue) { + const colorOption = document.createElement('div'); + colorOption.classList.add('color-option'); + + const colorPatch = document.createElement('span'); + colorPatch.classList.add('color-patch'); + + const colorText = document.createElement('span'); + colorText.classList.add('color-text'); + colorText.textContent = hue; + + const rgbHex = document.createElement('span'); + rgbHex.classList.add('rgb-hex'); + + if (hue === -2) { + colorPatch.style.backgroundColor = 'rgb(0,0,0)'; + rgbHex.innerHTML = '  #000000'; + } else if (hue === -1) { + colorPatch.style.backgroundColor = 'rgb(255,255,255)'; + rgbHex.innerHTML = '  #FFFFFF'; + } else { + colorPatch.style.backgroundColor = `hsl(${hue}, 100%, 50%)`; + const hexColor = this.hslToRgb(hue, 100, 50).toUpperCase(); + rgbHex.innerHTML = `  ${hexColor}`; + } + + colorOption.appendChild(colorPatch); + colorOption.appendChild(colorText); + colorOption.appendChild(rgbHex); + + // Add click event listener to each color option + colorOption.addEventListener('click', () => this.handleColorSelection(hue)); + + return colorOption; + } + + createColorOptions() { + const dropdownContent = this.shadowRoot.querySelector('.dropdown-content'); + this.colorList = this.generateColors(); + + this.colorList.forEach(hue => { + const colorOption = this.createColorOption(hue); + dropdownContent.appendChild(colorOption); + }); + + // Create elements for white and black colors + //const whiteColorOption = this.createColorOption(-1); + //dropdownContent.appendChild(whiteColorOption); + + //const blackColorOption = this.createColorOption(-2); + //dropdownContent.appendChild(blackColorOption); + } + + // Function to convert HSL to RGB + hslToRgb(h, s, l) { + h /= 360; + s /= 100; + l /= 100; + let r, g, b; + if (s === 0) { + r = g = b = l; // achromatic + } else { + const hue2rgb = (p, q, t) => { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; + return p; + }; + const q = l < 0.5 ? l * (1 + s) : l + s - l * s; + const p = 2 * l - q; + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + const toHex = (x) => { + const hex = Math.round(x * 255).toString(16); + return hex.length === 1 ? '0' + hex : hex; + }; + return `#${toHex(r)}${toHex(g)}${toHex(b)}`; + } + + + handleColorSelection(hue) { + this.currentHue = hue; + const selectedColorDiv = this.shadowRoot.getElementById('selectedColor'); + const colorPatch = selectedColorDiv.querySelector('.color-patch'); + + // Update the color patch and hue label + const rgb = this.getRGBfromHue(hue); + colorPatch.style.backgroundColor = rgb; + //console.log(colorPatch.style.backgroundColor); + this.setHueLabel(hue); + this.hideDropdown(); + + // Dispatch a 'change' event with the selected hue value + this.dispatchEvent(new CustomEvent('change', { detail: { hue, rgb } })); + } + + // Method to get the hue value of the selected item + getSelectedHue() { + return this.currentHue; + } + + getSelectedRGB() { + const selectedColorText = this.shadowRoot.getElementById('selectedColor').textContent.trim(); + return parseFloat(selectedColorText); + } + + getRGBfromHue(hue){ + if(hue == -1){ + return '#FFFFFF'; + }else if(hue == -2){ + return '#000000'; + }else{ + return this.hslToRgb(hue, 100, 50);; + } + } + // Method to get the RGB value of the selected item + getSelectedRgb() { + const selectedHue = this.getSelectedHue(); + return getRGBfromHue(selectedHue); + } + + setHue(hue) { + // Round the input hue to the nearest 10th + let roundedHue = hue; + if(hue === -1 || hue === -2){ + roundedHue = hue; + }else{ + roundedHue = Math.round(hue / 10) * 10; + if (roundedHue >= 360) { + roundedHue = 359; + } + + if (roundedHue < 0) { + roundedHue = 0; + } + } + + this.currentHue = roundedHue; + + this.colorList.forEach(colorVal => { + if (colorVal === roundedHue) { + this.handleColorSelection(roundedHue); + } + }); + + this.hideDropdown(); + } + + hideDropdown() { + const dropdownContent = this.shadowRoot.querySelector('.dropdown-content'); + dropdownContent.style.display = 'none'; + } + + setHueLabel(value){ + this.hueLabel.textContent = "Hue: " + value; + } + + + } + + + + // Register the custom element + customElements.define('hue-select', HueSelect); + \ No newline at end of file diff --git a/temporary/bak/www/label.html b/temporary/bak/www/label.html new file mode 100644 index 0000000..6593d48 --- /dev/null +++ b/temporary/bak/www/label.html @@ -0,0 +1,56 @@ + + + + + + +Event Container + + + +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/temporary/bak/www/lights.html b/temporary/bak/www/lights.html new file mode 100644 index 0000000..24814d8 --- /dev/null +++ b/temporary/bak/www/lights.html @@ -0,0 +1,452 @@ +{{NAVBAR}} + + + + + + + + + + + + +
+

Lights Configuration

+ +
+ Saved Animation Profiles +
+
+ + +
+
+ + + +
+
+
+
+ +
+
+ Countdown ( Constant Light ) +
+
+ + +
+
+ + +
+
+
+
+
+ +
+
+
+ + +
+
+
+
+ +
+ + + + diff --git a/temporary/bak/www/navbar.html b/temporary/bak/www/navbar.html new file mode 100644 index 0000000..3c23772 --- /dev/null +++ b/temporary/bak/www/navbar.html @@ -0,0 +1,55 @@ + + + + + + +
+ + \ No newline at end of file diff --git a/temporary/bak/www/ok.html b/temporary/bak/www/ok.html new file mode 100644 index 0000000..a5263dd --- /dev/null +++ b/temporary/bak/www/ok.html @@ -0,0 +1,24 @@ +{{NAVBAR}} + + + + Update Success + + + + + +
+

The update was successful.

+
+ +
+ + \ No newline at end of file diff --git a/temporary/bak/www/setup.html b/temporary/bak/www/setup.html new file mode 100644 index 0000000..2cfaa4e --- /dev/null +++ b/temporary/bak/www/setup.html @@ -0,0 +1,355 @@ +{{NAVBAR}} + + + + Booth Configuration Tools + + + + + + +

System Setup

+
+ +
+ LED Strip #1 Settings +
+
+
+ +
+
+ +
+
+ +
+
+
+ +
+ LED Strip #1 Settings +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ + +
+ Test Tool + +
+
+ +
+
+ +
+ +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ + +
+ Luma Stiks + +
+
+
+ +
+
+
+ +
+
+ +
+
+
+ +
+
+
+ +
+
+ +
+
+
+ +
+
+
+ +
+
+ +
+
+ +
+ +
+
+ +
+ + + + \ No newline at end of file diff --git a/temporary/bak/www/wifi.html b/temporary/bak/www/wifi.html new file mode 100644 index 0000000..58f9781 --- /dev/null +++ b/temporary/bak/www/wifi.html @@ -0,0 +1,127 @@ + + + + + WiFi Credentials + + + +

Enter WiFi Credentials

+
+ + + + + + Image not found +
+ + + + diff --git a/temporary/bleconfig.html b/temporary/bleconfig.html new file mode 100644 index 0000000..fac456f --- /dev/null +++ b/temporary/bleconfig.html @@ -0,0 +1,16 @@ +{{NAVBAR}} + + + + BLE Config + + + + + + +

App Control - Bluetooth Configuration

+ + \ No newline at end of file diff --git a/temporary/colorPicket.html b/temporary/colorPicket.html new file mode 100644 index 0000000..fe07814 --- /dev/null +++ b/temporary/colorPicket.html @@ -0,0 +1,31 @@ + + + +Color Picker + + + +
+

Countdown Animation

+
+ + + + +
+

Selection Screen

+
+ +
+ + + diff --git a/temporary/eventTest.htm b/temporary/eventTest.htm new file mode 100644 index 0000000..b30e777 --- /dev/null +++ b/temporary/eventTest.htm @@ -0,0 +1,145 @@ + + + + + + +Event Container + + + +
+
+ Event0
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ Mix  + Fwd +
+
+ +
+
+ +
+
+ + diff --git a/temporary/gridstyles.css b/temporary/gridstyles.css new file mode 100644 index 0000000..bf326f6 --- /dev/null +++ b/temporary/gridstyles.css @@ -0,0 +1,6 @@ +.container{ + color:blue; + display: grid; + gap: 1rem; + grid-template-columns: 50% 1fr 1fr; +} \ No newline at end of file diff --git a/temporary/gridtest.html b/temporary/gridtest.html new file mode 100644 index 0000000..872e384 --- /dev/null +++ b/temporary/gridtest.html @@ -0,0 +1,12 @@ + + + CSS Grid + + +
+
Test text 1
+
Test text 1
+
Test text 1
+
Test text 1
+
+ \ No newline at end of file diff --git a/temporary/hue-select-min.js b/temporary/hue-select-min.js new file mode 100644 index 0000000..e25f5aa --- /dev/null +++ b/temporary/hue-select-min.js @@ -0,0 +1 @@ +class HueSelect extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this.shadowRoot.innerHTML='\n\t\t\n\t\t\n\t ',this.currentHue=0,this.colorList,this.hueLabel=this.shadowRoot.getElementById("hue-label");this.shadowRoot.getElementById("selectedColor").querySelector(".color-patch").addEventListener("mouseenter",(()=>{this.shadowRoot.querySelector(".dropdown-content").style.display="block"})),window.addEventListener("click",(t=>{const e=this.shadowRoot.querySelector(".dropdown-content");t.target.closest(".dropdown")||(e.style.display="none")})),this.createColorOptions(),this.setHue(0)}generateColors(){const t=[];for(let e=0;e<=360;e+=10)360==e&&(e=359),t.push(e);return t.push(-1),t.push(-2),t}createColorOption(t){const e=document.createElement("div");e.classList.add("color-option");const o=document.createElement("span");o.classList.add("color-patch");const n=document.createElement("span");n.classList.add("color-text"),n.textContent=t;const s=document.createElement("span");if(s.classList.add("rgb-hex"),-2===t)o.style.backgroundColor="rgb(0,0,0)",s.innerHTML="  #000000";else if(-1===t)o.style.backgroundColor="rgb(255,255,255)",s.innerHTML="  #FFFFFF";else{o.style.backgroundColor=`hsl(${t}, 100%, 50%)`;const e=this.hslToRgb(t,100,50).toUpperCase();s.innerHTML=`  ${e}`}return e.appendChild(o),e.appendChild(n),e.appendChild(s),e.addEventListener("click",(()=>this.handleColorSelection(t))),e}createColorOptions(){const t=this.shadowRoot.querySelector(".dropdown-content");this.colorList=this.generateColors(),this.colorList.forEach((e=>{const o=this.createColorOption(e);t.appendChild(o)}))}hslToRgb(t,e,o){let n,s,l;if(t/=360,o/=100,0===(e/=100))n=s=l=o;else{const r=(t,e,o)=>(o<0&&(o+=1),o>1&&(o-=1),o<1/6?t+6*(e-t)*o:o<.5?e:o<2/3?t+(e-t)*(2/3-o)*6:t),i=o<.5?o*(1+e):o+e-o*e,d=2*o-i;n=r(d,i,t+1/3),s=r(d,i,t),l=r(d,i,t-1/3)}const r=t=>{const e=Math.round(255*t).toString(16);return 1===e.length?"0"+e:e};return`#${r(n)}${r(s)}${r(l)}`}handleColorSelection(t){this.currentHue=t;const e=this.shadowRoot.getElementById("selectedColor").querySelector(".color-patch"),o=this.getRGBfromHue(t);e.style.backgroundColor=o,console.log(e.style.backgroundColor),this.setHueLabel(t),this.hideDropdown(),this.dispatchEvent(new CustomEvent("change",{detail:{hue:t,rgb:o}}))}getSelectedHue(){return this.currentHue}getSelectedRGB(){const t=this.shadowRoot.getElementById("selectedColor").textContent.trim();return parseFloat(t)}getRGBfromHue(t){return-1==t?"#FFFFFF":-2==t?"#000000":this.hslToRgb(t,100,50)}getSelectedRgb(){const t=this.getSelectedHue();return getRGBfromHue(t)}setHue(t){let e=t;-1===t||-2===t?e=t:(e=10*Math.round(t/10),e>=360&&(e=359),e<0&&(e=0)),this.currentHue=e,this.colorList.forEach((t=>{t===e&&this.handleColorSelection(e)})),this.hideDropdown()}hideDropdown(){this.shadowRoot.querySelector(".dropdown-content").style.display="none"}setHueLabel(t){this.hueLabel.textContent="Hue: "+t}}customElements.define("hue-select",HueSelect); \ No newline at end of file diff --git a/temporary/lights-old.html b/temporary/lights-old.html new file mode 100644 index 0000000..62a71d5 --- /dev/null +++ b/temporary/lights-old.html @@ -0,0 +1,755 @@ +{{NAVBAR}} + + + + App Control Configuration + + + + + + +

App Control Configuration

+ +
+ Saved Animation Profiles + + + + +
+
+ + +    + + +     + +
+
+ + +
+ + +
+ Countdown ( Constant Light ) + + + + + + + + + + + + + + +
+
+ +
+
+ +
+ +
+
+
+
+ +
+
+ +
+
+
+
+ +
+ +
+ Event0 + + + + + + + + + +
+
+ +
+
+   +
+
+   +
+
+ +
+
+ +
+ +
+
+ +
+ +
+ Event1 + + + + + + + + + +
+
+ +
+
+   +
+
+   +
+
+ +
+
+ +
+ +
+
+ +
+ +
+ Event2 + + + + + + + + + +
+
+ +
+
+   +
+
+   +
+
+ +
+
+ +
+ +
+
+ +
+ +
+ Event3 + + + + + + + + + +
+
+ +
+
+   +
+
+   +
+
+ +
+
+ +
+ +
+
+ +
+ +
+ Event4 + + + + + + + + + +
+
+ +
+
+   +
+
+   +
+
+ +
+
+ +
+ +
+
+ +
+ +
+ Event5 + + + + + + + + + +
+
+ +
+
+   +
+
+   +
+
+ +
+
+ +
+ +
+
+ +
+ +
+ Event6 + + + + + + + + + +
+
+ +
+
+   +
+
+   +
+
+ +
+
+ +
+ +
+
+ +
+ +
+ Event7 + + + + + + + + + +
+
+ +
+
+   +
+
+   +
+
+ +
+
+ +
+ +
+
+ +
+ +
+ Event8 + + + + + + + + + +
+
+ +
+
+   +
+
+   +
+
+ +
+
+ +
+ +
+
+ +
+ +
+ Event9 + + + + + + + + + +
+
+ +
+
+   +
+
+   +
+
+ +
+
+ +
+ +
+
+ + +
+ +
+ Event10 + + + + + + + + + +
+
+ +
+
+   +
+
+   +
+
+ +
+
+ +
+ +
+
+ +
+ +
+ Event11 + + + + + + + + + +
+
+ +
+
+   +
+
+   +
+
+ +
+
+ +
+ +
+
+ + + + diff --git a/temporary/log.html b/temporary/log.html new file mode 100644 index 0000000..8684117 --- /dev/null +++ b/temporary/log.html @@ -0,0 +1,10 @@ + + + + + System Log + + +

System Log

+ + \ No newline at end of file diff --git a/temporary/neo_colors.h b/temporary/neo_colors.h new file mode 100644 index 0000000..0ffaf31 --- /dev/null +++ b/temporary/neo_colors.h @@ -0,0 +1,78 @@ +#ifndef NEO_COLORS_H +#define NEO_COLORS_H + +#include + +#define USE_CORRECTED_COLORS 0 + +const RgbColor col_black(0, 0, 0); +const RgbColor col_white(255, 255, 255); +const RgbColor col_red(255, 0, 0); +const RgbColor col_green(0, 255, 0); +const RgbColor col_blue(0, 0, 255); + +#if USE_CORRECTED_COLORS == 0 + +const RgbColor col_orange(255, 165, 0); +const RgbColor col_yellow(255, 255, 0); +const RgbColor col_cyan(0, 255, 255); +const RgbColor col_magenta(255, 0, 255); +const RgbColor col_purple(128, 0, 128); +const RgbColor col_pink(255, 192, 203); +const RgbColor col_teal(0, 128, 128); +const RgbColor col_lime(0, 255, 0); +const RgbColor col_indigo(75, 0, 130); +const RgbColor col_maroon(128, 0, 0); +const RgbColor col_navy(0, 0, 128); +const RgbColor col_olive(128, 128, 0); +const RgbColor col_beige(245, 245, 220); +const RgbColor col_brown(165, 42, 42); +const RgbColor col_coral(255, 127, 80); +const RgbColor col_gold(255, 215, 0); +const RgbColor col_gray(128, 128, 128); +const RgbColor col_ivory(255, 255, 240); +const RgbColor col_khaki(240, 230, 140); +const RgbColor col_lavender(230, 230, 250); +const RgbColor col_peach(255, 218, 185); +const RgbColor col_periwinkle(204, 204, 255); +const RgbColor col_salmon(250, 128, 114); +const RgbColor col_sienna(160, 82, 45); +const RgbColor col_silver(192, 192, 192); +const RgbColor col_tan(210, 180, 140); +const RgbColor col_turquoise(64, 224, 208); +const RgbColor col_violet(238, 130, 238); + +#else + +const RgbColor col_orange(255, 128, 0); +const RgbColor col_yellow(255, 255, 0); +const RgbColor col_cyan(0, 255, 255); +const RgbColor col_magenta(255, 0, 255); +const RgbColor col_purple(170, 0, 255); +const RgbColor col_pink(255, 170, 255); +const RgbColor col_teal(0, 128, 128); +const RgbColor col_lime(128, 255, 0); +const RgbColor col_indigo(85, 0, 255); +const RgbColor col_maroon(128, 0, 0); +const RgbColor col_navy(0, 0, 128); +const RgbColor col_olive(128, 128, 0); +const RgbColor col_beige(255, 230, 204); +const RgbColor col_brown(153, 51, 0); +const RgbColor col_coral(255, 102, 102); +const RgbColor col_gold(204, 153, 0); +const RgbColor col_gray(128, 128, 128); +const RgbColor col_ivory(255, 255, 204); +const RgbColor col_khaki(204, 204, 0); +const RgbColor col_lavender(204, 153, 255); +const RgbColor col_peach(255, 204, 153); +const RgbColor col_periwinkle(153, 153, 255); +const RgbColor col_salmon(255, 153, 102); +const RgbColor col_sienna(153, 76, 0); +const RgbColor col_silver(204, 204, 204); +const RgbColor col_tan(204, 153, 102); +const RgbColor col_turquoise(0, 204, 204); +const RgbColor col_violet(204, 0, 255); + +#endif + +#endif \ No newline at end of file diff --git a/temporary/styles.css b/temporary/styles.css new file mode 100644 index 0000000..94eac3f --- /dev/null +++ b/temporary/styles.css @@ -0,0 +1,50 @@ +.blue { + color: blue; +} + +h1{ + color: yellow; +} + +body{ + color: red; +} + +.secTitleFont{ + color: white; + +} +.secTitle{ + min-width: none; + height: 25px; + width: auto; + padding: 2px; + margin: 2px; + display: flex; + justify-content: center; + align-items: center; + /*border: 3px solid black;*/ + color: rgb(56, 132, 255); + background-color: rgb(61, 118, 153); + font-family: Tahoma, Arial, sans-serif; + font-size: smaller; + font-weight: bold; + flex-direction: ; +} + +.secCntrls{ + height: 100px; + width: auto; + margin: 5px; + border: 1px solid rgb(255, 255, 255); + color: rgba(255, 255, 255, 0); + font-family: Tahoma, Arial, sans-serif; + /*display: flex; + justify-content: center; + align-items: center; + position: relative;*/ +} + +.dropList{ + margin-right: 40px; +} \ No newline at end of file diff --git a/temporary/upgrade.html b/temporary/upgrade.html new file mode 100644 index 0000000..7afb28a --- /dev/null +++ b/temporary/upgrade.html @@ -0,0 +1,228 @@ + + + + Firmware Update + + + + + + + + + +

Firmware Update

+ +
+
+ Local Update +
+
+ +
+
+ +
+
+
+ +
+
+
+ +
+
+ Web Update +
+ +
+ +
+ +
+ +
+
+ +
+
+ Update Progress +
+ + + +
+
+
+ + + + \ No newline at end of file diff --git a/test/README b/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html diff --git a/webSock/AppUpgrade.cpp b/webSock/AppUpgrade.cpp new file mode 100644 index 0000000..d3a2857 --- /dev/null +++ b/webSock/AppUpgrade.cpp @@ -0,0 +1,522 @@ +#include "AppUpgrade.h" +#include "esp_log.h" +#include +#include +#include +#include "global.h" +#include "jsonConstrain.h" + +static const char* TAG = "AppUpdater"; +TaskHandle_t Update_Task_Handle = NULL; + +// Queue handle for firmware update messages +//QueueHandle_t updateMsgQueue = NULL; + + +bool Version::operator<(const Version& other) const { + if (major != other.major) return major < other.major; + if (minor != other.minor) return minor < other.minor; + return patch < other.patch; +} + +String Version::toString() const { + return String(major) + "." + String(minor) + "." + String(patch); +} + +AppUpdater::AppUpdater(fs::FS& fs, const char* currVersion, const char* bucket, const char* manifestName, const char* appBin) + : currentVersion(currVersion), bucketUrl(bucket), manifestName(manifestName), appName(appBin), fileSystem(fs), downloadBuffer(new uint8_t[BUFFER_SIZE]) +{ + ESP_LOGI(TAG, "AppUpdater initialized with version %s", currVersion); +} + +void AppUpdater::setProgressCallback(void (*callback)( UpdateStatus status, int percentage, const char* message)) { + progressCb = callback; +} + +void AppUpdater::updateProgress(UpdateStatus newStatus, int percentage, const char* message) { + status = newStatus; + if (progressCb) { + progressCb(status, percentage, message); + } +} + +bool AppUpdater::checkManifest() { + String url = String(bucketUrl) + manifestName; + ESP_LOGD(TAG, "Fetching manifest from: %s", url.c_str()); + + // Start the HTTP client and Send GET request for manifest + HTTPClient http; + http.begin(url); + int httpCode = http.GET(); + if (httpCode != HTTP_CODE_OK) { + ESP_LOGE(TAG, "HTTP GET failed, error: %d", httpCode); + http.end(); + return false; + } + + // Read the response + String payload = http.getString(); + http.end(); + + // Parse JSON + DeserializationError error = deserializeJson(jsonManifest, payload); + if (error) { + ESP_LOGE(TAG, "Failed to parse manifest: %s", error.c_str()); + return false; + } + + // Check for version section + JsonObject jsonVersion = jsonManifest["version"]; + if (jsonVersion.isNull()) { + ESP_LOGE(TAG, "No version section in manifest"); + return false; + } + + // Get the remote version + int major = jsonVersion["major"] | 0; + int minor = jsonVersion["minor"] | 0; + int patch = jsonVersion["patch"] | 0; + Version remoteVersion = {major, minor, patch}; + + Version localVersion; + sscanf(currentVersion, "%d.%d.%d", &localVersion.major, &localVersion.minor, &localVersion.patch); + + // Check if an update is available + if (remoteVersion < localVersion) { + ESP_LOGI(TAG, "No updates available"); + return false; + } + + ESP_LOGD(TAG, "Manifest content: %s", payload.c_str()); + + return true; +} + +bool AppUpdater::updateFile(const char* remotePath, const char* localPath, const char* expectedMd5) { + updateProgress(UpdateStatus::DOWNLOADING, 0, localPath); + + // Construct full URL + String url = String(bucketUrl) + remotePath; + ESP_LOGD(TAG, "Downloading: %s -> %s", url.c_str(), localPath); + + String localMd5 = getLocalMD5(localPath); + + if (localMd5.equals(expectedMd5)) { + ESP_LOGI(TAG, "File already up to date: %s", localPath); + updateProgress(UpdateStatus::SKIPPING, 100, localPath); + return true; + } + + // Start the download + HTTPClient http; + http.begin(url); + int httpCode = http.GET(); + if (httpCode != HTTP_CODE_OK) { + ESP_LOGE(TAG, "Download failed: %d", httpCode); + updateProgress(UpdateStatus::ERROR, 0); + http.end(); + return false; + } + + // Get the stream and content length + WiFiClient* stream = http.getStreamPtr(); + size_t contentLength = http.getSize(); + + // Verify and save the file + bool success = verifyAndSaveFile(stream, contentLength, localPath, expectedMd5); + http.end(); + + updateProgress(success ? UpdateStatus::COMPLETE : UpdateStatus::ERROR, 100, localPath); + return success; +} + +bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, const char* localPath, const char* expectedMd5) +{ + MD5Builder md5; + md5.begin(); + size_t totalRead = 0; + + // Create temporary filename + String tempPath = String(localPath) + ".tmp"; + + // Open temporary file for writing + File file = fileSystem.open(tempPath.c_str(), FILE_WRITE); + if (!file) { + ESP_LOGE(TAG, "Failed to open temporary file for writing"); + return false; + } + + updateProgress(UpdateStatus::DOWNLOADING, 0, localPath); + + // Single pass: Save file and calculate MD5 + while (totalRead < contentLength) { + size_t available = stream->available(); + if (available) { + size_t readLen = stream->readBytes(downloadBuffer.get(), std::min(available, size_t(BUFFER_SIZE))); + + // Write to temp file and update MD5 + if (file.write(downloadBuffer.get(), readLen) != readLen) { + ESP_LOGE(TAG, "Failed to write to temporary file"); + + file.close(); + fileSystem.remove(tempPath.c_str()); + return false; + } + + md5.add(downloadBuffer.get(), readLen); + totalRead += readLen; + updateProgress(UpdateStatus::DOWNLOADING, (totalRead * 90) / contentLength , localPath); + } + yield(); + } + + file.close(); + md5.calculate(); + String calculatedMd5 = md5.toString(); + + // Verify MD5 hash + if (!calculatedMd5.equals(expectedMd5)) { + ESP_LOGE(TAG, "MD5 mismatch for %s", localPath); + fileSystem.remove(tempPath.c_str()); + return false; + } + + // Replace original file with verified temp file + if (fileSystem.exists(localPath)) { + fileSystem.remove(localPath); + } + if (!fileSystem.rename(tempPath.c_str(), localPath)) { + ESP_LOGE(TAG, "Failed to rename temporary file"); + fileSystem.remove(tempPath.c_str()); + return false; + } + + updateProgress(UpdateStatus::COMPLETE, 100, localPath); + return true; +} + +String AppUpdater::getLocalMD5(const char* filePath){ + File file = fileSystem.open(filePath, "r"); + if(!file){ + ESP_LOGE(TAG, "Error opening %s...", filePath); + return String(); + } + + MD5Builder md5Builder; + md5Builder.begin(); + size_t fileSize = file.size(); + size_t totalRead = 0; + size_t readLen = 0; + while (totalRead < fileSize) { + readLen = file.readBytes(reinterpret_cast(downloadBuffer.get()), std::min(fileSize - totalRead, size_t(BUFFER_SIZE))); + md5Builder.add(downloadBuffer.get(), readLen); + totalRead += readLen; + } + + md5Builder.calculate(); + file.close(); + return md5Builder.toString(); +} + +bool AppUpdater::updateFilesArray() { + int successCount = 0; + int totalFiles = jsonFilesArray.size(); + ESP_LOGI(TAG, "Found %d files in manifest", totalFiles); + + // Iterate over each file entry in the manifest + for (JsonObject file : jsonFilesArray) { + const char* remotePath = file["remote"]; + const char* localPath = file["local"]; + const char* expectedMd5 = file["md5"]; + + // Skip invalid entries + if (!remotePath || !localPath || !expectedMd5) { + ESP_LOGE(TAG, "Invalid file entry in manifest"); + continue; + } + + // Attempt to update the file + if (updateFile(remotePath, localPath, expectedMd5)) { + successCount++; + } + } + + ESP_LOGI(TAG, "Manifest update complete: %d/%d files updated", successCount, totalFiles); + return successCount == totalFiles; +} + +bool AppUpdater::updateApp() { + updateProgress(UpdateStatus::MESSAGE, 0, "Starting firmware update"); + + // Check for firmware section in manifest + if (!jsonManifest["firmware"].is() || !jsonManifest["firmware"]["md5"].is()) { + ESP_LOGE(TAG, "Invalid firmware section in manifest"); + updateProgress(UpdateStatus::MESSAGE, 0, "Firmware: Invalid firmware section in manifest"); + return false; + } + + // Get the firmware MD5 hash and URL + const char* expectedMd5 = jsonManifest["firmware"]["md5"]; + String firmwareUrl = String(bucketUrl) + appName; + + // Download the firmware + HTTPClient http; + http.begin(firmwareUrl); + int httpCode = http.GET(); + if (httpCode != HTTP_CODE_OK) { + ESP_LOGE(TAG, "Firmware download failed: %d", httpCode); + updateProgress(UpdateStatus::MESSAGE, 0, "Firmware: Firmware download failed"); + http.end(); + return false; + } + + // Check available space + size_t firmwareSize = http.getSize(); + if (!Update.begin(firmwareSize)) { + ESP_LOGE(TAG, "Firmware: Not enough space for update"); + updateProgress(UpdateStatus::MESSAGE, 0, "Firmware: Not enough space for update"); + http.end(); + return false; + } + + // Set up MD5 checking + MD5Builder md5; + md5.begin(); + + // Download and verify firmware + WiFiClient* stream = http.getStreamPtr(); + size_t remaining = firmwareSize; + while (remaining > 0) { + size_t chunk = std::min(remaining, size_t(BUFFER_SIZE)); + size_t read = stream->readBytes(downloadBuffer.get(), chunk); + + // Check for timeout + if (read == 0) { + ESP_LOGE(TAG, "Read timeout"); + + Update.abort(); + http.end(); + return false; + } + + // Update MD5 and write firmware + md5.add(downloadBuffer.get(), read); + if (Update.write(downloadBuffer.get(), read) != read) { + ESP_LOGE(TAG, "Write failed"); + Update.abort(); + http.end(); + return false; + } + + remaining -= read; + updateProgress(UpdateStatus::DOWNLOADING, (firmwareSize - remaining) * 100 / firmwareSize, "firmware"); + } + + // Verify MD5 + md5.calculate(); + String calculatedMd5 = md5.toString(); + if (!calculatedMd5.equals(expectedMd5)) { + ESP_LOGE(TAG, "MD5 mismatch. Expected: %s, Got: %s", expectedMd5, calculatedMd5.c_str()); + updateProgress(UpdateStatus::MESSAGE, 0, "Firmware: MD5 mismatch"); + Update.abort(); + http.end(); + return false; + } + + // Finish update + if (!Update.end()) { + ESP_LOGE(TAG, "Update end failed"); + updateProgress(UpdateStatus::MESSAGE, 0, "Firmware: Update failed"); + http.end(); + return false; + } + + http.end(); + return true; +} + +bool AppUpdater::IsUpdateAvailable(){ + return updateAvailable; +} + + + +//AsyncEventSource* eventSource = nullptr; +AsyncWebSocket* wsSource = nullptr; + +void startFirmwareUpdateTask(AsyncWebSocket* wsSrc) { + wsSource = wsSrc; + if(Update_Task_Handle) { + ESP_LOGW(TAG, "Firmware update task already running"); + return; + } + xTaskCreate(firmwareUpdateTask, "FirmwareUpdate", 8192, NULL, 1, &Update_Task_Handle); +} + +void firmwareUpdateTask(void* parameter) { + static const char* TAG = "UpdateTask"; + String updateJsonPath = "/system/update.json"; + AppUpdater* updater = nullptr; + + try { + // Read and parse update.json + File file = LittleFS.open(updateJsonPath); + if (!file) { + throw std::runtime_error("Failed to open update.json"); + } + + JsonDocument doc; + DeserializationError error = deserializeJson(doc, file); + file.close(); + + if (error) { throw std::runtime_error("Failed to parse update.json"); } + + // Get update configuration + JsonObject jObj = doc.as(); + String baseUrl = jsonConstrainString(TAG, jObj, "baseurl", "https://storage.googleapis.com/boothifier/"); + String folderName = jsonConstrainString(TAG, jObj, "folder", "latest/"); + String url = baseUrl + folderName; + + // Initialize updater + updater = new AppUpdater(LittleFS, FIRMWARE_VERSION, url.c_str(), "update.json", "firmware.bin"); + updater->setProgressCallback(updateProgress); + + ESP_LOGI(TAG, "Starting update check from: %s", url.c_str()); + + + // Test loop + for(int i = 0; i < 10; i++) { + updateProgress(AppUpdater::UpdateStatus::MESSAGE, i * 10, "test"); + vTaskDelay(2000); + } + updateProgress(AppUpdater::UpdateStatus::MESSAGE, 100, "test complete"); + goto end; + + // Check and perform updates + if (!updater->checkManifest()) { throw std::runtime_error("Failed to check manifest"); } + + if (updater->IsUpdateAvailable()) { + ESP_LOGI(TAG, "Update available, updating files..."); + + if (!updater->updateFilesArray()) { throw std::runtime_error("Failed to update files"); } + + ESP_LOGI(TAG, "Updating firmware..."); + if (!updater->updateApp()) { throw std::runtime_error("Failed to update firmware"); } + + ESP_LOGI(TAG, "Update successful, restarting..."); + delete updater; + vTaskDelay(2000); + ESP.restart(); + } + + } catch (const std::exception& e) { + ESP_LOGE(TAG, "Update failed: %s", e.what()); + } + end: + delete updater; + Update_Task_Handle = NULL; + vTaskDelete(NULL); +} + +void updateProgress(AppUpdater::UpdateStatus newStatus, int percentage, const char* message = nullptr) { + + char buffer[128]; + const char* msg; + bool isComplete = false; + + switch (newStatus) { + case AppUpdater::UpdateStatus::IDLE: + snprintf(buffer, sizeof(buffer), "Update idle"); + msg = buffer; + break; + case AppUpdater::UpdateStatus::MESSAGE: + msg = message ? message : ""; + break; + case AppUpdater::UpdateStatus::DOWNLOADING: + snprintf(buffer, sizeof(buffer), "%s: Download progress: %d%%", message, percentage); + msg = buffer; + break; + case AppUpdater::UpdateStatus::VERIFYING: + snprintf(buffer, sizeof(buffer), "%s: Verifying update: %d%%", message, percentage); + msg = buffer; + break; + case AppUpdater::UpdateStatus::SKIPPING: + snprintf(buffer, sizeof(buffer), "%s: Skipping file update: already up to date", message); + msg = buffer; + break; + case AppUpdater::UpdateStatus::SAVING: + snprintf(buffer, sizeof(buffer), "%s: Saving update: %d%%", message, percentage); + msg = buffer; + break; + case AppUpdater::UpdateStatus::COMPLETE: + snprintf(buffer, sizeof(buffer), "%s: Update complete", message); + msg = buffer; + isComplete = true; + break; + case AppUpdater::UpdateStatus::ERROR: + snprintf(buffer, sizeof(buffer), "%s: Update error occurred", message); + msg = buffer; + break; + default: + snprintf(buffer, sizeof(buffer), "Unknown update status: %d", (int)newStatus); + msg = buffer; + break; + } + + ESP_LOGI(TAG, "%s", msg); + sendUpdateMessage(msg, isComplete, percentage); +} + +void sendUpdateMessage(const char* message, bool complete, int progress = -1) { + if(wsSource && wsSource->count() > 0) { + JsonDocument jsonDoc; + jsonDoc["message"] = message; + jsonDoc["complete"] = complete; + jsonDoc["progress"] = progress; + String strMessage; + serializeJson(jsonDoc, strMessage); + wsSource->textAll(strMessage); + } +} + + + + + + +/* +void setup() { + Serial.begin(115200); + + // Initialize WiFi connection first + // ... WiFi connection code ... + + // Initialize filesystem + if(!LittleFS.begin()) { + Serial.println("LittleFS Mount Failed"); + return; + } + + // Create updater instance with: + // - Current version: "1.0.0" + // - Update server URL: "https://my-update-server.com/" + // - Filesystem: LittleFS + AppUpdater updater("1.0.0", "https://storage.googleapis.com/boothifier/latest/", LittleFS); + + // Set progress callback + updater.setProgressCallback([](int progress) { + Serial.printf("Update progress: %d%%\n", progress); + }); + + // Check and update firmware + if (updater.checkAndUpdate()) { + Serial.println("Update successful! Rebooting..."); + ESP.restart(); + } + + // Update specific files from manifest + int updatedFiles = updater.updateFilesFromManifest("test_update.json"); + Serial.printf("Updated %d files\n", updatedFiles); +} + +*/ \ No newline at end of file diff --git a/webSock/AppUpgrade.h b/webSock/AppUpgrade.h new file mode 100644 index 0000000..c23d9c0 --- /dev/null +++ b/webSock/AppUpgrade.h @@ -0,0 +1,156 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "AppVersion.h" + +#define DEFAULT_MANIFEST_URL "https://storage.googleapis.com/boothifier/latest/" +#define BUFFER_SIZE 4096 + +extern TaskHandle_t Update_Task_Handle; + +/** + * @brief File information structure + */ +struct FileInfo { + String remotePath; ///< Path on remote server + String localPath; ///< Path in local filesystem + String md5; ///< MD5 hash for verification + //size_t size; ///< File size in bytes +}; + +/** + * @class AppUpdater + * @brief Handles firmware and filesystem updates + */ +class AppUpdater { + public: + Version localVersion; + const char* bucketUrl; + const char* appName; + const char* manifestName; + JsonDocument jsonManifest; + JsonArray jsonFilesArray; + + /** + * @brief Update status enumeration + */ + enum class UpdateStatus { + IDLE, ///< No update in progress + MESSAGE, ///< Update message + DOWNLOADING, ///< Downloading files + VERIFYING, ///< Verifying file integrity + SKIPPING, ///< File already up to date + SAVING, ///< Saving to filesystem + COMPLETE, ///< Update complete + ERROR ///< Error occurred + }; + + /** + * @brief Constructor + * @param version Current firmware version + * @param bucket Base URL for updates + * @param fs Filesystem reference + */ + AppUpdater(fs::FS& fs, const char* currVersion, const char* bucket, const char* manifestName ="update.json", const char* appBin = "firmware.bin" ); + + /** + * @brief Set progress callback function + * @param callback Function to call with progress updates + */ + void setProgressCallback(void (*callback)( UpdateStatus status, int percentage, const char* message)); + + /** + * @brief Check for and apply updates + * @return true if update successful + */ + bool IsUpdateAvailable(void); + + /** + * @brief Update files from manifest + * @param manifestPath Path to manifest file + * @return true if all files updated successfully + */ + //bool updateFilesFromManifest(const char* manifestPath = DEFAULT_MANIFEST_URL); + bool updateFilesArray(void); + + /** + * @brief Update single file + * @param remotePath Remote file path + * @param localPath Local file path + * @param expectedMd5 Expected MD5 hash + * @return true if file updated successfully + */ + bool updateFile(const char* remotePath, const char* localPath, const char* expectedMd5); + + /** + * @brief Get manifest content + * @param manifestPath Path to manifest file + * @return Manifest content as a json document + */ + bool checkManifest(void); + + bool updateApp(void); + + String getVersion(void); + + private: + typedef void (*ProgressCallback)(UpdateStatus status, int percentage, const char* message); + ProgressCallback progressCb; + fs::FS& fileSystem; + UpdateStatus status; + std::unique_ptr downloadBuffer; + bool updateAvailable = false; + + + + /** + * @brief Verify and save file + * @param stream Input stream + * @param contentLength Expected content length + * @param localPath Local file path + * @param expectedMd5 Expected MD5 hash + * @return true if successful + */ + bool verifyAndSaveFile(WiFiClient* stream, size_t contentLength, + const char* localPath, const char* expectedMd5); + + /** + * @brief Update progress callback + * @param percentage Progress percentage + * @param newStatus Current status + */ + void updateProgress(UpdateStatus newStatus, int percentage, const char* message = nullptr); + + String getLocalMD5(const char* filePath); +}; + + + +// Queue handle for firmware update messages +extern QueueHandle_t updateMsgQueue; + +// Message structure for update progress +struct UpdateMessage { + String message; + bool complete; + int progress; +}; + +/** + * @brief Firmware update task + * @param param Task parameters + */ +void firmwareUpdateTask(void* param); + +void startFirmwareUpdateTask(AsyncWebSocket* eventSrc); + +void updateProgress(AppUpdater::UpdateStatus status, int percentage, const char* message); + +void sendUpdateMessage(const char* message, bool complete, int progress); + +void handleUpdateProgress(AsyncWebServerRequest *request); \ No newline at end of file diff --git a/webSock/my_wifi.cpp b/webSock/my_wifi.cpp new file mode 100644 index 0000000..36adfbf --- /dev/null +++ b/webSock/my_wifi.cpp @@ -0,0 +1,1164 @@ +#include "my_wifi.h" +#include +#include +#include +#include +#include +#include "esp_log.h" +#include +#include +#include +#include "common/fileSystem.h" +#include "global.h" +#include "my_buzzer.h" +#include +#include +#include "jsonconstrain.h" +#include "AppUpgrade.h" + +static const char *tag = "WIFI"; + +volatile bool WifiClientConnected = false; +AsyncWebServer webServer(80); +AsyncWebSocket wsUpgradeProgress("/upgrade-progress"); +//AsyncEventSource eventUpgradeProgress("/upgrade-progress"); +//DNSServer *dnsServer; +//#define DNS_PORT 53 + +String client_ssid; +String client_pass; +String ap_ssid; +String ap_pass; + +String mDnsName; +String HostName; + +IPAddress local_IP(192,168,10,1); +IPAddress gateway(192,168,10,1); +IPAddress subnet(255,255,255,0); + +// for file manager page +String filesDropdownOptions((char*)0); +String dirDropdownOptions((char*)0); +String savePath((char*)0); // needed for storing file when editing a file +String savePathInput((char*)0); +const char* http_username = "admin"; +const char* http_password = "admin"; +const char* param_delete_path = "delete-path"; +const char* param_edit_path = "edit-path"; +const char* param_dir_pad = "dir-path"; +const char* param_edit_textarea = "edit-textarea"; +const char* param_save_path = "save-path"; +String allowedExtensionsForEdit = "txt, h, htm, html, css, cpp, js, json, ini, cfg"; + +volatile bool scanInProgress = false; + +static String networkList = ""; +int networkCount = 0; + +volatile int scanStatus = 0; // 0=none, 1=scanning, 2=complete, -1=error +String scanResults = ""; // Store scan results globally + +TaskHandle_t Wifi_Task_Handle; + +void Wifi_Task(void* parameter) { + + for(;;) { + static String last_ssid = ""; + + if (!WifiClientConnected || last_ssid != client_ssid) { + WifiClientConnected = false; + ESP_LOGD(tag, "Wifi trying to connect to: %s", client_ssid.c_str()); + WiFi.disconnect(); + vTaskDelay(1000); + WiFi.setAutoReconnect(false); + WiFi.begin(client_ssid.c_str(), client_pass.c_str()); + vTaskDelay(1000); + // wait up to 10 iterations for connection + int attempts = 0; + while (WiFi.status() != WL_CONNECTED && attempts < 10) { + wl_status_t status = WiFi.status(); + switch (status) { + case WL_NO_SSID_AVAIL: + ESP_LOGW(tag, "No AP with SSID %s found", client_ssid.c_str()); + break; + case WL_CONNECT_FAILED: + ESP_LOGW(tag, "Connection failed (wrong password?)"); + break; + case WL_DISCONNECTED: + ESP_LOGD(tag, "Not connected yet... (attempt %d/10)", attempts + 1); + break; + default: + ESP_LOGD(tag, "Status: %d", status); + break; + } + vTaskDelay(2000); + attempts++; + } + + if(WiFi.status() == WL_CONNECTED){ + ESP_LOGD(tag, "Connected to %s", client_ssid.c_str()); + //mDnsName = WiFi.hostname(); + WifiClientConnected = true; + WiFi.setAutoReconnect(true); + last_ssid = client_ssid; + // Save the WiFi settings + Wifi_Save_Credentials("/system/wifi.json"); + } else { + ESP_LOGW(tag, "Failed to connect to %s", client_ssid.c_str()); + } + } + + vTaskSuspend(Wifi_Task_Handle); + } + + vTaskDelete(NULL); +} + +void Wifi_Save_Credentials(String path) { + // Load existing JSON + JsonDocument doc; + File readFile = LittleFS.open(path, "r"); + if (readFile) { + DeserializationError error = deserializeJson(doc, readFile); + readFile.close(); + if (error) { + ESP_LOGE(tag, "Failed to parse existing JSON"); + return; + } + } + + // Update or create wifi-client section + JsonObject wifiClient = doc["wifi-client"].to(); + wifiClient["ssid"] = client_ssid; + wifiClient["pass"] = client_pass; + + // Save updated JSON + File writeFile = LittleFS.open(path, "w"); + if (!writeFile) { + ESP_LOGE(tag, "Error opening %s for writing", path.c_str()); + return; + } + + // Serialize JSON with pretty formatting + if (serializeJsonPretty(doc, writeFile) == 0) { + ESP_LOGE(tag, "Failed to write JSON to file"); + } + writeFile.close(); +} + +void Wifi_Init() { + // Initialize LittleFS + if (!LittleFS.begin(true)) { + ESP_LOGE(tag, "LittleFS mount failed"); + return; + } + + // Set Wi-Fi task to run on Core 1 + esp_wifi_set_ps(WIFI_PS_NONE); // Disable power save mode for better responsiveness + + // Set WiFi to AP+STA mode + WiFi.mode(WIFI_MODE_APSTA); + + Wifi_Scan_for_Networks(); + + // Configure and start AP + WiFi.softAPConfig(local_IP, gateway, subnet); + if (!WiFi.softAP(ap_ssid, ap_pass)) { + ESP_LOGE(tag, "AP start failed"); + return; + } + + // Add CORS headers + DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); + DefaultHeaders::Instance().addHeader("Access-Control-Allow-Methods", "GET, POST, PUT"); + DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "Content-Type"); + + Setup_WebServer_Handlers(webServer); + + WiFi.onEvent(onWiFiEvent); + WiFi.setHostname(mDnsName.c_str()); + + webServer.begin(); + ESP_LOGD(tag, "AP started with IP: %s", WiFi.softAPIP().toString().c_str()); + + // Start the WiFi task + xTaskCreatePinnedToCore(Wifi_Task, "Wifi_Task", 1024*4, NULL, 1, &Wifi_Task_Handle, 0); +} + +void Wifi_Load_Settings(String path){ + // Load WiFi settings + File file = LittleFS.open(path, "r"); + if (!file) { + ESP_LOGE(tag, "Error opening %s", path.c_str()); + return; + } + + JsonDocument doc; + DeserializationError error = deserializeJson(doc, file); + file.close(); + + if (error) { + ESP_LOGE(tag, "Failed to deserialize %s", path.c_str()); + return; + } + + JsonObject wifiJson = doc.as(); + if (wifiJson.isNull()) { + ESP_LOGE(tag, "%s is empty", path.c_str()); + return; + } + + // Load AP settings + JsonObject apJson = wifiJson["wifi-ap"]; + if (!apJson.isNull()) { + ap_ssid = jsonConstrainString(tag, apJson, "ssid", "ATA-AP"); + ap_pass = jsonConstrainString(tag, apJson, "pass", "12345678"); + local_IP.fromString(jsonConstrainString(tag, apJson, "ip", "192.168.10.1")); + gateway.fromString(jsonConstrainString(tag, apJson, "gateway", "192.168.10.1")); + subnet.fromString(jsonConstrainString(tag, apJson, "subnet", "255.255.255.0")); + } + + // Load Client settings + JsonObject clientJson = wifiJson["wifi-client"]; + if (!apJson.isNull()) { + client_ssid = jsonConstrainString(tag, clientJson, "ssid", "none"); + client_pass = jsonConstrainString(tag, clientJson, "pass", "12345678"); + } +} + +void Wifi_Scan_for_Networks(){ + // Start a scan for available networks + WiFi.scanNetworks(false, false); + while (WiFi.scanComplete() == WIFI_SCAN_RUNNING) { + vTaskDelay(100); // Wait for scan to complete + } + + networkCount = WiFi.scanComplete(); + if (networkCount >= 0) { + JsonDocument doc; + JsonArray networks = doc["networks"].to(); + + for (int i = 0; i < networkCount; i++) { + auto network = networks.add(); + network["ssid"] = WiFi.SSID(i); + network["rssi"] = WiFi.RSSI(i); + network["encryption"] = WiFi.encryptionType(i) != WIFI_AUTH_OPEN; + } + + String jsonString; + serializeJson(doc, jsonString); + networkList = jsonString; + WiFi.scanDelete(); + } else { + ESP_LOGE(tag, "WiFi scan failed"); + } +} + +void Setup_WebServer_Handlers(AsyncWebServer& server){ + + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ + request->send(LittleFS, "/www/index.html", "text/html"); + }); + server.on("/home", HTTP_GET, [](AsyncWebServerRequest *request){ + sendHtmlFile("/www/home.html", request, HomeHtmlProcessor); + }); + server.on("/setup", HTTP_GET, [](AsyncWebServerRequest *request){ + if(!request->authenticate(http_username, http_password)){ + return request->requestAuthentication(); + } + //sendHtmlFile("/www/setup.html", request, htmlProcessor); + }); + server.on("/about", HTTP_GET, [](AsyncWebServerRequest *request){ + sendHtmlFile("/www/about.html", request, fileManagerHtmlProcessor); + //request->send(LittleFS, "/www/about.html", "text/html"); + }); + + + // Wifi related handlers + server.on("/wifi/connect", HTTP_POST, [](AsyncWebServerRequest *request){ + if (request->hasParam("ssid", false, false) && request->hasParam("pass", false, false)) { + client_ssid = request->getParam("ssid", false, false)->value().c_str(); + if (Wifi_Task_Handle != NULL && eTaskGetState(Wifi_Task_Handle) == eSuspended) { + ESP_LOGD(tag, "Resuming WiFi task"); + WifiClientConnected = false; + vTaskResume(Wifi_Task_Handle); + } else { + ESP_LOGE(tag, "Failed to resume WiFi task: invalid handle or task not suspended"); + } + vTaskResume(Wifi_Task_Handle); + request->send(200, "application/json", "{\"status\":\"connecting\"}"); + } else { + ESP_LOGE(tag, "Missing ssid or pass parameter"); + request->send(400, "application/json", "{\"error\":\"Missing ssid or pass parameter\"}"); + } + }); + server.on("/wifi/status", HTTP_GET, [](AsyncWebServerRequest *request){ + String jsonStr = "{\"status\":\"" + String(WifiClientConnected ? "Connected" : "Disconnected") + + "\",\"ip\":\"" + WiFi.localIP().toString() + "\"}"; + request->send(200, "application/json", jsonStr); + }); + server.on("/wifi/scans", HTTP_GET, [](AsyncWebServerRequest *request){ + if(networkCount <= 0) { + request->send(400, "application/json", "{\"error\":\"No scan results\"}"); + return; + } + + request->send(200, "application/json", networkList); + }); + server.on("/wifi", HTTP_GET, [](AsyncWebServerRequest *request){ + if(WiFi.getMode() == WIFI_MODE_APSTA){ + // TODO Disable navigation bar + } + //sendHtmlFile("/www/wifi.html", request, htmlProcessor); + request->send(LittleFS, "/www/wifi.html", "text/html"); + }); + + + // File Manager related handlers + server.on("/files/upload", HTTP_POST, [](AsyncWebServerRequest *request) { request->send(200); }, handleFilesUpload_OnBody); + + server.on("/files/download", HTTP_GET, [](AsyncWebServerRequest *request){ + if (!request->hasParam("file")) { + ESP_LOGE(tag, "Missing file parameter"); + request->send(400, "text/plain", "Missing file parameter"); + return; + } + + try { + String filename = uriDecode(request->getParam("file")->value()); + ESP_LOGD(tag, "Download request for: %s", filename.c_str()); + request->send(LittleFS, filename, "application/octet-stream"); + } + catch (const std::exception& e) { + ESP_LOGE(tag, "Download failed: %s", e.what()); + request->send(404, "text/plain", "File not found!"); + } + }); + server.on("/files/delete", HTTP_GET, [](AsyncWebServerRequest *request) { + static const char* tag = "DeleteHandler"; + + // Authentication check + if (!request->authenticate(http_username, http_password)) { + ESP_LOGW(tag, "Authentication failed for delete request"); + return request->requestAuthentication(); + } + + // Parameter validation + if (!request->hasParam(param_delete_path)) { + ESP_LOGE(tag, "Missing delete path parameter"); + request->send(400, "text/plain", "Missing file path"); + return; + } + + // Get and validate filename + String filename = uriDecode(request->getParam(param_delete_path)->value()); + if (filename == "choose") { + request->redirect("/files"); + return; + } + + // Ensure path starts with / + if (!filename.startsWith("/")) { + filename = "/" + filename; + } + + try { + if (LittleFS.remove(filename.c_str())) { + ESP_LOGI(tag, "Successfully deleted file: %s", filename.c_str()); + } else { + ESP_LOGE(tag, "Failed to delete file: %s", filename.c_str()); + request->send(500, "text/plain", "Delete failed"); + return; + } + } catch (const std::exception& e) { + ESP_LOGE(tag, "Exception during delete: %s", e.what()); + request->send(500, "text/plain", "Delete failed"); + return; + } + + request->redirect("/files"); + }); + server.on("/files/edit", HTTP_GET, [](AsyncWebServerRequest *request) { + static const char* tag = "EditHandler"; + + // Authentication check + if (!request->authenticate(http_username, http_password)) { + ESP_LOGW(tag, "Authentication failed"); + return request->requestAuthentication(); + } + + // Parameter validation + if (!request->hasParam(param_edit_path)) { + ESP_LOGE(tag, "Missing edit path parameter"); + request->send(400, "text/plain", "Missing file path"); + return; + } + + // Get and decode filename + String fileName = uriDecode(request->getParam(param_edit_path)->value()); + ESP_LOGD(tag, "Edit request for file: %s", fileName.c_str()); + + // Set save path + savePath = (fileName == "new") ? "/new.txt" : fileName; + + // Validate path + if (!savePath.startsWith("/")) { + savePath = "/" + savePath; + } + + ESP_LOGD(tag, "Save path set to: %s", savePath.c_str()); + + try { + sendHtmlFile("/www/edit.html", request, fileManagerHtmlProcessor); + } catch (const std::exception& e) { + ESP_LOGE(tag, "Failed to send edit page: %s", e.what()); + request->send(500, "text/plain", "Internal server error"); + } + }); + server.on("/files/save", HTTP_GET, [](AsyncWebServerRequest *request){ + if(!request->authenticate(http_username, http_password)){ + return request->requestAuthentication(); + } + String inputMessage((char*)0); + if (request->hasParam(param_edit_textarea)) { + inputMessage = request->getParam(param_edit_textarea)->value(); + } + if (request->hasParam(param_save_path)) { + savePath = uriDecode(request->getParam(param_save_path)->value()); + } + writeFile(LittleFS, savePath.c_str(), inputMessage.c_str()); + + request->redirect("/files"); + }); + server.on("/files", HTTP_GET, [](AsyncWebServerRequest *request){ + if(!request->authenticate(http_username, http_password)){ + return request->requestAuthentication(); + } + sendHtmlFile("/www/files.html", request, fileManagerHtmlProcessor); + }); + + + // Lights related handlers + server.on("/lights/settings", HTTP_GET, [](AsyncWebServerRequest *request) { + request->send(200); + }); + server.on("/lights/settings", HTTP_POST, [](AsyncWebServerRequest *request) { + request->send(200); + }); + server.on("/lights/animation", HTTP_POST, [](AsyncWebServerRequest *request) { + request->send(200); + }); + server.on("/lights/setpixel", HTTP_POST, [](AsyncWebServerRequest *request) { + request->send(200); + }); + + + // System and LED related handlers + server.on("/system/summary", HTTP_GET, [](AsyncWebServerRequest *request){ + String response = "{\"status\":\"" + String(WifiClientConnected ? "Connected" : "Disconnected") + "\"}"; + request->send(200, "application/json", response); + }); + server.on("/leds/settings", HTTP_GET, [](AsyncWebServerRequest *request){ + /* + //CreateSysSummmaryPacket(doc); + String summary; + serializeJson(jsDoc, summary); + request->send(200, "application/json", summary); + */ + String response = "{\"status\":\"" + String(WifiClientConnected ? "Connected" : "Disconnected") + "\"}"; + request->send(200, "application/json", response); + }); + server.on("/leds/settings", HTTP_POST, [](AsyncWebServerRequest *request){ + String response = "{\"status\":\"" + String(WifiClientConnected ? "Connected" : "Disconnected") + "\"}"; + request->send(200, "application/json", response); + }); + + + + // LightStik related handlers + server.on("/lightstik/settings", HTTP_GET, [](AsyncWebServerRequest *request){ + String response = "{\"status\":\"" + String(WifiClientConnected ? "Connected" : "Disconnected") + "\"}"; + request->send(200, "application/json", response); + }); + server.on("/lightstik/settings", HTTP_POST, [](AsyncWebServerRequest *request){ + String response = "{\"status\":\"" + String(WifiClientConnected ? "Connected" : "Disconnected") + "\"}"; + request->send(200, "application/json", response); + }); + server.on("/lightstik/register", HTTP_POST, [](AsyncWebServerRequest *request){ + String response = "{\"status\":\"" + String(WifiClientConnected ? "Connected" : "Disconnected") + "\"}"; + request->send(200, "application/json", response); + }); + + + // Firmware Update Handlers + server.on("/upgrade/check", HTTP_GET, [](AsyncWebServerRequest *request) { + JsonDocument doc; + doc["currentVersion"] = FIRMWARE_VERSION; + doc["latestVersion"] = "1.1.0"; + doc["updateAvailable"] = true; + + String response; + serializeJson(doc, response); + request->send(200, "application/json", response); + }); + // Start update process + server.on("/upgrade/start", HTTP_POST, [](AsyncWebServerRequest *request) { + //if (!request->authenticate(http_username, http_password)) { + // return request->requestAuthentication(); + //} + startFirmwareUpdateTask(&wsUpgradeProgress); + request->send(200); + }); + //server.on("/upgrade/progress", HTTP_GET, [](AsyncWebServerRequest *request) { + /* + if (!request->authenticate(http_username, http_password)) { + return request->requestAuthentication(); + } + AsyncWebServerResponse *response = request->beginResponse(200, "text/event-stream"); + response->addHeader("Cache-Control", "no-cache"); + response->addHeader("Connection", "keep-alive"); + request->send(response); + */ + //}); + server.on("/upgrade", HTTP_GET, [](AsyncWebServerRequest *request) { + if(!request->authenticate(http_username, http_password)){ + return request->requestAuthentication(); + } + request->send(LittleFS, "/www/upgrade.html", "text/html"); + }); + + wsUpgradeProgress.onEvent(onWsUpdateProgressEvent); + server.addHandler(&wsUpgradeProgress); + + // Basic Connection status check + server.on("/api/status", HTTP_GET, [](AsyncWebServerRequest *request) { + request->send(200, "application/json", "{\"status\":\"connected\"}"); + }); + // Server requested files that aren't template processed + server.on("/*", HTTP_GET, [](AsyncWebServerRequest *request) { // handle file uploads + // Validate request + if (!request) { + ESP_LOGE(tag, "Invalid request"); + return; + } + + // Get and validate file path + String filePath = request->url(); + if (filePath.isEmpty()) { + ESP_LOGE(tag, "Empty file path"); + request->send(400, "text/plain", "Invalid file path"); + return; + } + + // Ensure path starts with '/' + if (!filePath.startsWith("/")) { + filePath = "/" + filePath; + } + + try { + // Get content type once + const char* contentType = getFileType(getFileExtension(filePath.c_str())); + if (!contentType) { + ESP_LOGW(tag, "Unknown file type: %s", filePath.c_str()); + contentType = "application/octet-stream"; + } + + ESP_LOGD(tag, "Sending file: %s (%s)", filePath.c_str(), contentType); + request->send(LittleFS, filePath, contentType); + } + catch (const std::runtime_error& e) { + ESP_LOGE(tag, "FileSystem error: %s for path: %s", e.what(), filePath.c_str()); + request->send(404, "text/plain", "File not found"); + } + catch (const std::exception& e) { + ESP_LOGE(tag, "Error: %s for path: %s", e.what(), filePath.c_str()); + request->send(500, "text/plain", "Internal server error"); + } + }); + // 404 handler + server.onNotFound([](AsyncWebServerRequest *request) { + ESP_LOGE(tag, "404: %s", request->url().c_str()); + request->send(404, "text/plain", "Not found"); + }); + +} + +void onWsUpdateProgressEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) { + switch (type) { + case WS_EVT_CONNECT: + ESP_LOGD(tag,"WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str()); + break; + case WS_EVT_DISCONNECT: + ESP_LOGD(tag, "WebSocket client #%u disconnected\n", client->id()); + break; + case WS_EVT_DATA: + ESP_LOGD(tag, "WebSocket client #%u data\n", client->id()); + //handleWebSocketMessage(arg, data, len); + break; + case WS_EVT_PONG: + ESP_LOGD(tag, "WebSocket client #%u pong\n", client->id()); + break; + case WS_EVT_ERROR: + ESP_LOGD(tag, "WebSocket client #%u error\n", client->id()); + break; + } +} + + + +void handleFilesUpload_OnBody(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { + static const size_t MAX_UPLOAD_SIZE = 1024 * 1024; // 1MB limit + + if (!index) { + // Initial upload chunk + if (!request->hasParam("dir-path", true, false)) { + ESP_LOGE(tag, "Missing dir-path parameter"); + request->send(400, "text/plain", "Missing dir-path"); + return; + } + + AsyncWebParameter* p = request->getParam("dir-path", true, false); + String path = p->value() + "/" + filename; + ESP_LOGD(tag, "Starting upload: %s", path.c_str()); + + // Validate path + if (!path.startsWith("/")) { + path = "/" + path; + } + + request->_tempFile = LittleFS.open(path, "w"); + if (!request->_tempFile) { + ESP_LOGE(tag, "Failed to create file: %s", path.c_str()); + request->send(500, "text/plain", "Failed to create file"); + return; + } + } + + // Write chunk + if (len && request->_tempFile) { + if (index + len > MAX_UPLOAD_SIZE) { + request->_tempFile.close(); + LittleFS.remove(request->_tempFile.name()); + ESP_LOGE(tag, "Upload too large"); + request->send(413, "text/plain", "File too large"); + return; + } + + if (!request->_tempFile.write(data, len)) { + ESP_LOGE(tag, "Write failed"); + request->_tempFile.close(); + request->send(500, "text/plain", "Write failed"); + return; + } + } + + if (final) { + request->_tempFile.close(); + ESP_LOGD(tag, "Upload complete: %s, %u bytes", filename.c_str(), index + len); + request->redirect("/files"); + } +} + + +// Send html file with template processing {{VAR}} +void sendHtmlFile(const char* filePath, AsyncWebServerRequest *request, String (*callback)(const String&)) { + try { + const char* htmlFile = readFile(LittleFS, filePath); + if (!htmlFile) { + ESP_LOGE(tag, "Failed to read file: %s", filePath); + request->send(404, "text/plain", "File not found"); + return; + } + + String processedData = varReplace(htmlFile, callback); + delete[] htmlFile; // Clean up allocated memory + + ESP_LOGD(tag, "Sent file: %s", filePath); + request->send(200, "text/html", processedData); + } + catch (const std::exception& e) { + ESP_LOGE(tag, "Error processing file %s: %s", filePath, e.what()); + request->send(500, "text/plain", "Server error"); + } +} + +const char* getFileExtension(const char* filename) { + // Input validation + if (!filename) { + ESP_LOGW(tag, "Null filename provided"); + return ""; + } + + // Find last dot + const char* lastDot = strrchr(filename, '.'); + if (!lastDot || lastDot == filename || *(lastDot + 1) == '\0') { + ESP_LOGD(tag, "No valid extension found in: %s", filename); + return ""; + } + + ESP_LOGD(tag, "Found extension: %s", lastDot + 1); + return lastDot + 1; +} + +const char* getFileType(const char* ext) { + if (!ext) return "application/octet-stream"; + + ESP_LOGD(tag, "Getting file type for extension: %s", ext); + + if (strcmp(ext, "png") == 0) return "image/png"; + if (strcmp(ext, "jpg") == 0 || strcmp(ext, "jpeg") == 0) return "image/jpeg"; + if (strcmp(ext, "gif") == 0) return "image/gif"; + if (strcmp(ext, "ico") == 0) return "image/x-icon"; + if (strcmp(ext, "txt") == 0) return "text/plain"; + if (strcmp(ext, "css") == 0) return "text/css"; + if (strcmp(ext, "htm") == 0 || strcmp(ext, "html") == 0) return "text/html"; + if (strcmp(ext, "js") == 0) return "text/javascript"; + if (strcmp(ext, "json") == 0) return "application/json"; + + ESP_LOGW(tag, "Unknown file extension: %s", ext); + return "application/octet-stream"; +} + +// Finds segments between {{VAR}} and calls a callback function to replace VAR with new content +String varReplace(const String& input, String (*callback)(const String&)) { + static const char* tag = "varReplace"; + + // Validate inputs + if (input.isEmpty() || !callback) { + ESP_LOGW(tag, "Empty input or null callback"); + return input; + } + + // Pre-allocate result string with estimated size + String result; + result.reserve(input.length() * 1.2); // Add 20% for potential replacements + + const int maxSegmentLength = 32; + int startPos = 0; + + // Process all segments + while (true) { + // Find next variable + int start = input.indexOf("{{", startPos); + if (start == -1) { + break; + } + + // Add text before variable + result += input.substring(startPos, start); + + // Find end of variable + int end = input.indexOf("}}", start + 2); + if (end == -1) { + ESP_LOGW(tag, "Unmatched {{ at position %d", start); + result += input.substring(start); + break; + } + + // Extract and validate segment + String segment = input.substring(start + 2, end); + if (segment.length() <= maxSegmentLength) { + try { + String replacement = callback(segment); + result += replacement; + } catch (const std::exception& e) { + ESP_LOGE(tag, "Callback error: %s", e.what()); + result += input.substring(start, end + 2); + } + } else { + ESP_LOGW(tag, "Segment too long: %d chars", segment.length()); + result += input.substring(start, end + 2); + } + + startPos = end + 2; + } + + // Add remaining text + if (startPos < input.length()) { + result += input.substring(startPos); + } + + return result; +} + +const char* convertFileSize(const size_t bytes) { + static char fileSizeBuffer[16]; // Pre-allocated buffer for the file size + + if (bytes < 1024) { + snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%d B", bytes); + } else if (bytes < 1024 * 1024) { + snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%.2f kB", bytes / 1024.0); + } else if (bytes < 1024 * 1024 * 1024) { + snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%.2f MB", bytes / (1024.0 * 1024.0)); + } else { + snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%.2f GB", bytes / (1024.0 * 1024.0 * 1024.0)); + } + + return fileSizeBuffer; +} + +void onWiFiEvent(WiFiEvent_t event) { + //Serial.printf("[WiFi-event] event: %d\n", event); + switch (event) { + case ARDUINO_EVENT_WIFI_READY: + ESP_LOGD(tag, "WiFi interface ready"); + break; + case ARDUINO_EVENT_WIFI_SCAN_DONE: + ESP_LOGD(tag,"Completed scan for access points"); + break; + case ARDUINO_EVENT_WIFI_STA_START: + ESP_LOGD(tag,"WiFi client started"); + break; + case ARDUINO_EVENT_WIFI_STA_STOP: + ESP_LOGD(tag,"WiFi clients stopped"); + break; + case ARDUINO_EVENT_WIFI_STA_CONNECTED: + ESP_LOGD(tag,"Connected to AP"); + break; + case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: + WifiClientConnected = false; + ESP_LOGD(tag, "WiFi Disconnected"); + Buzzer_Play_Tune(TUNE_DISCONNECTED); + break; + case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE: + ESP_LOGD(tag,"Authentication mode of access point has changed"); + break; + case ARDUINO_EVENT_WIFI_STA_GOT_IP: + WifiClientConnected = true; + ESP_LOGD(tag,"My IP: %s", WiFi.localIP().toString()); + //Wifi_Start_MDNS(); + Buzzer_Play_Tune(TUNE_CONNECTED); + break; + case ARDUINO_EVENT_WIFI_STA_LOST_IP: + WifiClientConnected = false; + ESP_LOGD(tag,"Lost IP address and IP address is reset to 0"); + break; + case ARDUINO_EVENT_WPS_ER_SUCCESS: + ESP_LOGD(tag,"WiFi Protected Setup (WPS): succeeded in enrollee mode"); + break; + case ARDUINO_EVENT_WPS_ER_FAILED: + ESP_LOGD(tag,"WiFi Protected Setup (WPS): failed in enrollee mode"); + break; + case ARDUINO_EVENT_WPS_ER_TIMEOUT: + ESP_LOGD(tag,"WiFi Protected Setup (WPS): timeout in enrollee mode"); + break; + case ARDUINO_EVENT_WPS_ER_PIN: + ESP_LOGD(tag,"WiFi Protected Setup (WPS): pin code in enrollee mode"); + break; + case ARDUINO_EVENT_WIFI_AP_START: + ESP_LOGD(tag, "WiFi access point started"); + break; + case ARDUINO_EVENT_WIFI_AP_STOP: + ESP_LOGD(tag, "WiFi access point stopped"); + break; + case ARDUINO_EVENT_WIFI_AP_STACONNECTED: + ESP_LOGD(tag, "Client connected"); + break; + case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED: + ESP_LOGD(tag, "SoftAP Client Disconnected"); + Buzzer_Play_Tune(TUNE_DISCONNECTED); + break; + case ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED: + ESP_LOGD(tag,"SoftAP Client Connected"); + Buzzer_Play_Tune(TUNE_CONNECTED); + break; + case ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED: + ESP_LOGD(tag,"Received probe request"); + break; + case ARDUINO_EVENT_WIFI_AP_GOT_IP6: + ESP_LOGD(tag,"AP IPv6 is preferred"); + break; + case ARDUINO_EVENT_WIFI_STA_GOT_IP6: + ESP_LOGD(tag,"STA IPv6 is preferred"); + break; + case ARDUINO_EVENT_ETH_GOT_IP6: + ESP_LOGD(tag,"Ethernet IPv6 is preferred"); + break; + case ARDUINO_EVENT_ETH_START: + ESP_LOGD(tag,"Ethernet started"); + break; + case ARDUINO_EVENT_ETH_STOP: + ESP_LOGD(tag,"Ethernet stopped"); + break; + case ARDUINO_EVENT_ETH_CONNECTED: + ESP_LOGD(tag,"Ethernet connected"); + break; + case ARDUINO_EVENT_ETH_DISCONNECTED: + ESP_LOGD(tag,"Ethernet disconnected"); + break; + case ARDUINO_EVENT_ETH_GOT_IP: + ESP_LOGD(tag,"Obtained IP address"); + break; + default: break; + } +} + +void Wifi_Start_MDNS(void) { + ESP_LOGV(tag, "Initializing MDNS: %s", mDnsName.c_str()); + if (!MDNS.begin(mDnsName.c_str())) { + ESP_LOGE(tag, "Error setting up MDNS responder!"); + }else{ + ESP_LOGV(tag, "You can access device via http://%s.local", mDnsName); + } +} + +bool writeFile(fs::FS &fs, const char* path, const char* message) { + // Validate inputs + if (!path || !message) { + ESP_LOGE(tag, "Invalid parameters: path=%p message=%p", path, message); + return false; + } + + // Open file with error checking + File file = fs.open(path, "w"); + if (!file) { + ESP_LOGE(tag, "Failed to open file: %s", path); + return false; + } + + // Write with error handling + try { + size_t bytesWritten = file.print(message); + if (bytesWritten == 0) { + ESP_LOGE(tag, "Failed to write to file: %s", path); + file.close(); + return false; + } + + // Ensure all data is written + file.flush(); + file.close(); + ESP_LOGD(tag, "Successfully wrote %u bytes to %s", bytesWritten, path); + return true; + } + catch (const std::exception& e) { + ESP_LOGE(tag, "Exception while writing file %s: %s", path, e.what()); + file.close(); + return false; + } +} + +char* readFile(fs::FS &fs, const char* path) { + static const char* tag = "readFile"; + static const size_t MAX_FILE_SIZE = 1024 * 1024; // 1MB limit + + // Validate input + if (!path) { + ESP_LOGE(tag, "Invalid path parameter"); + return nullptr; + } + + // Open file + File file = fs.open(path, "r"); + if (!file || file.isDirectory()) { + ESP_LOGE(tag, "Failed to open file: %s", path); + return nullptr; + } + + // Check file size + size_t fileSize = file.size(); + if (fileSize == 0 || fileSize > MAX_FILE_SIZE) { + ESP_LOGE(tag, "Invalid file size: %u bytes", fileSize); + file.close(); + return nullptr; + } + + // Allocate memory + char* fileContent = new (std::nothrow) char[fileSize + 1]; + if (!fileContent) { + ESP_LOGE(tag, "Memory allocation failed for size: %u", fileSize + 1); + file.close(); + return nullptr; + } + + // Read file + size_t bytesRead = file.readBytes(fileContent, fileSize); + file.close(); + + if (bytesRead != fileSize) { + ESP_LOGE(tag, "Read failed: expected %u bytes, got %u", fileSize, bytesRead); + delete[] fileContent; + return nullptr; + } + + // Null terminate + fileContent[bytesRead] = '\0'; + ESP_LOGD(tag, "Successfully read %u bytes from %s", bytesRead, path); + return fileContent; +} + +String getSoftAPMacAddress() { + uint8_t mac[6]; + WiFi.softAPmacAddress(mac); + + String macString = ""; + for (int i = 0; i < 6; i++) { + macString += String(mac[i], HEX); + if (i < 5) macString += ":"; + } + return macString; +} + +String listDirAsHtml(String directoryList[], int count) { + String listedFiles; + + for (int i = 0; i < count; i++) { + // directory html + listedFiles += "Dir: "; + listedFiles += directoryList[i]; + listedFiles += "/-\n"; + + filesDropdownOptions += "\n"; + + dirDropdownOptions += "\n"; + + File dir = LittleFS.open(directoryList[i]); + File file = dir.openNextFile(); + while (file) { + String fileName = file.name(); + if (!file.isDirectory()) { + //Serial.println(" File: " + String(file.name())); + listedFiles += "  "; + listedFiles += fileName; + listedFiles += ""; + listedFiles += convertFileSize(file.size()); + listedFiles += "\n"; + + filesDropdownOptions += "\n"; + } + file = dir.openNextFile(); + } + dir.close(); + } + + return listedFiles; +} + + + +/******************** Specific Html Processors ********************/ +// file manager html processor +String fileManagerHtmlProcessor(const String& var){ + if(var == "ALLOWED_EXTENSIONS_EDIT"){ return allowedExtensionsForEdit; } + + if(var == "FS_FREE_BYTES"){ return convertFileSize(LittleFS.totalBytes() - LittleFS.usedBytes()); } + + if(var == "FS_USED_BYTES"){ return convertFileSize(LittleFS.usedBytes()); } + + if(var == "FS_TOTAL_BYTES"){ return convertFileSize(LittleFS.totalBytes()); } + + if(var == "RAM_FREE_BYTES"){ return convertFileSize(LittleFS.totalBytes()); } + + if(var == "RAM_USED_BYTES"){ return convertFileSize(LittleFS.totalBytes()); } + + if(var == "RAM_TOTAL_BYTES"){ return convertFileSize(LittleFS.totalBytes()); } + + if(var == "LISTED_FILES"){ + filesDropdownOptions = ""; // clear out + dirDropdownOptions = ""; // clear out + String directories[MAX_DIRECTORIES]; + int dirCount = 0; + getAllDirectories(directories, dirCount); + return listDirAsHtml(directories, dirCount); + } + + if(var == "EDIT-DEL_FILES"){ return filesDropdownOptions; } + + if(var == "DIR_LIST"){ return dirDropdownOptions; } + + if(var == "SAVE_PATH_INPUT"){ return savePath; } + + if(var == "FIRM_VER"){ return FIRMWARE_VERSION; } + + return var; +} + +String HomeHtmlProcessor(const String& var) { + if (var == "APP_NAME") { + //return sysProps.appName; + return "N/A"; + } + if (var == "OLED") { + return "N/A"; + } + if (var == "STRIP1") { + //return (strip1) ? "Yes" : "No"; + return "N/A"; + } + if (var == "STRIP2") { + //return (strip2) ? "Yes" : "No"; + return "N/A"; + } + if (var == "FRONT_LIGHT") { + //return (animProps.frontLight.enabled) ? "Yes" : "No"; + return "N/A"; + } + if (var == "REAR_LIGHT") { + //return (animProps.rearLight.enabled) ? "Yes" : "No"; + return "N/A"; + } + if (var == "FIRMWARE") { + return FIRMWARE_VERSION; + } + if (var == "BOOTH_T") { + //return String(sysProps.t_sensor.temperature) + "F"; + return "N/A"; + } + if (var == "SETPOINT") { + //return String(sysProps.t_sensor.Setpoint1) + "F"; + return "N/A"; + } + if (var == "FLASH_SIZE") { return convertFileSize(ESP.getSketchSize());} + if (var == "FLASH_FREE") { return convertFileSize(ESP.getFreeSketchSpace());} + if (var == "HEAP_SIZE") { return convertFileSize(ESP.getHeapSize()); } + if (var == "HEAP_FREE") { return convertFileSize(ESP.getFreeHeap()); } + if (var == "CPU_FREQ") { return String(ESP.getCpuFreqMHz()) + "Mhz"; } + if (var == "IP") { return WiFi.localIP().toString(); } + if (var == "MAC") { return chipInfo.macStr; } + if (var == "SSID") { return WiFi.SSID(); } + if (var == "RSSI") { return String(WiFi.RSSI()); } + if (var == "WIFI_CH") { return String(WiFi.channel()); } + if (var == "ENCRYP") { return String(WiFi.encryptionType(0)); } + if (var == "AP_SSID") { return WiFi.softAPSSID(); } + if (var == "AP_CLIENTS") { return String(WiFi.softAPgetStationNum()); } + if (var == "BLE") { return (commMode == COMM_WIFI_AP_BLE) ? "Yes" : "No"; } + if (var == "BLE_SSID") { + //return (commMode == COMM_WIFI_AP_BLE) ? BLEDeviceName : ""; + return "N/A"; + } + if (var == "BLE_CLIENTS") { + //return (BTDeviceConnected) ? "1" : "0"; + return "N/A"; + } + if (var == "AP_MAC") { return getSoftAPMacAddress(); } + + // Return an empty string if the variable is not recognized + return var; +} + + + +void handleUpdateProgress(AsyncWebServerRequest *request) { + static const char* tag = "UpdateProgress"; + + //if (!request->authenticate(http_username, http_password)) { + // return request->requestAuthentication(); + //} + + request->send(200, "text/plain", "Update progress"); // Send a simple response +} diff --git a/webSock/my_wifi.h b/webSock/my_wifi.h new file mode 100644 index 0000000..603d416 --- /dev/null +++ b/webSock/my_wifi.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include +#include + +void Wifi_Init(void); +void Wifi_Load_Settings(String path); +void Wifi_Scan_for_Networks(void); +void Wifi_Start_MDNS(void); +void onWiFiEvent(WiFiEvent_t event); +void Wifi_Save_Credentials(String path); +void Setup_WebServer_Handlers(AsyncWebServer& serv); + +void handlePOST_Update(AsyncWebServerRequest *request); +void updateCallback(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); +void updateFirmwareProgress(size_t progress, size_t total); + +void handleGET_Query(AsyncWebServerRequest *request); + +void handleFilesUpload_OnBody(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); + +void sendHtmlFile(const char* filePath, AsyncWebServerRequest *request, String (*callback)(const String&)); +String fileManagerHtmlProcessor(const String& var); +String HomeHtmlProcessor(const String& var); +String listDirAsHtml(String directoryList[], int count); + +const char* getFileExtension(const char* filename); +const char* getFileType(const char* ext); +const char* convertFileSize(const size_t bytes); +bool writeFile(fs::FS &fs, const char *path, const char *message); +char* readFile(fs::FS &fs, const char *path); + +String varReplace(const String& input, String (*callback)(const String&)); +String getSoftAPMacAddress(void); + +void onWsUpdateProgressEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len); diff --git a/webSock/upgrade.html b/webSock/upgrade.html new file mode 100644 index 0000000..c0b55ea --- /dev/null +++ b/webSock/upgrade.html @@ -0,0 +1,193 @@ + + + + + + Firmware Upgrade + + + + +
+

Firmware Upgrade

+ +
+ + Disconnected +
+ +
+

Current Version: -

+

Latest Version: -

+
+ +
+ + +
+ +
+
+ + + + \ No newline at end of file diff --git a/web_ble.html b/web_ble.html new file mode 100644 index 0000000..b8309a0 --- /dev/null +++ b/web_ble.html @@ -0,0 +1,113 @@ + + + + + + BLE Interaction + + + +

BLE Interaction

+ + + + + + + + diff --git a/wokwi.toml b/wokwi.toml new file mode 100644 index 0000000..0587e7d --- /dev/null +++ b/wokwi.toml @@ -0,0 +1,6 @@ +[wokwi] +version = 1 +firmware = '\.pio\build\esp32s3dev\firmware.bin' +elf = '\.pio\build\esp32s3dev\firmware.elf' +rfc2217ServerPort = 4000 +gdbServerPort=3333 \ No newline at end of file diff --git a/z_old/HSVTable.cpp b/z_old/HSVTable.cpp new file mode 100644 index 0000000..a5643e7 --- /dev/null +++ b/z_old/HSVTable.cpp @@ -0,0 +1,175 @@ +#include "HSVTable.h" +#include +#include "LEDStrip.h" + + +const uint8_t lights[360]={ + 0, 0, 0, 0, 0, 1, 1, 2, + 2, 3, 4, 5, 6, 7, 8, 9, + 11, 12, 13, 15, 17, 18, 20, 22, + 24, 26, 28, 30, 32, 35, 37, 39, + 42, 44, 47, 49, 52, 55, 58, 60, + 63, 66, 69, 72, 75, 78, 81, 85, + 88, 91, 94, 97, 101, 104, 107, 111, +114, 117, 121, 124, 127, 131, 134, 137, +141, 144, 147, 150, 154, 157, 160, 163, +167, 170, 173, 176, 179, 182, 185, 188, +191, 194, 197, 200, 202, 205, 208, 210, +213, 215, 217, 220, 222, 224, 226, 229, +231, 232, 234, 236, 238, 239, 241, 242, +244, 245, 246, 248, 249, 250, 251, 251, +252, 253, 253, 254, 254, 255, 255, 255, +255, 255, 255, 255, 254, 254, 253, 253, +252, 251, 251, 250, 249, 248, 246, 245, +244, 242, 241, 239, 238, 236, 234, 232, +231, 229, 226, 224, 222, 220, 217, 215, +213, 210, 208, 205, 202, 200, 197, 194, +191, 188, 185, 182, 179, 176, 173, 170, +167, 163, 160, 157, 154, 150, 147, 144, +141, 137, 134, 131, 127, 124, 121, 117, +114, 111, 107, 104, 101, 97, 94, 91, + 88, 85, 81, 78, 75, 72, 69, 66, + 63, 60, 58, 55, 52, 49, 47, 44, + 42, 39, 37, 35, 32, 30, 28, 26, + 24, 22, 20, 18, 17, 15, 13, 12, + 11, 9, 8, 7, 6, 5, 4, 3, + 2, 2, 1, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0}; + +const uint8_t HSVlights[61] = +{0, 4, 8, 13, 17, 21, 25, 30, 34, 38, 42, 47, 51, 55, 59, 64, 68, 72, 76, +81, 85, 89, 93, 98, 102, 106, 110, 115, 119, 123, 127, 132, 136, 140, 144, +149, 153, 157, 161, 166, 170, 174, 178, 183, 187, 191, 195, 200, 204, 208, +212, 217, 221, 225, 229, 234, 238, 242, 246, 251, 255}; + +const uint8_t HSVpower[121] = +{0, 2, 4, 6, 8, 11, 13, 15, 17, 19, 21, 23, 25, 28, 30, 32, 34, 36, 38, 40, +42, 45, 47, 49, 51, 53, 55, 57, 59, 62, 64, 66, 68, 70, 72, 74, 76, 79, 81, +83, 85, 87, 89, 91, 93, 96, 98, 100, 102, 104, 106, 108, 110, 113, 115, 117, +119, 121, 123, 125, 127, 130, 132, 134, 136, 138, 140, 142, 144, 147, 149, +151, 153, 155, 157, 159, 161, 164, 166, 168, 170, 172, 174, 176, 178, 181, +183, 185, 187, 189, 191, 193, 195, 198, 200, 202, 204, 206, 208, 210, 212, +215, 217, 219, 221, 223, 225, 227, 229, 232, 234, 236, 238, 240, 242, 244, +246, 249, 251, 253, 255}; + +// the real HSV rainbow +rgbpixel_t trueHSV(int angle) +{ + byte red, green, blue; + + if (angle<60) {red = 255; green = HSVlights[angle]; blue = 0;} else + if (angle<120) {red = HSVlights[120-angle]; green = 255; blue = 0;} else + if (angle<180) {red = 0, green = 255; blue = HSVlights[angle-120];} else + if (angle<240) {red = 0, green = HSVlights[240-angle]; blue = 255;} else + if (angle<300) {red = HSVlights[angle-240], green = 0; blue = 255;} else + {red = 255, green = 0; blue = HSVlights[360-angle];} + + //return {red, green, blue}; + return {red, green, blue}; +} + +// the 'power-conscious' HSV rainbow +rgbpixel_t powerHSV(int angle) +{ + byte red, green, blue; + if (angle<120) {red = HSVpower[120-angle]; green = HSVpower[angle]; blue = 0;} else + if (angle<240) {red = 0; green = HSVpower[240-angle]; blue = HSVpower[angle-120];} else + {red = HSVpower[angle-240]; green = 0; blue = HSVpower[360-angle];} + + return {red, green, blue}; +} + +// sine wave rainbow +rgbpixel_t sineHSV(int angle) +{ + return {lights[(angle+120)%360], lights[angle], lights[(angle+240)%360]}; +} + + +// hsv out: 0-360, input rgb 0-255 +float RGBToHSV(rgbpixel_t rgb) { + float delta; // min; + float h = 0, v; //, s; + + uint8_t min_ = std::min(std::min(rgb.red, rgb.grn), rgb.blu); + v = std::max(std::max(rgb.red, rgb.grn), rgb.blu); + delta = v - min_; + + if (rgb.red == v){ + h = (rgb.grn - rgb.blu) / delta; + }else if (rgb.grn == v){ + h = 2 + (rgb.blu - rgb.red) / delta; + }else if (rgb.blu == v){ + h = 4 + (rgb.red - rgb.grn) / delta; + } + + h *= 60; + + if(h < 0.0){ h = h + 360;} + if(h > 359.99){ h = 359.99;} + return isnan(h) ? 0.0 : h; +} + + +rgbpixel_t HSVToRGB(float H) { + float r = 0.0, g = 0.0, b = 0.0; + float S = 1.0; + float V = 1.0; + + if (H < 0) { H += 360; } + if (H > 359) { H -= 360; } + + if (S == 0.0) { + r = V; g = V; b = V; + }else { + int i; + float f, p, q, t; + + if (H == 360){ H = 0; } + else { H = H / 60; } + + i = (int)trunc(H); + f = H - i; + + p = V * (1.0 - S); + q = V * (1.0 - (S * f)); + t = V * (1.0 - (S * (1.0 - f))); + + switch (i) { + case 0: + r = V; g = t; b = p; break; + case 1: + r = q; g = V; b = p; break; + case 2: + r = p; g = V; b = t; break; + case 3: + r = p; g = q; b = V; break; + case 4: + r = t; g = p; b = V; break; + default: + r = V; g = p; b = q; break; + } + } + + rgbpixel_t pix; + pix.red = (uint8_t)(r * 255); + pix.grn = (uint8_t)(g * 255); + pix.blu = (uint8_t)(b * 255); + + return pix; +} + diff --git a/z_old/HSVTable.h b/z_old/HSVTable.h new file mode 100644 index 0000000..f88957c --- /dev/null +++ b/z_old/HSVTable.h @@ -0,0 +1,23 @@ +#ifndef HSVTABLE_H +#define HSVTABLE_H + +#include "LEDStrip.h" + + +rgbpixel_t trueHSV(int angle); + +rgbpixel_t powerHSV(int angle); + +rgbpixel_t sineHSV(int angle); + +rgbpixel_t HSVToRGB(float H); + +// Returne a 0-360 float +float RGBToHSV(rgbpixel_t rgb); + +// Input 0-360 float + + + + +#endif \ No newline at end of file diff --git a/z_old/LEDStrip.cpp b/z_old/LEDStrip.cpp new file mode 100644 index 0000000..dd54478 --- /dev/null +++ b/z_old/LEDStrip.cpp @@ -0,0 +1,473 @@ +#include "LEDStrip.h" +#include "driver/i2s.h" +#include +#include "HSVTable.h" + +static const char* tag = "LEDStrip"; + +LEDSTRIP::LEDSTRIP(int port, int size, int pin, LED_ORDER ledOrder, int shift, int offset) +{ + this->port = port; + this->size = size; + this->shift = shift; + this->offset = offset; + this->effSize = this->size - this->offset; + this->ledOrder = ledOrder; + + // create pixel array and buffer array + pixels = new rgbpixel_t[size]; + out_buffer_size = size * PIXEL_SIZE; + out_buffer = new uint8_t[out_buffer_size + RESET_BUFFER_SIZE]; + reset_buffer = &out_buffer[out_buffer_size + 1]; // pointer to reset buffer section + active_out_buffer = &out_buffer[0] + (offset * PIXEL_SIZE); // start of the first active + + // initialize buffer + memset(pixels, 0, this->size * sizeof(rgbpixel_t)); + memset(out_buffer, 0, out_buffer_size + RESET_BUFFER_SIZE); + + dma_frame_size = I2S_TX_DMA_BUFF_SIZE; + if((size * PIXEL_SIZE) < I2S_TX_DMA_BUFF_SIZE) { + dma_frame_size = size * PIXEL_SIZE; + } + + // Although the buffer can be smaller than total buffer size it will require more cpu interrupts + // So this provides for the entire size rounded up to the closes buffer chunk (I2S_TX_DMA_BUFF_SIZE) + int dma_buff_count = 1 + ((out_buffer_size + RESET_BUFFER_SIZE + 2) / I2S_TX_DMA_BUFF_SIZE); + + // install driver + i2s_driver_config_t i2s_config = { + .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_TX), + .sample_rate = SAMPLE_RATE, + .bits_per_sample = I2S_BITS_PER_SAMPLE_8BIT, + .channel_format = i2s_channel_fmt_t(I2S_CHANNEL_FMT_RIGHT_LEFT), + .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S), + .intr_alloc_flags = ESP_INTR_FLAG_LEVEL2, // high interrupt priority, + //.intr_alloc_flags = ESP_INTR_FLAG_LOWMED, // high interrupt priority, + .dma_buf_count = dma_buff_count, // + .dma_buf_len = I2S_TX_DMA_BUFF_SIZE, + .use_apll = true + }; // possibly needed for accuracy + + //i2sEventQueue = xQueueCreate(8, sizeof(i2s_event_t)); + esp_err_t err = i2s_driver_install((i2s_port_t)port, &i2s_config, 8, &i2sEventQueue); + + if(err){ + if(err == ESP_ERR_INVALID_ARG) {ESP_LOGE(tag, "I2S driver error: Parameter error");} + else if(err == ESP_ERR_NO_MEM) {ESP_LOGE(tag, "I2S driver error: Out of Memory");} + else if(err == ESP_ERR_INVALID_STATE) {ESP_LOGE(tag, "I2S driver error: Port is in use");} + } + else{ + i2s_pin_config_t pin_config = {.bck_io_num = I2S_PIN_NO_CHANGE, + .ws_io_num = I2S_PIN_NO_CHANGE, + .data_out_num = pin, + .data_in_num = I2S_PIN_NO_CHANGE }; + + ESP_LOGV(tag, "I2S port pin configured...."); + err = i2s_set_pin((i2s_port_t)port, &pin_config); + if(err){ + if(err == ESP_ERR_INVALID_ARG) {ESP_LOGE(tag, "I2S driver error: Parameter error");} + else if(err == ESP_FAIL) {ESP_LOGE(tag, "I2S driver error: IO error");} + } + else{ + ESP_LOGV(tag, "I2S pin config success!"); + } + } + +} + + +#if(HIGH_RES_BIT_RATE == 1) +static const uint8_t bitpatterns[2] = {0b11000000, 0b11111100}; +void LEDSTRIP::updateBuff(void) { + uint8_t* buff = active_out_buffer; + for (uint16_t i = 0; i < this->effSize; i++) + { + uint8_t red = pixels[i].red >> powerDiv; + uint8_t grn = pixels[i].grn >> powerDiv; + uint8_t blu = pixels[i].blu >> powerDiv; + + *buff++ = bitpatterns[red >> 7 & 0x01]; + *buff++ = bitpatterns[red >> 6 & 0x01]; + *buff++ = bitpatterns[red >> 5 & 0x01]; + *buff++ = bitpatterns[red >> 4 & 0x01]; + *buff++ = bitpatterns[red >> 3 & 0x01]; + *buff++ = bitpatterns[red >> 2 & 0x01]; + *buff++ = bitpatterns[red >> 1 & 0x01]; + *buff++ = bitpatterns[red & 0x01]; + + *buff++ = bitpatterns[grn >> 7 & 0x01]; + *buff++ = bitpatterns[grn >> 6 & 0x01]; + *buff++ = bitpatterns[grn >> 5 & 0x01]; + *buff++ = bitpatterns[grn >> 4 & 0x01]; + *buff++ = bitpatterns[grn >> 3 & 0x01]; + *buff++ = bitpatterns[grn >> 2 & 0x01]; + *buff++ = bitpatterns[grn >> 1 & 0x01]; + *buff++ = bitpatterns[grn & 0x01]; + + *buff++ = bitpatterns[blu >> 7 & 0x01]; + *buff++ = bitpatterns[blu >> 6 & 0x01]; + *buff++ = bitpatterns[blu >> 5 & 0x01]; + *buff++ = bitpatterns[blu >> 4 & 0x01]; + *buff++ = bitpatterns[blu >> 3 & 0x01]; + *buff++ = bitpatterns[blu >> 2 & 0x01]; + *buff++ = bitpatterns[blu >> 1 & 0x01]; + *buff++ = bitpatterns[blu & 0x01]; + } + + *buff++ = 0; +} +#else +static const uint16_t bitpatterns[4] = {0x88, 0x8e, 0xe8, 0xee}; +void LEDSTRIP::updateBuff(void) +{ + uint8_t* buff = active_out_buffer; + + switch(this->ledOrder){ + case ORDER_RGB: + for (uint16_t i = 0; i < this->effSize; i++) + { + uint8_t red = pixels[i].red >> powerDiv; + uint8_t grn = pixels[i].grn >> powerDiv; + uint8_t blu = pixels[i].blu >> powerDiv; + + *buff++ = bitpatterns[red >> 6 & 0x03]; + *buff++ = bitpatterns[red >> 4 & 0x03]; + *buff++ = bitpatterns[red >> 2 & 0x03]; + *buff++ = bitpatterns[red & 0x03]; + + *buff++ = bitpatterns[grn >> 6 & 0x03]; + *buff++ = bitpatterns[grn >> 4 & 0x03]; + *buff++ = bitpatterns[grn >> 2 & 0x03]; + *buff++ = bitpatterns[grn & 0x03]; + + *buff++ = bitpatterns[blu >> 6 & 0x03]; + *buff++ = bitpatterns[blu >> 4 & 0x03]; + *buff++ = bitpatterns[blu >> 2 & 0x03]; + *buff++ = bitpatterns[blu & 0x03]; + } + break; + case ORDER_RBG: + for (uint16_t i = 0; i < this->size; i++) + { + uint8_t red = pixels[i].red >> powerDiv; + uint8_t grn = pixels[i].grn >> powerDiv; + uint8_t blu = pixels[i].blu >> powerDiv; + + *buff++ = bitpatterns[red >> 6 & 0x03]; + *buff++ = bitpatterns[red >> 4 & 0x03]; + *buff++ = bitpatterns[red >> 2 & 0x03]; + *buff++ = bitpatterns[red & 0x03]; + + *buff++ = bitpatterns[blu >> 6 & 0x03]; + *buff++ = bitpatterns[blu >> 4 & 0x03]; + *buff++ = bitpatterns[blu >> 2 & 0x03]; + *buff++ = bitpatterns[blu & 0x03]; + + *buff++ = bitpatterns[grn >> 6 & 0x03]; + *buff++ = bitpatterns[grn >> 4 & 0x03]; + *buff++ = bitpatterns[grn >> 2 & 0x03]; + *buff++ = bitpatterns[grn & 0x03]; + } + break; + case ORDER_GRB: + for (uint16_t i = 0; i < this->size; i++) + { + uint8_t red = pixels[i].red >> powerDiv; + uint8_t grn = pixels[i].grn >> powerDiv; + uint8_t blu = pixels[i].blu >> powerDiv; + + *buff++ = bitpatterns[grn >> 6 & 0x03]; + *buff++ = bitpatterns[grn >> 4 & 0x03]; + *buff++ = bitpatterns[grn >> 2 & 0x03]; + *buff++ = bitpatterns[grn & 0x03]; + + *buff++ = bitpatterns[red >> 6 & 0x03]; + *buff++ = bitpatterns[red >> 4 & 0x03]; + *buff++ = bitpatterns[red >> 2 & 0x03]; + *buff++ = bitpatterns[red & 0x03]; + + *buff++ = bitpatterns[blu >> 6 & 0x03]; + *buff++ = bitpatterns[blu >> 4 & 0x03]; + *buff++ = bitpatterns[blu >> 2 & 0x03]; + *buff++ = bitpatterns[blu & 0x03]; + } + break; + case ORDER_GBR: + for (uint16_t i = 0; i < this->size; i++) + { + uint8_t red = pixels[i].red >> powerDiv; + uint8_t grn = pixels[i].grn >> powerDiv; + uint8_t blu = pixels[i].blu >> powerDiv; + + *buff++ = bitpatterns[grn >> 6 & 0x03]; + *buff++ = bitpatterns[grn >> 4 & 0x03]; + *buff++ = bitpatterns[grn >> 2 & 0x03]; + *buff++ = bitpatterns[grn & 0x03]; + + *buff++ = bitpatterns[blu >> 6 & 0x03]; + *buff++ = bitpatterns[blu >> 4 & 0x03]; + *buff++ = bitpatterns[blu >> 2 & 0x03]; + *buff++ = bitpatterns[blu & 0x03]; + + *buff++ = bitpatterns[red >> 6 & 0x03]; + *buff++ = bitpatterns[red >> 4 & 0x03]; + *buff++ = bitpatterns[red >> 2 & 0x03]; + *buff++ = bitpatterns[red & 0x03]; + } + break; + case ORDER_BRG: + for (uint16_t i = 0; i < this->size; i++) + { + uint8_t red = pixels[i].red >> powerDiv; + uint8_t grn = pixels[i].grn >> powerDiv; + uint8_t blu = pixels[i].blu >> powerDiv; + + *buff++ = bitpatterns[blu >> 6 & 0x03]; + *buff++ = bitpatterns[blu >> 4 & 0x03]; + *buff++ = bitpatterns[blu >> 2 & 0x03]; + *buff++ = bitpatterns[blu & 0x03]; + + *buff++ = bitpatterns[red >> 6 & 0x03]; + *buff++ = bitpatterns[red >> 4 & 0x03]; + *buff++ = bitpatterns[red >> 2 & 0x03]; + *buff++ = bitpatterns[red & 0x03]; + + *buff++ = bitpatterns[grn >> 6 & 0x03]; + *buff++ = bitpatterns[grn >> 4 & 0x03]; + *buff++ = bitpatterns[grn >> 2 & 0x03]; + *buff++ = bitpatterns[grn & 0x03]; + } + break; + default: //ORDER_BGR: + for (uint16_t i = 0; i < this->size; i++) + { + uint8_t red = pixels[i].red >> powerDiv; + uint8_t grn = pixels[i].grn >> powerDiv; + uint8_t blu = pixels[i].blu >> powerDiv; + + *buff++ = bitpatterns[blu >> 6 & 0x03]; + *buff++ = bitpatterns[blu >> 4 & 0x03]; + *buff++ = bitpatterns[blu >> 2 & 0x03]; + *buff++ = bitpatterns[blu & 0x03]; + + *buff++ = bitpatterns[grn >> 6 & 0x03]; + *buff++ = bitpatterns[grn >> 4 & 0x03]; + *buff++ = bitpatterns[grn >> 2 & 0x03]; + *buff++ = bitpatterns[grn & 0x03]; + + *buff++ = bitpatterns[red >> 6 & 0x03]; + *buff++ = bitpatterns[red >> 4 & 0x03]; + *buff++ = bitpatterns[red >> 2 & 0x03]; + *buff++ = bitpatterns[red & 0x03]; + } + break; + } +} +#endif + +void LEDSTRIP::show(bool update) +{ + if(update){ updateBuff(); }; + + //i2s_stop((i2s_port_t)this->port); + i2s_start((i2s_port_t)this->port); + i2s_zero_dma_buffer((i2s_port_t)this->port); + i2s_write_data(out_buffer, out_buffer_size + RESET_BUFFER_SIZE); + + /* + i2s_event_t i2s_event; + while(1){ + if(xQueueReceive(i2sEventQueue, &i2s_event, 100)){ + if(i2s_event.type == I2S_EVENT_TX_DONE) { break;} + }else{ + break; + } + } + */ +} + +void LEDSTRIP::setPowerDiv(uint8_t div){ + if(div < 0){div = 0;} + if(div > 4){div = 4;} + powerDiv = div; +} + +// Function to write data to the I2S buffer +void LEDSTRIP::i2s_write_data(const void *data, size_t len) +{ + size_t bytesWritten = 0; + + while (bytesWritten < len) { + size_t bytesToWrite = len - bytesWritten; + + size_t written = 0; + esp_err_t err = i2s_write(I2S_NUM_0, (const char*)data + bytesWritten, bytesToWrite, &written, portMAX_DELAY); + if (err != ESP_OK) { + // Handle error + ESP_LOGE(tag, "i2s send error: %d", err); + break; + } + + bytesWritten += written; + } +} + +inline int LEDSTRIP::calcIndex(int index) +{ + //int x = (index + shift) % effSize; + //if(x < 0) { // convert nex index to positive range + // x += effSize; + //} + //return x + offset; + int x = (index + shift) % effSize; + x = (x < 0) ? (x + effSize) : x; + return (x + offset) % effSize; +} + +void LEDSTRIP::setPixel(int index, rgbpixel_t& col) +{ + pixels[calcIndex(index)] = col; +} + +void LEDSTRIP::setPixel(int index, const rgbpixel_t col) +{ + pixels[calcIndex(index)] = col; +} + +/* +void LEDSTRIP::setPixel(int index, rgbpixel_t col, uint8_t scale) +{ + uint16_t n = scale + 1; + uint8_t r = (col.red * n) >> 8; + uint8_t g = (col.grn * n) >> 8; + uint8_t b = (col.blu * n) >> 8; + pixels[calcIndex(index)] = {r, g, b}; +} +*/ + +void LEDSTRIP::setPixelRaw(int index, rgbpixel_t& col){ + pixels[index] = col; +} + +void LEDSTRIP::setPixelMirrored(int index, rgbpixel_t col) +{ + pixels[calcIndex(index)] = col; + pixels[calcIndex(-index-1)] = col; +} + +rgbpixel_t LEDSTRIP::getPixel(int index) +{ + return pixels[calcIndex(index)]; +} + +void LEDSTRIP::rotatePixels(LED_DIR dir) +{ + // store the shifted out pixels + rgbpixel_t tPix; + if(dir == DIR_FWD){ + tPix = pixels[size -1]; + for(int i=size-1; i > offset; i--){ + pixels[i] = pixels[i-1]; + } + pixels[offset] = tPix; + }else{ + tPix = pixels[offset]; + for(int i=offset; i < size-1; i++){ + pixels[i] = pixels[i+1]; + } + pixels[size-1] = tPix; + } +} + +void LEDSTRIP::shiftPixels(int numPixels, LED_DIR dir) +{ + // Calculate the shift direction and absolute shift count + int shiftDir = (dir == DIR_FWD) ? 1 : -1; + int absShiftCount = (numPixels % this->effSize) * shiftDir; + + // Shift the pixels by swapping them in place using circular buffering + for (int i = 0; i < this->effSize; i++) { + rgbpixel_t temp = pixels[i]; + int j = i; + + while (true) { + int k = j + absShiftCount; + + if (k < 0) { + k += this->effSize; + } else if (k >= this->effSize) { + k -= this->effSize; + } + + if (k == i) { + break; + } + + pixels[j] = pixels[k]; + j = k; + } + + pixels[j] = temp; + + // Check if we have completed a cycle + if (j == i + absShiftCount) { + break; + } + } +} + +void scalePixel(rgbpixel_t& pix, uint8_t _scale) +{ + uint16_t n = _scale + 1; + pix.red = (pix.red * n) >> 8; + pix.grn = (pix.grn * n) >> 8; + pix.blu = (pix.blu * n) >> 8; +} + +void linearizePixel(rgbpixel_t& pix){ + pix.red = ((int)pix.red * (int)pix.red ) >> 8; + pix.grn = ((int)pix.grn * (int)pix.grn ) >> 8; + pix.blu = ((int)pix.blu * (int)pix.blu ) >> 8; +} + + +void PixelFadeToBlack(rgbpixel_t& pix, uint8_t fadeValue) { + pix.red = (pix.red <= 8) ? 0 : pix.red - ((pix.red * fadeValue)>>8); + pix.grn = (pix.grn <= 8) ? 0 : pix.grn - ((pix.grn * fadeValue)>>8); + pix.blu = (pix.blu <= 8) ? 0 : pix.blu - ((pix.blu * fadeValue)>>8); +} + +void LEDSTRIP::zeroPixels(void) +{ + for(int i=0; i < this->size; i++){ + pixels[i] = {0,0,0}; + } +} + +void LEDSTRIP::fill(rgbpixel_t color, int startIndex, int count) +{ + int endIndex = startIndex + count; + for (int i = startIndex; i < endIndex; i++) { + pixels[calcIndex(i)] = color; + } +} + +// a factor of 0=no changer, factor=255 newCol replaces 100% +void LEDSTRIP::transitionPixel(int index, rgbpixel_t& newCol, uint8_t factor) +{ + int i = calcIndex(index); + int f = factor + 1; + pixels[i].red = (pixels[i].red * (256 - f) + newCol.red * f) >> 8; + pixels[i].grn = (pixels[i].grn * (256 - f) + newCol.grn * f) >> 8; + pixels[i].blu = (pixels[i].blu * (256 - f) + newCol.blu * f) >> 8; +} + +LED_ORDER getRGBOrder(const char* order){ + if(strcmp(order, "rgb")==0){ return ORDER_RGB; } + if(strcmp(order, "rbg")==0){ return ORDER_RBG; } + if(strcmp(order, "grb")==0){ return ORDER_GRB; } + if(strcmp(order, "gbr")==0){ return ORDER_GBR; } + if(strcmp(order, "brg")==0){ return ORDER_BRG; } + if(strcmp(order, "bgr")==0){ return ORDER_BGR; } + else{ return ORDER_GRB; } +} \ No newline at end of file diff --git a/z_old/LEDStrip.h b/z_old/LEDStrip.h new file mode 100644 index 0000000..6c18f0a --- /dev/null +++ b/z_old/LEDStrip.h @@ -0,0 +1,117 @@ +#ifndef LEDSTRIP_H +#define LEDSTRIP_H + +//#include +#include "LEDStrip.h" +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "esp_intr_alloc.h" + +#define HIGH_RES_BIT_RATE 0 + +#if(HIGH_RES_BIT_RATE == 1) + #define PIXEL_SIZE (8*3) + #define SAMPLE_RATE (360000) // 1.6us cycle + #define RESET_BUFFER_SIZE (100 * PIXEL_SIZE) // led latching timeout 300us mininum (50us older chips) +#else + // This can be set to 1 (normally 50) as long at the update freq isn't fast enough to interfere with the required reset length time + // This reduces memory requirements + #define RESET_PIXELS 1 + #define PIXEL_SIZE (4*3)// each colour takes 4 bytes and 3 colors per pixel + #define SAMPLE_RATE (160000) // 1.6us cycle + #define RESET_BUFFER_SIZE (RESET_PIXELS * PIXEL_SIZE) // led latching timeout 4 or more pixel lengths +#endif + +#define I2S_TX_DMA_BUFF_SIZE 128 // smaller buffer means it will start quicker + +typedef struct{ + uint8_t red; + uint8_t grn; + uint8_t blu; +} rgbpixel_t; + +enum LED_DIR{DIR_REV, DIR_FWD}; + +enum LED_ORDER{ORDER_RGB, ORDER_RBG, ORDER_GRB, ORDER_GBR, ORDER_BRG, ORDER_BGR}; + +LED_ORDER getRGBOrder(const char* order); + +void PixelFadeToBlack(rgbpixel_t& pix, uint8_t fadeValue); + +void scalePixel(rgbpixel_t& pix, uint8_t _scale); +void linearizePixel(rgbpixel_t& pix); +//rgbpixel_t scalePixel(rgbpixel_t pix, uint8_t _scale); + +//void i2sDmaInterruptHandler(void *arg); + +class LEDSTRIP { + public: + SemaphoreHandle_t i2sSemaphore; + int effSize; + int size; + int shift; + int offset; + int powerDiv = 0; + LED_ORDER ledOrder; + rgbpixel_t* pixels; + QueueHandle_t i2sEventQueue; + + LEDSTRIP(int port, int size, int pin, LED_ORDER ledOrder, int shift=0, int offset=0); + // process buffer before sending + void updateBuff(void); + // Send data to dma and I2S port + void show(bool update=true); + + void setPixel(int index, rgbpixel_t& col); + void setPixel(int index, const rgbpixel_t col); + void setPixel(int index, rgbpixel_t& col, uint8_t scale=255); + //void setPixelTrueHue(int index, int hueAngle); + //void setPixelPowerHue(int index, int hueAngle); + //void setPixelSineHue(int index, int hueAngle); + void setPixelRaw(int index, rgbpixel_t& col); + + void setPixelMirrored(int index, rgbpixel_t col); + rgbpixel_t getPixel(int index); + + //void scale(rgbpixel_t& pix, uint8_t _scale); + void fill(rgbpixel_t color, int startIndex, int count); + void fade(rgbpixel_t& col); + void zeroPixels(void); + //void setMirrored(void); + + // mirror pixels about the shift position + void rotatePixels(LED_DIR dir); + void shiftPixels(int numPixels, LED_DIR dir); + void transitionPixel(int index, rgbpixel_t& newCol, uint8_t factor); + //rgbpixel_t hueToRGB(uint8_t hue, uint8_t sat, uint8_t val); + + void setPowerDiv(uint8_t div); + + + private: + static int instanceCount; //number in class instances + + int port; + + + //intr_handle_t i2sInterruptHandle; + + int dma_frame_size; + // dma output buffer + uint8_t* out_buffer; + int out_buffer_size; + // pointer to actual active part of buffer + uint8_t* active_out_buffer; + // index position calculation + uint8_t* reset_buffer; + void i2s_write_data(const void *data, size_t size); + int calcIndex(int ind); + +}; + +//void IRAM_ATTR i2s0DmaTransmitComplete(void* arg); +//void IRAM_ATTR i2s1DmaTransmitComplete(void* arg); + +#endif diff --git a/z_old/color_tools.cpp b/z_old/color_tools.cpp new file mode 100644 index 0000000..3dc53d0 --- /dev/null +++ b/z_old/color_tools.cpp @@ -0,0 +1,69 @@ +#include "color_tools.h" +#include + +#define MAX_HUE 360.0 + + +/************************* CLASS - HUE PALLET DISPENSER ************************/ + +HUE_PALLET_DISPENSER::HUE_PALLET_DISPENSER(void){ + Initialize(0, 360, 6); +} + +void HUE_PALLET_DISPENSER::Initialize(float hue, float range, int colSteps){ + this->range = range; + this->hueSteps = colSteps; + + this->startHue = hue - this->range / 2; + if(this->startHue < 0.0){ + this->startHue += MAX_HUE; + }else if(this->startHue > MAX_HUE){ + this->startHue -= MAX_HUE; + } + + this->hueIndex = 0; + + if(this->hueSteps <= 1){ + this->hueIncrement = 0.0; + this->currHue = hue; + }else{ + this->hueIncrement = this->range / (this->hueSteps - 1); + } +} + + +int HUE_PALLET_DISPENSER::GetNextPalletHue(void){ + if(this->hueSteps > 1){ + this->currHue = this->startHue + this->hueIndex * this->hueIncrement; + if(this->currHue < 0){ + this->currHue += MAX_HUE; + }else if(this->currHue > MAX_HUE){ + this->currHue -= MAX_HUE; + } + + // TODO Remove later + //rgbpixel_t p = HUEtoRGB(huePallet.currHue); + //Log.traceln(" index: %d, hue= %F, col: %d, %d, %d", huePallet.hueIndex, huePallet.currHue, p.red, p.grn, p.blu); + + this->hueIndex = ++this->hueIndex % this->hueSteps; + return round(this->currHue); + }else{ + return round(this->currHue); + } +} + +float HUE_PALLET_DISPENSER::PeekNextPalletHue(int hueOffset){ + float tempHue = this->startHue + (this->hueIndex + hueOffset) * this->hueIncrement; + + if(tempHue < 0){ + tempHue += MAX_HUE; + }else if(tempHue > MAX_HUE){ + tempHue -= MAX_HUE; + } + + return tempHue; +} + +void HUE_PALLET_DISPENSER::SetHueIndex(int hueIndex){ + this->currHue = hueIndex; +} diff --git a/z_old/color_tools.h b/z_old/color_tools.h new file mode 100644 index 0000000..176ef2c --- /dev/null +++ b/z_old/color_tools.h @@ -0,0 +1,38 @@ +#ifndef _COLOR_TOOLS_H +#define _COLOR_TOOLS_H + + +class HUE_PALLET_DISPENSER { + public: + float range = 1.0; + float hueIncrement = 1.0; + float currHue = 1.0; + int hueIndex = 0; + int hueSteps = 1; + float startHue = 0; + + //HUE_PALLET_DISPENSER(float hue1, float hue2, int colSteps); + HUE_PALLET_DISPENSER(void); + void Initialize(float hue, float range, int colSteps); + int GetNextPalletHue(void); + float PeekNextPalletHue(int offset); + void SetHueIndex(int hueIndex); + private: +}; + +enum SPEED_PROFILE {SPEED_SIMPLE, SPEED_SQUARE, SPEED_LIN_ACCEL}; + +class SPEED_DISPENSER{ + public: + + SPEED_DISPENSER(void); + + void Initialize(int min, int max, int steps, SPEED_PROFILE prof); + int GetNextSpeedInterval(void); +}; + + + + + +#endif \ No newline at end of file diff --git a/z_old/fileSystem.cpp b/z_old/fileSystem.cpp new file mode 100644 index 0000000..d36929d --- /dev/null +++ b/z_old/fileSystem.cpp @@ -0,0 +1,74 @@ +#include "fileSystem.h" +#include +#include + +static const char* tag = "fs"; + +void Init_File_System(void) +{ + if(!LittleFS.begin()){ + ESP_LOGE(tag, "An Error occured while mounting LittleFS"); + return; + } + + // To format all space in LITTLEFS + // LITTLEFS.format() + + // Get all information of your LITTLEFS + ESP_LOGD(tag, "File system info."); + ESP_LOGD(tag, "Total: %d, Used: %d, Free:%d", LittleFS.totalBytes(), LittleFS.usedBytes(), LittleFS.totalBytes() - LittleFS.usedBytes()); + + // Open dir folder + File dir = LittleFS.open("/"); + + // Cycle all the content + printAllFiles(); +} + +void getAllDirectories(String directoryList[], int& count) +{ + File root = LittleFS.open("/"); + if (!root.isDirectory()) { return; } // Root is not a directory + + directoryList[0] = "/"; + count = 1; + + File file = root.openNextFile(); + while (file) { + if (file.isDirectory()) { + directoryList[count] = '/' + String(file.name()); + count++; + } + file = root.openNextFile(); + } + + root.close(); +} + +void printFilesInDirectories(String directoryList[], int count) +{ + for (int i = 0; i < count; i++) { + ESP_LOGD(tag, "Dir: %s", directoryList[i].c_str()); + + File dir = LittleFS.open(directoryList[i]); + File file = dir.openNextFile(); + while (file) { + if (!file.isDirectory()) { + ESP_LOGD(tag, " File: %s", file.name()); + } + file = dir.openNextFile(); + } + dir.close(); + } +} + +void printAllFiles(void) +{ + String directories[8]; + int dirCount = 0; + + getAllDirectories(directories, dirCount); + + ESP_LOGD(tag, "File System Listing:"); + printFilesInDirectories(directories, dirCount); +} \ No newline at end of file diff --git a/z_old/fileSystem.h b/z_old/fileSystem.h new file mode 100644 index 0000000..1dbb701 --- /dev/null +++ b/z_old/fileSystem.h @@ -0,0 +1,23 @@ +#ifndef _FILESYSTEM_H +#define _FILESYSTEM_H + +#define SPIFFS LITTLEFS +#include + +const int MAX_DIRECTORIES = 8; + +void Init_File_System(void); + +void readTestFile(void); + +//void printDirectory(File dir, int numTabs = 3); + +void writeFilesToSerial(void); + +void getAllDirectories(String directoryList[], int& count); + +void printFilesInDirectories(String directoryList[], int count); + +void printAllFiles(void); + +#endif \ No newline at end of file diff --git a/z_old/my_board.cpp b/z_old/my_board.cpp new file mode 100644 index 0000000..8eeeb64 --- /dev/null +++ b/z_old/my_board.cpp @@ -0,0 +1,249 @@ +#include "my_board.h" + +#include +#include +#include +#include "global.h" +#include "JsonConstrain.h" + +static const char* tag = "board"; +PWM_Output *pwmOut[4]; +RAMP_Output *RearLightControl; + +void Init_Board_Pins(void) +{ + pinMode(BoardLED1, OUTPUT); + pinMode(BoardLED2, OUTPUT); + + pinMode(Button1_Pin, INPUT_PULLUP); + pinMode(Button2_Pin, INPUT_PULLUP); + pinMode(Button3_Pin, INPUT_PULLUP); + + ESP_LOGV(tag, "Board pins initialized..."); +} + +void Init_PWM_Outputs(void) +{ + File file = LittleFS.open("/cfg/relays.json"); + if(!file){ + ESP_LOGE(tag, "Error opening relays.json..."); + }else{ + StaticJsonDocument<1024> doc; + DeserializationError error = deserializeJson(doc, file); + if(!error){ + file.close(); + if(error){ ESP_LOGE(tag, "relays.json deserialize error!.."); return;} + + JsonArray jsArray = doc["relays"]; + + if(jsArray.isNull() || jsArray.size() < 1 || jsArray.size() > 4 ) { return; } + + int x = 0; + for(JsonObject obj : jsArray){ + //Default config if keys are not found + if (!obj.containsKey("pin") || !obj.containsKey("freq") || !obj.containsKey("max") || !obj.containsKey("default")) { + pwmOut[x] = new PWM_Output(0, x, 12, 500, 100.0, false); + ESP_LOGD(tag, "pwmOut[%d] default config", x); + x++; + continue; + } + + int pin; + switch(x){ + case 0: + pin = jsonConstrainInt(obj, "pin", 0, 48, RELAY1_Pin); + break; + case 1: + pin = jsonConstrainInt(obj, "pin", 0, 48, RELAY2_Pin); + break; + case 2: + pin = jsonConstrainInt(obj, "pin", 0, 48, RELAY3_Pin); + break; + default: + pin = jsonConstrainInt(obj, "pin", 0, 48, RELAY4_Pin); + } + + int freq = jsonConstrainInt(obj, "freq", 100, 2000, 500); + float maxVal = jsonConstrainInt(obj, "max", 2.0, 100.0, 95); + float defaultVal = jsonConstrainInt(obj, "default", 0.0, 100.0, 40); + ESP_LOGD(tag, "pwmOut[%d]: freq:%d, max:%F, default:%F", x, freq, maxVal, defaultVal); + + pwmOut[x] = new PWM_Output(pin, x, RELAY_RES, freq, maxVal, false); + pwmOut[x]->setOutput(defaultVal); + x++; + } + }else{ + ESP_LOGE(tag, "Deserialization error on relays.json"); + } + } +} + +int linearizeLED(float inp){ + if(inp > 100.0){ inp = 100.0;}; + return (inp*inp*0.4095f); +} + +/******************* PWM_Output Definition ********************/ + +// max: 0-100% +PWM_Output::PWM_Output(uint8_t pin, uint8_t ch, int res, uint32_t freq, float maxDuty, bool visionCorrected) +{ + this->currDuty = 0; + this->ch = ch; + this->maxDuty = maxDuty; + this->visionCorrected = visionCorrected; + this->freq = freq; + this->res = res; + //this->msecRampRate = msecRampRate; + pinMode(pin, OUTPUT); + if(!ledcSetup(ch, freq, res)) { + ESP_LOGE(tag, "pwmOut-> ch:%d ledcSetup failed!", ch); + return; + } + ledcAttachPin(pin, ch); + setOutput(this->currDuty); +} + +// Range is 0 to 100% +void PWM_Output::setOutput(float duty) +{ + if(duty > maxDuty) { duty = maxDuty;} + + // calculate correct duty value + int outDutyVal; + if(this->visionCorrected){ outDutyVal = linearizeLED(duty); } + else{ outDutyVal = duty * 40.95f; } + + // FIXME pwm output ramp cause a freeze here + // Smooth Transition to final duty value + /* + if(msecRampRate){ + int d = this->currOutVal; + float dutyInc = (outDutyVal - this->currOutVal)/msecRampRate; + for(;;){ + d += dutyInc; + if(d < 0){d = 0;} + if(d > 4095){d = 4095;} + ledcWrite(this->ch, d); + if(d >= outDutyVal || d <= 0){ break; } + vTaskDelay(msecRampRate); + } + ledcWrite(this->ch, outDutyVal); + this->currOutVal = outDutyVal; + } + else{ + */ + ledcWrite(this->ch, outDutyVal); + this->currOutVal = outDutyVal; + //} + + this->currDuty = duty; +} + +void PWM_Output::setFreq(uint32_t fq) +{ + uint32_t newFreq; + + if(this->freq != fq){ + newFreq = ledcChangeFrequency(this->ch, fq, this->res); + if(newFreq){ + this->freq = fq; + } + } +} + + + +/******************* RAMP_Output Definition ********************/ + + + +void Initialize_Rear_Control(int relayIndex, int buttonIndex, int rampTime, int steps, float min, float max){ + RearLightControl = new RAMP_Output(pwmOut[relayIndex], btn[buttonIndex], rampTime, steps, min, max); +} + +RAMP_Output *RAMP_Output::instance = nullptr; // Initialize the static member variable + +void longPressStartCallback(void){ + xTimerStart(RearLightControl->timerHandle, 0); +} + +void longPressStopCallback(void){ + xTimerStop(RearLightControl->timerHandle, 0); + RAMP_Output::instance->SwitchDirection(); // Call the non-static member function using the stored instance +} + +void SingleClickCallback(void){ + RAMP_Output::instance->ClickOnOff(); +} + +void RAMP_Output::TimerCallback(TimerHandle_t xTimer) { + RearLightControl->IncrementTick(); +} + +RAMP_Output::RAMP_Output(PWM_Output *output, OneButton *button, int rampTime, int steps, float min, float max) { + this->Output = output; + this->Button = button; + this->rampTime = rampTime; + this->steps = steps; + this->min = min; + this->max = max; + this->stepSize = (max - min) / steps; + + ESP_LOGD(tag, "Rear Lights: stepSize= %.2f", stepSize); + + instance = this; // Store the instance in the static member variable + + if (this->Button) { + this->Button->attachLongPressStart(longPressStartCallback); + this->Button->attachLongPressStop(longPressStopCallback); + this->Button->attachClick(SingleClickCallback); + } else { + Serial.println("Error: Button is nullptr"); + } + + Output->visionCorrected = true; + Output->currDuty = min; + Output->setOutput(min); + + timerHandle = xTimerCreate("RampTimer", pdMS_TO_TICKS(rampTime/8), pdTRUE, this, TimerCallback); +} + +void RAMP_Output::ClickOnOff(void) { + if(IsOn){ + IsOn = false; + Output->setOutput(0); + }else{ + IsOn = true; + Output->setOutput(lastDuty); + } +} + +void RAMP_Output::IncrementTick(void) { + if (dirFwd) { + // Increment output value while ensuring it doesn't exceed max + float newValue = std::min(Output->currDuty + stepSize, max); + Output->setOutput( newValue); + lastDuty = newValue; + //ESP_LOGD(tag, "up: %.2f", newValue); + } else { + // Decrement output value while ensuring it doesn't go below min + float newValue = std::max(Output->currDuty - stepSize, min); + Output->setOutput( newValue ); + lastDuty = newValue; + //ESP_LOGD(tag, "down: %.2f", newValue); + } +} + +void RAMP_Output::SwitchDirection(void) { + dirFwd = !dirFwd; +} + +// Add destructor to clean up resources +RAMP_Output::~RAMP_Output() { + xTimerDelete(timerHandle, 0); + // Add any other necessary cleanup here +} + + + diff --git a/z_old/my_board.h b/z_old/my_board.h new file mode 100644 index 0000000..27a93fb --- /dev/null +++ b/z_old/my_board.h @@ -0,0 +1,162 @@ +#ifndef _MY_BOARD_H +#define _MY_BOARD_H + +#include +#include "my_buttons.h" + + +#define S3MODULEKIT 0 + +//************************* + +#define BoardLED1 17 +#define BoardLED2 18 + +#define Set_Status_LED1(x) digitalWrite(BoardLED1, x) +#define Set_Status_LED2(x) digitalWrite(BoardLED2, x) + +//************************ + +#define Button1_Pin 8 +#define Button2_Pin 19 +#define Button3_Pin 0 + +#define Button1_State digitalRead(Button1_Pin) +#define Button2_State digitalRead(Button2_Pin) +#define Button3_State digitalRead(Button3_Pin) + +//*********************** + +#define I2C_SDA1_Pin 1 +#define I2C_SCL1_Pin 2 + +#define Buzzer_Pin 37 + +#if S3MODULEKIT == 1 + #define RGBLED1_Pin 48 +#else + #define RGBLED1_Pin 3 +#endif + #define RGBLED2_Pin 46 + +#define RX433_Pin 16 +#define TX433_Pin 38 + +#define VIN_12V_Pin 20 + +#define RELAY_RES 12 +#define RELAY1_Pin 45 +#define RELAY2_Pin 48 +#define RELAY3_Pin 47 +#define RELAY4_Pin 21 +#define RELAY5_Pin 35 /* test*/ + +#define FanIndex 3 + +#define TMP102_ADDR 72 + +#define buzzerCh 4 /* 0-7chs available, 0-3 used by pwm outputs */ + +#define TOUCH1_Pin 9 +#define TOUCH2_Pin 10 +#define TOUCH3_Pin 11 +#define TOUCH4_Pin 12 +#define TOUCH5_Pin 13 +#define TOUCH_SHIELD_Pin 9 + +#define EXT1_Pin 35 +#define EXT2_Pin 36 + +#define OLED_DC 7 +#define OLED_RST 6 +#define OLED_MOSI 5 +#define OLED_SCK 4 +#define OLED_CS 15 + + +int linearizeLED(float inp); + +#define SetRelay(c,val) ledcWrite(c, val); +#define SetFrontLightCorr(val) ledcWrite(FrontConstLightCh,linearizeLED(val)) +#define SetRearLightCorr(val) ledcWrite(RearConstLightCh,linearizeLED(val)) +#define SetFrontLight(val) ledcWrite(FrontConstLightCh, val) +#define SetRearLight(val) ledcWrite(RearConstLightCh,val) + +void Init_Board_Pins(void); + +void Init_PWM_Outputs(void); + +struct _relay +{ + int freq; + bool linCorr; + int max; + int min; +}; + +struct PWMOUT{ + float max; + bool visionCorrected; + int resolution; + int frequency; +}; + +class PWM_Output { + public: + float currDuty; + bool visionCorrected; + PWM_Output(uint8_t pin, uint8_t ch, int res, uint32_t freq, float maxDuty, bool visionCorrected=false); + void setOutput(float duty); + void setFreq(uint32_t fq); + + float getMaxDuty() const { + return maxDuty; + } + void setMaxDuty(float duty) { + // add any validation or constraints here + if(duty < 0) {duty = 0.0;} + else{if(duty > 100.0) {duty = 100.0;}} + maxDuty = duty; + } + + private: + uint8_t ch; + uint32_t freq; + uint8_t res; + uint8_t currOutVal; + float maxDuty; + int msecRampRate; + +}; + +extern PWM_Output *pwmOut[4]; + + +// Push button Ramp Up/Down Logic for Lights +class RAMP_Output { + public: + TimerHandle_t timerHandle; + RAMP_Output(PWM_Output *output, OneButton *button, int rampTime, int steps, float min, float max); + ~RAMP_Output(); + void IncrementTick(void); + void SwitchDirection(void); + void ClickOnOff(void); + static void TimerCallback(TimerHandle_t xTimer); + static RAMP_Output *instance; // Static member variable to store an instance + private: + PWM_Output *Output; + OneButton *Button; + int steps; + int rampTime; + float min; + float max; + float stepSize; + bool dirFwd = true; + float lastDuty; + bool IsOn = true; + +}; + +void Initialize_Rear_Control(int relayIndex, int buttonIndex, int rampTime, int steps, float min, float max); + +#endif diff --git a/z_old/my_buttons.cpp b/z_old/my_buttons.cpp new file mode 100644 index 0000000..d1465b3 --- /dev/null +++ b/z_old/my_buttons.cpp @@ -0,0 +1,92 @@ +#include "my_buttons.h" +#include "my_board.h" +#include "global.h" +#include "led_strip.h" +#include "my_buzzer.h" + +static const char* tag = "button"; + +OneButton *btn[3]; + +void Init_ButtonEvents(void) +{ + btn[0] = new OneButton(btn1Pin, true, true); + btn[1] = new OneButton(btn2Pin, true, true); + btn[2] = new OneButton(btn3Pin, true, true); + + if(btn[0] != NULL){ + btn[0]->setDebounceTicks(DEBOUCE_TIME); // just below the update period to guarantee 1 sample delay + btn[0]->attachClick(btn1_click); + btn[0]->attachDoubleClick(btn1_doubleClick); + } + else { + ESP_LOGW(tag, "Button1 Not Initialized"); + } + + if(btn[1] != NULL){ + btn[1]->setDebounceTicks(DEBOUCE_TIME); + btn[1]->attachClick(btn2_click); + btn[1]->attachDoubleClick(btn2_doubleClick); + btn[1]->attachLongPressStart(btn2_LongPressStart); + } + else { + ESP_LOGW(tag, "Button2 Not Initialized"); + } + + if(btn[2] != NULL){ + btn[2]->setDebounceTicks(DEBOUCE_TIME); + btn[2]->attachClick(btn3_click); + btn[2]->attachDoubleClick(btn3_doubleClick); + btn[2]->attachLongPressStart(btn3_LongPressStart); + btn[2]->attachLongPressStop(btn3_LongPressStop); + } + else { + ESP_LOGW(tag, "Button3 Not Initialized"); + } + + ESP_LOGV(tag, "Initialized Buttons Events...\n"); +} + +void btn1_click() { + IncrementEventIndex(); + Pulse_LED_Status(150); + ESP_LOGD(tag, "btn1 1x"); +} + +void btn1_doubleClick() { + ESP_LOGD(tag, "btn1 2x"); +} + +void btn2_click() { + Pulse_LED_Status(150); + Buzzer_Beep(150); + // send packet + ESP_LOGD(tag, "btn2 1x"); +} + +void btn2_doubleClick() { + ESP_LOGD(tag, "btn2 2x"); +} + +void btn2_LongPressStart(){ + // scan for devices +} + +void btn3_click() { + Pulse_LED_Status(150); + ESP_LOGD(tag, "btn3 1x"); +} + +void btn3_doubleClick() { + ESP_LOGD(tag, "btn3 2x"); +} + +//bool IncFwd = true; +void btn3_LongPressStart(){ + ESP_LOGD(tag, "btn3 long press"); +} + +void btn3_LongPressStop(){ + //IncFwd = !IncFwd; // switch increment direction +} + diff --git a/z_old/my_buttons.h b/z_old/my_buttons.h new file mode 100644 index 0000000..467eb12 --- /dev/null +++ b/z_old/my_buttons.h @@ -0,0 +1,34 @@ +#ifndef _MY_BUTTONS_H +#define _MY_BUTTONS_H + +#include "OneButton.h" +#include "global.h" + +#define DEBOUCE_TIME (BUTTON_UPDATE_PERIOD-24) + +#define btn1Pin Button1_Pin +#define btn2Pin Button2_Pin +#define btn3Pin Button3_Pin + +extern OneButton *btn[3]; + + +#define Update_Buttons() btn[1]->tick(); btn[2]->tick(); btn[3]->tick(); + +void Init_ButtonEvents(void); + +void btn1_click(); +void btn1_doubleClick(); + +void btn2_click(); +void btn2_doubleClick(); +void btn2_LongPressStart(); + +void btn3_click(); +void btn3_doubleClick(); +void btn3_LongPressStart(); +void btn3_LongPressStop(); + + + +#endif \ No newline at end of file diff --git a/z_old/my_buzzer.cpp b/z_old/my_buzzer.cpp new file mode 100644 index 0000000..2bd8dfc --- /dev/null +++ b/z_old/my_buzzer.cpp @@ -0,0 +1,123 @@ +#include "my_buzzer.h" +#include +#include +#include +#include +#include "my_board.h" +#include "global.h" +#include "JsonConstrain.h" +#include "global.h" + +//#define buzzPin Buzzer_Pin + +static const char* tag = "buzzer"; + +MelodyPlayer* player; + +BUZZ_TUNE buzzTune[12]; +uint8_t buzzPin; + +void Init_Buzzer(uint8_t pin) +{ + File file = LittleFS.open("/cfg/buzzer.json"); + if(!file){ + file.close(); + ESP_LOGE(tag, "Error opening buzzer.json..."); + }else{ + JsonDocument doc; + DeserializationError error = deserializeJson(doc, file); + file.close(); + if(error){ ESP_LOGE(tag, "buzzer.json deserialize error!.."); return; } + + if(jsonConstrainBool(tag, doc.as(), "en", false)){ + //player = new MelodyPlayer(buzzPin, buzzerCh, false); + int freeChannel = findUnusedLedcChannel(); + freeChannel = 1; + player = new MelodyPlayer(buzzPin, freeChannel, false); + + if(player){ + Buzzer_Load_Tunes(doc.as()); // Load Tunes + ESP_LOGD(tag, "Buzzer initialized.. using Ch:%d", freeChannel); + } + else{ + ESP_LOGE(tag, "Buzzer initialization failed.."); + } + + } + } +} + +void Buzzer_Play_Tune(TUNE_TYPE tune, bool async, bool hasPriority) +{ + static int prev_tune = -1; + static Melody melody; + + //melody = MelodyFactory.loadRtttlString( buzzTune[tune].melody.c_str() ); + //player->playAsync(melody); + + + if(!player) { + ESP_LOGV(tag, "no buzzer"); + return; + } + + //if(!player->isPlaying() || (player->isPlaying() && hasPriority)){ + // ESP_LOGD(tag, "Playing tune: %d, melody: %s", tune, buzzTune[tune].melody.c_str()); + + // if(prev_tune == tune){ + // ESP_LOGD(tag, "Same tune: %d, melody: %s", tune, buzzTune[tune].melody.c_str()); + // for(int c = 0; c < buzzTune[tune].cycles; c++){ + // ( async ) ? player->playAsync() : player->play(); + // } + // } + // else{ + melody = MelodyFactory.loadRtttlString( buzzTune[tune].melody.c_str() ); + prev_tune = tune; + ESP_LOGD(tag, "New tune: %d, melody: %s", tune, buzzTune[tune].melody.c_str()); + + for(int c = 0; c < buzzTune[tune].cycles; c++){ + ( async ) ? player->playAsync(melody) : player->play(melody); + + } + // } + //} + //else{ + // ESP_LOGD(tag, "buzzer busy"); + //} + +} + +void Buzzer_Beep(int mSecs, int freq) +{ + ledcAttachPin(buzzPin, buzzerCh); + ledcSetup(buzzerCh, 2000, 8); + ledcWrite(buzzerCh, 125); + vTaskDelay(mSecs); + ledcWrite(buzzerCh, 0); +} + +void Buzzer_Load_Tunes(const JsonObject &doc) +{ + const char* tuneName[] PROGMEM = {"boot", "error", "success", "click", "beep", + "wifi-conn", "wifi-disc", "ble-conn", "ble-disc", + "download", "waiting", "restart", "test", "ack", nullptr}; + + int listCount = 0; + while (true){ + if(tuneName[listCount] == nullptr){ break; } + listCount++; + } + + JsonObject js[listCount]; // JsonObject leaks in a loop so create separate objects + + listCount = 3; + + for(int i = 0; i < listCount; i++){ + js[i] = doc[tuneName[i]]; + buzzTune[i].cycles = jsonConstrain(tag, js[i], "cycles", 1, 100, 1); + buzzTune[i].pause = jsonConstrain(tag, js[i], "pause", 0, 100, 0); + buzzTune[i].melody = jsonConstrainString(tag, js[i], "tune", "Ack:d=16,o=5,b=112:b,b#").c_str(); + ESP_LOGD(tag, "tune %d : %s", i, buzzTune[i].melody.c_str()); + } + ESP_LOGV(tag, "loaded %d tunes", listCount); +} \ No newline at end of file diff --git a/z_old/neo_colors.h b/z_old/neo_colors.h new file mode 100644 index 0000000..41e4cbd --- /dev/null +++ b/z_old/neo_colors.h @@ -0,0 +1,138 @@ +#ifndef NEO_COLORS_H +#define NEO_COLORS_H + +#include "LEDStrip.h" + +/* +rgbpixel_t pallet_rainbow[1]; +rgbpixel_t pallet_white[1]; +rgbpixel_t pallet_USA[1]; +rgbpixel_t pallet_halloween[1]; +rgbpixel_t pallet_christmas[1]; +rgbpixel_t pallet_autumn[1]; +rgbpixel_t pallet_summer[1]; +rgbpixel_t pallet_neon[1]; +*/ + + + +#define USE_CORRECTED_COLORS 0 + +#define col_black color_pallet[0] +#define col_white color_pallet[1] +#define col_red color_pallet[2] +#define col_green color_pallet[3] +#define col_blue color_pallet[4] +#define col_orange color_pallet[5] +#define col_yellow color_pallet[6] +#define col_cyan color_pallet[7] +#define col_magenta color_pallet[8] +#define col_purple color_pallet[9] +#define col_pink color_pallet[10] +#define col_teal color_pallet[11] +#define col_lime color_pallet[12] +#define col_indigo color_pallet[13] +#define col_maroon color_pallet[14] +#define col_navy color_pallet[15] +#define col_olive color_pallet[16] +#define col_beige color_pallet[17] +#define col_brown color_pallet[18] +#define col_coral color_pallet[19] +#define col_gold color_pallet[20] +#define col_gray color_pallet[21] +#define col_ivory color_pallet[22] +#define col_khaki color_pallet[23] +#define col_lavender color_pallet[24] +#define col_peach color_pallet[25] +#define col_periwinkle color_pallet[26] +#define col_salmon color_pallet[27] +#define col_sienna color_pallet[28] +#define col_silver color_pallet[29] +#define col_tan color_pallet[30] +#define col_turquoise color_pallet[31] +#define col_violet color_pallet[32] + + +#if USE_CORRECTED_COLORS == 0 + +const rgbpixel_t color_pallet[] = { + {0 , 0 , 0 }, // col_black + {255, 255, 255}, // col_white + {255, 0 , 0 }, // col_red + {0 , 255, 0 }, // col_green + {0 , 0 , 255}, // col_blue + {255, 165, 0 }, // col_orange + {255, 255, 0 }, // col_yellow + {0 , 255, 255}, // col_cyan + {255, 0 , 255}, // col_magenta + {128, 0 , 128}, // col_purple + {255, 192, 203}, // col_pink + {0 , 128, 128}, // col_teal + {0 , 255, 0 }, // col_lime + {75 , 0 , 130}, // col_indigo + {128, 0 , 0 }, // col_maroon + {0 , 0 , 128}, // col_navy + {128, 128, 0 }, // col_olive + {245, 245, 220}, // col_beige + {165, 42 , 42 }, // col_brown + {255, 127, 80 }, // col_coral + {255, 215, 0 }, // col_gold + {128, 128, 128}, // col_gray + {255, 255, 240}, // col_ivory + {240, 230, 140}, // col_khaki + {230, 230, 250}, // col_lavender + {255, 218, 185}, // col_peach + {204, 204, 255}, // col_periwinkle + {250, 128, 114}, // col_salmon + {160, 82 , 45}, // col_sienna + {192, 192, 192}, // col_silver + {210, 180, 140}, // col_tan + {64 , 224, 208}, // col_turquoise + {238, 130, 238} // col_violet +}; + +#else + +const rgbpixel_t color_pallet[] = +{ + {0 , 0 , 0 }, // col_black + {255, 255, 255}, // col_white + {255, 0 , 0 }, // col_red + {0 , 255, 0 }, // col_green + {0 , 0 , 255}, // col_blue + + {255, 128, 0 }, // col_orange + {255, 255, 0 }, // col_yellow + {0 , 255, 255}, // col_cyan + {255, 0 , 255}, // col_magenta + {170, 0 , 255}, // col_purple + {255, 170, 255}, // col_pink + {0 , 128, 128}, // col_teal + {128, 255, 0 }, // col_lime + {85 , 0 , 255}, // col_indigo + {128, 0 , 0 }, // col_maroon + {0 , 0 , 128}, // col_navy + {128, 128, 0 }, // col_olive + {255, 230, 204}, // col_beige + {153, 51 , 0 }, // col_brown + {255, 102, 102}, // col_coral + {204, 153, 0 }, // col_gold + {128, 128, 128}, // col_gray + {255, 255, 204}, // col_ivory + {204, 204, 0 }, // col_khaki + {204, 153, 255}, // col_lavender + {255, 204, 153}, // col_peach + {153, 153, 255}, // col_periwinkle + {255, 153, 102}, // col_salmon + {153, 76 , 0 }, // col_sienna + {204, 204, 204}, // col_silver + {204, 153, 102}, // col_tan + {0 , 204, 204}, // col_turquoise + {204, 0 , 255} // col_violet +}; +#endif + + + + +#endif diff --git a/z_old/old/anim-list.json b/z_old/old/anim-list.json new file mode 100644 index 0000000..fd3a27f --- /dev/null +++ b/z_old/old/anim-list.json @@ -0,0 +1,105 @@ +{ + "whitefills":[ + { + "name":"Bottom/Up Fill", + "speed":"", + "hue-range":"", + "param1":"Time: ", + "param2":"Const Light Delay: ", + "check1":"", + "check2":"", + "check3":"", + "check4":"50% Lum" + } + ], + "animations":[ + { + "name":"Rainbow", + "speed":"Speed: ", + "hue-range":"", + "param1":"", + "param2":"", + "check1":"", + "check2":"", + "check3":"", + "check4":"50% Lum" + }, + { + "name":"Hue Spectrum", + "speed":"Speed: ", + "hue-range":"Hue Range: ", + "param1":"", + "param2":"", + "check1":"Dir Rev ", + "check2":"", + "check3":"", + "check4":"50% Lum" + }, + { + "name":"Color Pulse Cycle", + "speed":"Speed: ", + "hue-range":"Hue Range: ", + "param1":"", + "param2":"Colors: ", + "check1":"", + "check2":"", + "check3":"", + "check4":"50% Lum" + }, + { + "name":"Comets", + "speed":"Speed: ", + "hue-range":"Hue Range: ", + "param1":"Sectors: ", + "param2":"Colors: ", + "check1":"Mixed Colors ", + "check2":"Random Decay", + "check3":"Shorter Tail ", + "check4":"50% Lum" + }, + { + "name":"Dashes", + "speed":"Speed: ", + "hue-range":"Hue Range: ", + "param1":"Sectors: ", + "param2":"Colors: ", + "check1":"Mixed Colors ", + "check2":"Rotate ", + "check3":"", + "check4":"50% Lum" + }, + { + "name":"Snakes", + "speed":"Speed: ", + "hue-range":"Hue Range: ", + "param1":"Sectors: ", + "param2":"Colors: ", + "check1":"Mixed Colors ", + "check2":"Rotate ", + "check3":"", + "check4":"50% Lum" + }, + { + "name":"Sectors", + "speed":"Speed: ", + "hue-range":"Hue Range: ", + "param1":"Sectors: ", + "param2":"Colors: ", + "check1":"", + "check2":"Rotate ", + "check3":"", + "check4":"50% Lum" + }, + { + "name":"Fire", + "speed":"Speed: ", + "hue-range":"Run-time per: ", + "param1":"Cooling: ", + "param2":"Sparking: ", + "check1":"Cycle Colors ", + "check2":"", + "check3":"", + "check4":"50% Lum" + } + ] +} \ No newline at end of file diff --git a/z_old/old/anim-profile-common.json b/z_old/old/anim-profile-common.json new file mode 100644 index 0000000..87730d5 --- /dev/null +++ b/z_old/old/anim-profile-common.json @@ -0,0 +1,9 @@ +{ + "countdown": { + "min": 0, + "max": 98, + "hold": 1000, + "ramp": 500 + }, + "profile-index": 3 +} \ No newline at end of file diff --git a/z_old/old/anim-profile1.json b/z_old/old/anim-profile1.json new file mode 100644 index 0000000..96209ec --- /dev/null +++ b/z_old/old/anim-profile1.json @@ -0,0 +1,149 @@ +{ +"name": "Basic", +"events": [ + { + "anim": 0, + "hue": -1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 0, + "check1": false, + "check2": true, + "check3": false, + "check4": false + }, + { + "anim": 1, + "hue": 40, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 2, + "hue": 80, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": true, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 150, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + } +] +} \ No newline at end of file diff --git a/z_old/old/anim-profile2.json b/z_old/old/anim-profile2.json new file mode 100644 index 0000000..530f239 --- /dev/null +++ b/z_old/old/anim-profile2.json @@ -0,0 +1,149 @@ +{ +"name": "Rosey", +"events": [ + { + "anim": 0, + "hue": -1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 0, + "check1": false, + "check2": true, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": true, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + } +] +} \ No newline at end of file diff --git a/z_old/old/anim-profile3.json b/z_old/old/anim-profile3.json new file mode 100644 index 0000000..195e8c5 --- /dev/null +++ b/z_old/old/anim-profile3.json @@ -0,0 +1,149 @@ +{ +"name": "Bluish", +"events": [ + { + "anim": 0, + "hue": -1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 0, + "check1": false, + "check2": true, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": true, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + } +] +} \ No newline at end of file diff --git a/z_old/old/anim-profile4.json b/z_old/old/anim-profile4.json new file mode 100644 index 0000000..949c7ad --- /dev/null +++ b/z_old/old/anim-profile4.json @@ -0,0 +1,149 @@ +{ +"name": "USA", +"events": [ + { + "anim": 0, + "hue": -1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 0, + "check1": false, + "check2": true, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": true, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + } +] +} \ No newline at end of file diff --git a/z_old/old/anim-profile5.json b/z_old/old/anim-profile5.json new file mode 100644 index 0000000..38980f7 --- /dev/null +++ b/z_old/old/anim-profile5.json @@ -0,0 +1,149 @@ +{ +"name": "Summer", +"events": [ + { + "anim": 0, + "hue": -1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 0, + "check1": false, + "check2": true, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": true, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + } +] +} \ No newline at end of file diff --git a/z_old/old/anim-profile6.json b/z_old/old/anim-profile6.json new file mode 100644 index 0000000..5fa5a46 --- /dev/null +++ b/z_old/old/anim-profile6.json @@ -0,0 +1,149 @@ +{ +"name": "Winter", +"events": [ + { + "anim": 0, + "hue": -1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 0, + "check1": false, + "check2": true, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": true, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + } +] +} \ No newline at end of file diff --git a/z_old/old/anim-profile7.json b/z_old/old/anim-profile7.json new file mode 100644 index 0000000..f9205df --- /dev/null +++ b/z_old/old/anim-profile7.json @@ -0,0 +1,149 @@ +{ +"name": "Christmas", +"events": [ + { + "anim": 0, + "hue": -1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 0, + "check1": false, + "check2": true, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": true, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + } +] +} \ No newline at end of file diff --git a/z_old/old/anim-profile8.json b/z_old/old/anim-profile8.json new file mode 100644 index 0000000..eb06cd8 --- /dev/null +++ b/z_old/old/anim-profile8.json @@ -0,0 +1,149 @@ +{ +"name": "Halloween", +"events": [ + { + "anim": 0, + "hue": -1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 0, + "check1": false, + "check2": true, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": true, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + } +] +} \ No newline at end of file diff --git a/z_old/old/anim-profiles copy.json b/z_old/old/anim-profiles copy.json new file mode 100644 index 0000000..e50c0e4 --- /dev/null +++ b/z_old/old/anim-profiles copy.json @@ -0,0 +1,1203 @@ +{ + "countdown": { + "min": 0, + "max": 98, + "hold": 1000, + "ramp": 500 + }, + "profile-index": 3, + "profiles": [ + { + "name": "Profile 1", + "events": [ + { + "anim": 0, + "hue": -1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 0, + "check1": false, + "check2": true, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": true, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + } + ] + }, + { + "name": "Profile 2", + "events": [ + { + "anim": 0, + "hue": -1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 0, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + } + ] + }, + { + "name": "Profile 3", + "events": [ + { + "anim": 0, + "hue": -1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 0, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + } + ] + }, + { + "name": "Profile 4", + "events": [ + { + "anim": 0, + "hue": -1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 75, + "check1": false, + "check2": true, + "check3": false, + "check4": false + }, + { + "anim": 1, + "hue": 0, + "hue-range": 340, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 3, + "hue": 0, + "hue-range": 340, + "speed": 75, + "param1": 30, + "param2": 60, + "check1": false, + "check2": true, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 40, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + } + ] + }, + { + "name": "Profile 5", + "events": [ + { + "anim": 0, + "hue": -1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 0, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + } + ] + }, + { + "name": "Profile 5", + "events": [ + { + "anim": 0, + "hue": -1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 0, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + } + ] + }, + { + "name": "Wedding 7", + "events": [ + { + "anim": 0, + "hue": -1, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 0, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + } + ] + }, + { + "name": "Wedding 8", + "events": [ + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 0, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + }, + { + "anim": 0, + "hue": 10, + "hue-range": 1, + "speed": 50, + "param1": 50, + "param2": 50, + "check1": false, + "check2": false, + "check3": false, + "check4": false + } + ] + } + ] +} \ No newline at end of file diff --git a/z_old/old/anim-settings.json b/z_old/old/anim-settings.json new file mode 100644 index 0000000..3889b0e --- /dev/null +++ b/z_old/old/anim-settings.json @@ -0,0 +1,97 @@ +{ + "twinkle":{ + "col1": "#000000", + "col2": "#000000", + "density": 1, + "speed": 25, + "pwr": 255 + }, + "Strobe":{ + "col1": "#000000", + "col2": "#000000", + "density": 1, + "speed": 25, + "pwr": 255 + }, + "Solid":{ + "col1": "#000000", + "col2": "#000000", + "density": 1, + "pwr": 255 + }, + "Fade":{ + "col1": "#000000", + "range": 128, + "speed": 25, + "pwr": 255 + }, + "HueSwirl":{ + "col1": "#000000", + "range": 128, + "speed": 25, + "pwr": 255 + }, + "Meteors":{ + "col1": "#000000", + "col2": "#000000", + "range": 1, + "density": 1, + "speed": 25, + "pwr": 255 + }, + "Dashes":{ + "col1": "#000000", + "col2": "#000000", + "col3": "#000000", + "segments": 1, + "density": 1, + "speed": 25, + "pwr": 255 + }, + "DashesRange":{ + "col1": "#000000", + "col2": "#000000", + "range": 128, + "segments": 1, + "density": 1, + "speed": 25, + "pwr": 255 + }, + "Fire":{ + "col1": "#000000", + "cool": 25, + "spark": 25, + "speed": 25, + "pwr": 255 + }, + "Rain":{ + "col1": "#000000", + "col2": "#000000", + "range": 128, + "density": 1, + "speed": 25, + "pwr": 255 + }, + "Stacking":{ + "col1": "#000000", + "col2": "#000000", + "range": 128, + "speed": 25, + "pwr": 255 + }, + "Snake":{ + "col1": "#000000", + "col2": "#000000", + "range": 128, + "speed": 25, + "pwr": 255 + }, + "Theater":{ + "col1": "#000000", + "col2": "#000000", + "range": 128, + "speed": 25, + "step": 20, + "pwr": 255 + } +} \ No newline at end of file diff --git a/z_old/old/app-events.json b/z_old/old/app-events.json new file mode 100644 index 0000000..fa7b393 --- /dev/null +++ b/z_old/old/app-events.json @@ -0,0 +1,90 @@ +{ + "index": 0, + "apps":[ + { + "name": "DSLR-Booth Configuration", + "events":[ + "White Fill / Countdown", + "Experience Selection", + "Select Sharing Screen", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + { + "name": "FotoFliqs Configuration", + "events":[ + "White Fill / Countdown", + "Experience Selection", + "Preview", + "Notice!", + "Idle / Pause", + "Advertisement", + "Printing", + "Phone Input / QR Code / Apple Drop", + "", + "", + "", + "" + ] + }, + { + "name": "Touchpix Configuration", + "events":[ + "White Fill / Countdown", + "Experience Selection", + "Preview", + "Notice!", + "Idle / Pause", + "Advertisement", + "Printing", + "Phone Input / QR Code / Apple Drop", + "", + "", + "", + "" + ] + }, + { + "name": "FotoZap Configuration", + "events":[ + "White Fill / Countdown", + "Experience Selection", + "Select Sharing Screen", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + { + "name": "Twineit Configuration", + "events":[ + "White Fill / Countdown", + "Experience Selection", + "Select Sharing Screen", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + } + ] +} \ No newline at end of file diff --git a/z_old/old/ble.json b/z_old/old/ble.json new file mode 100644 index 0000000..8cd73ca --- /dev/null +++ b/z_old/old/ble.json @@ -0,0 +1,9 @@ +{ + "en": "true", + "core": 1, + "device-name": "ATA_COMM", + "key": "123456", + "service-uuid": "6E400001-B5A3-F393-E0A9-E50E24DCCA9E", + "char-uuid-rx": "6E400002-B5A3-F393-E0A9-E50E24DCCA9E", + "char-uuid-tx": "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" +} \ No newline at end of file diff --git a/z_old/old/board.json b/z_old/old/board.json new file mode 100644 index 0000000..2077f39 --- /dev/null +++ b/z_old/old/board.json @@ -0,0 +1,63 @@ +{ + "use_ver":"hwver15", + "hwver14":{ + "rgb1": 3, + "rgb2": 46, + "btn1": 8, + "btn2": 19, + "btn3": 0, + "buzzer": 37, + "touch1": 9, + "touch2": 10, + "touch3": 11, + "touch4": 12, + "touch5": 13, + "shield": 9, + "relay1": 1, + "relay2": 2, + "relay3": 3, + "relay4": 4, + "stat1": 17, + "stat2": 18, + "adc1": 20, + "oled_dc": 7, + "oled_rst": 6, + "oled_mosi": 5, + "oled_sck": 4, + "oled_cs": 15, + "ext1": 35, + "ext2": 36, + "rf433tx": 38, + "rf433rx": 16 + }, + "hwver15":{ + "rgb1": 3, + "rgb2": 9, + "btn1": 8, + "btn2": 18, + "btn3": 0, + "buzzer": 42, + "touch1": 11, + "touch2": 12, + "touch3": 13, + "touch4": 21, + "touch5": -1, + "shield": 14, + "relay1": 38, + "relay2": 48, + "relay3": 47, + "relay4": 21, + "stat1": 39, + "stat2": 37, + "adc1": 10, + "oled_dc": 7, + "oled_rst": 6, + "oled_mosi": 5, + "oled_sck": 4, + "oled_cs": 15, + "ext1": 41, + "ext2": -1, + "rf433tx": 40, + "rf433rx": 16 + } +} diff --git a/z_old/old/led-devices.json b/z_old/old/led-devices.json new file mode 100644 index 0000000..7f5fd74 --- /dev/null +++ b/z_old/old/led-devices.json @@ -0,0 +1,42 @@ +{ + "strip1": { + "en": true, + "size": 138, + "chip": "SK6812", + "rgb-order": "rgb", + "shift":-27, + "offset": 0, + "power-div": 0, + "pin": 3, + "i2s-ch": 0, + "core": 1 + }, + "strip2": { + "en": false, + "size": 20, + "chip": "SK6812", + "rgb-order": "grb", + "shift":0, + "offset": 0, + "power-div": 0, + "pin": 46, + "i2s-ch": 1, + "core": 0 + }, + "front-light": { + "en": true, + "relay": 0, + "core": 1, + "style": 0, + "delay": 0.75 + }, + "rear-light": { + "en": true, + "relay": 1, + "button": 2, + "min": 0.0, + "max": 100.0, + "ramp":3000, + "steps":8 + } +} \ No newline at end of file diff --git a/z_old/old/ramp-lights.json b/z_old/old/ramp-lights.json new file mode 100644 index 0000000..97a7052 --- /dev/null +++ b/z_old/old/ramp-lights.json @@ -0,0 +1,23 @@ +{ + "ramp-lights": + [ + { + "en": true, + "relay-index": 0, + "button-index": 0, + "min": 5.0, + "max": 100.0, + "step": 1.5, + "skip-count": 5 + }, + { + "en": true, + "relay-index": 1, + "button-index": 1, + "min": 5.0, + "max": 100.0, + "step": 1.5, + "skip-count": 5 + } + ] +} \ No newline at end of file diff --git a/z_old/old/relays.json b/z_old/old/relays.json new file mode 100644 index 0000000..7e11224 --- /dev/null +++ b/z_old/old/relays.json @@ -0,0 +1,41 @@ +{ +"relays": + [ + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + } + ] +} \ No newline at end of file diff --git a/z_old/old/system.json b/z_old/old/system.json new file mode 100644 index 0000000..8cc156a --- /dev/null +++ b/z_old/old/system.json @@ -0,0 +1,146 @@ +{ + "boardver": "board15", + "mode": 0, + "buttons": + [ + { + "en": true + }, + { + "en": true + }, + { + "en": true + } + ], +"pwmout": + [ + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": false, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + }, + { + "en": true, + "freq": 250, + "min": 0, + "max": 100, + "default": 0, + "vision": true, + "deltarate": 0 + } + ], + "ramp-lights": + [ + { + "en": true, + "relay-index": 0, + "button-index": 0, + "min": 5.0, + "max": 100.0, + "step": 1.5, + "skip-count": 5 + }, + { + "en": true, + "relay-index": 1, + "button-index": 1, + "min": 5.0, + "max": 100.0, + "step": 1.5, + "skip-count": 5 + } + + ], + "oled": { + "en": false, + "height": 64, + "width": 128 + }, + "t-sensor": { + "en": true, + "addr": 72, + "sp1": 85.0, + "fan-pwr1": 50.0, + "sp2": 90.0, + "fan-pwr2": 100.0, + "hyst": 1.0, + "relay": 3, + "interval": 5000 + }, + "adc": { + "ain1_factor": 1.0 + }, + "status-led":{ + "interval": 250 + }, + "strip1": { + "en": true, + "size": 138, + "chip": "SK6812", + "rgb-order": "rgb", + "shift":-27, + "offset": 0, + "power-div": 0, + "i2s-ch": 0, + "core": 1 + }, + "strip2": { + "en": true, + "size": 30, + "chip": "SK6812", + "rgb-order": "rgb", + "shift":-27, + "offset": 0, + "power-div": 0, + "i2s-ch": 0, + "core": 1 + }, + "rx433": { + "en": "true" + }, + "tx433": { + "en": "true" + }, + "ble":{ + "en": true, + "name": "Ata_Lights", + "core": 1 + }, + "wifi-client":{ + "en": true, + "mdns-name": "atadev", + "ssid": "padillaguest", + "pass": "padillaguest" + }, + "wifi-ap":{ + "ssid": "ATA_AP", + "append-id": true, + "pass": "12345678", + "ip": [192,168,10.1], + "gateway": [192,168,10,200], + "subnet": [255,255,255,0] + } +} diff --git a/z_old/old/touch-pins.json b/z_old/old/touch-pins.json new file mode 100644 index 0000000..d556439 --- /dev/null +++ b/z_old/old/touch-pins.json @@ -0,0 +1,30 @@ +{ + "touchpins": + [ + { + "en": true, + "pin": 23, + "min": 100 + }, + { + "en": true, + "pin": 23, + "min": 100 + }, + { + "en": true, + "pin": 23, + "min": 100 + }, + { + "en": true, + "pin": 23, + "min": 100 + }, + { + "en": true, + "pin": 23, + "min": 100 + } + ] +} \ No newline at end of file diff --git a/z_old/wifi_temp.cpp b/z_old/wifi_temp.cpp new file mode 100644 index 0000000..4d2645f --- /dev/null +++ b/z_old/wifi_temp.cpp @@ -0,0 +1,1449 @@ + +/* + wifi is started in STATION mode and tries to connected to AP saved + in the creds.json file. It will keep trying to connect forever. If + it connects then the board LEDS will toggle, the buzzer will play a tune + and the IP address will be sent to the serial port. Also the device will + be discoverable as "http://atadev.local/" + + If credentials need to be changed then double reset can be done to + trigger the AP mode and a wifi config page will be available on address + 192.168.4.1. Credentials can be entered and applied then you can reset + the device so it connects with the correct credentials. + +*/ +#include "my_wifi.h" + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "global.h" +#include +#include "common/my_buzzer.h" +#include "my_board.h" +#include "common/led_animation.h" +#include "led_strip.h" +#include "common/fileSystem.h" +#include "JsonConstrain.h" +#include "led_strip.h" +#include "my_oled.h" +#include "BTSerial.h" +#include "esp_log.h" + +#include "firmware_html.h" + +int RebootSystem = 0; +#define WIFI_TIMEOUT_MS 10000 + +static const char* tag = "wifi"; + +bool WifiClientConnected = false; +AsyncWebServer webServer(80); +DNSServer *dnsServer; +#define DNS_PORT 53 + +#define StartDelayedRebooth RebootSystem = 1000/BUTTON_UPDATE_PERIOD; // start reboot countdown for 1sec + +String ssid; +String passPhrase; + +String AP_SSID; +String AP_Pass; +String mDnsName; +String HostName; + +IPAddress local_IP(192,168,10,1); +IPAddress gateway(192,168,10,200); +IPAddress subnet(255,255,255,0); + +// for file manager page +String filesDropdownOptions((char*)0); +String dirDropdownOptions((char*)0); +String savePath((char*)0); // needed for storing file when editing a file +String savePathInput((char*)0); +const char* http_username = "admin"; +const char* http_password = "admin"; +const char* param_delete_path = "delete-path"; +const char* param_edit_path = "edit-path"; +const char* param_dir_pad = "dir-path"; +const char* param_edit_textarea = "edit-textarea"; +const char* param_save_path = "save-path"; +String allowedExtensionsForEdit = "txt, h, htm, html, css, cpp, js, json, ini, cfg"; +//const char* jquery = "/jquery-3.6.3.min.js"; + +void Init_Wifi_Task(void) +{ + File file = LittleFS.open("/cfg/wifi.json", "r"); + if(!file){ + ESP_LOGE(tag, "Failed to open wifi.json!"); + file.close(); + return; + } + + // Parse the JSON file + StaticJsonDocument<1024> doc; + DeserializationError error = deserializeJson(doc, file); + if(!error){ + JsonObject wifiJson = doc["wifi"]; + file.close(); + mDnsName = jsonConstrainString(tag, wifiJson,"mdns-name", "atadev"); + ESP_LOGV(tag, "mDnsName: %s", mDnsName.c_str()); + + if(jsonConstrainBool(tag, wifiJson, "en", false)){ + JsonObject j = doc["cred1"]; + Wifi_Read_Credentials( j ); + xTaskCreatePinnedToCore(Wifi_Task, "Wifi_Task", 1024*10, NULL, 1, NULL, CONFIG_ARDUINO_RUNNING_CORE); + ESP_LOGV(tag, "Initialized WiFi (task created)..."); + } + + JsonObject ap = doc["ap"]; + AP_SSID = jsonConstrainString(tag, ap, "ssid", "ATA_AP"); + if(jsonConstrainBool(tag, ap, "append-id", true)){ + String macHex = String(chipInfo.macByte[1], HEX) + String(chipInfo.macByte[0], HEX); + AP_SSID += "_"; + macHex.toUpperCase(); + AP_SSID += macHex; + } + AP_Pass = jsonConstrainString(tag, ap, "pass", "12345678"); + }else{ + ESP_LOGE(tag, "Deserialization error for wifi.json"); + } +} + +void onWiFiEvent(WiFiEvent_t event) +{ + //Serial.printf("[WiFi-event] event: %d\n", event); + switch (event) { + case ARDUINO_EVENT_WIFI_READY: + //Serial.println("WiFi interface ready"); + break; + case ARDUINO_EVENT_WIFI_SCAN_DONE: + //Serial.println("Completed scan for access points"); + break; + case ARDUINO_EVENT_WIFI_STA_START: + //Serial.println("WiFi client started"); + break; + case ARDUINO_EVENT_WIFI_STA_STOP: + //Serial.println("WiFi clients stopped"); + break; + case ARDUINO_EVENT_WIFI_STA_CONNECTED: + WifiClientConnected = true; + //Serial.println("Connected to access point"); + break; + case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: + WifiClientConnected = false; + ESP_LOGV(tag,"WiFi Disconnected"); + //Buzzer_Play_Tune(TUNE_WIFI_DISCONNECTED); + break; + case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE: + //Serial.println("Authentication mode of access point has changed"); + break; + case ARDUINO_EVENT_WIFI_STA_GOT_IP: + ESP_LOGV(tag,"My IP: %s", WiFi.localIP().toString()); + Wifi_Start_MDNS(); + Buzzer_Play_Tune(TUNE_WIFI_CONNECTED); + break; + case ARDUINO_EVENT_WIFI_STA_LOST_IP: + //Serial.println("Lost IP address and IP address is reset to 0"); + break; + case ARDUINO_EVENT_WPS_ER_SUCCESS: + //Serial.println("WiFi Protected Setup (WPS): succeeded in enrollee mode"); + break; + case ARDUINO_EVENT_WPS_ER_FAILED: + //Serial.println("WiFi Protected Setup (WPS): failed in enrollee mode"); + break; + case ARDUINO_EVENT_WPS_ER_TIMEOUT: + // Serial.println("WiFi Protected Setup (WPS): timeout in enrollee mode"); + break; + case ARDUINO_EVENT_WPS_ER_PIN: + //Serial.println("WiFi Protected Setup (WPS): pin code in enrollee mode"); + break; + case ARDUINO_EVENT_WIFI_AP_START: + //Serial.println("WiFi access point started"); + break; + case ARDUINO_EVENT_WIFI_AP_STOP: + //Serial.println("WiFi access point stopped"); + break; + case ARDUINO_EVENT_WIFI_AP_STACONNECTED: + //Serial.println("Client connected"); + break; + case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED: + ESP_LOGV(tag,"SoftAP Client Disconnected"); + //Buzzer_Play_Tune(TUNE_WIFI_DISCONNECTED); + break; + case ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED: + ESP_LOGV(tag,"SoftAP Client Connected"); + Buzzer_Play_Tune(TUNE_WIFI_CONNECTED); + break; + case ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED: + //Serial.println("Received probe request"); + break; + case ARDUINO_EVENT_WIFI_AP_GOT_IP6: + //Serial.println("AP IPv6 is preferred"); + break; + case ARDUINO_EVENT_WIFI_STA_GOT_IP6: + //Serial.println("STA IPv6 is preferred"); + break; + case ARDUINO_EVENT_ETH_GOT_IP6: + //Serial.println("Ethernet IPv6 is preferred"); + break; + case ARDUINO_EVENT_ETH_START: + //Serial.println("Ethernet started"); + break; + case ARDUINO_EVENT_ETH_STOP: + //Serial.println("Ethernet stopped"); + break; + case ARDUINO_EVENT_ETH_CONNECTED: + //Serial.println("Ethernet connected"); + break; + case ARDUINO_EVENT_ETH_DISCONNECTED: + //Serial.println("Ethernet disconnected"); + break; + case ARDUINO_EVENT_ETH_GOT_IP: + // Serial.println("Obtained IP address"); + break; + default: break; + } +} + +void Wifi_Task(void *parameters) +{ + // Extend watchdog timer + esp_task_wdt_init(2, false); + + Setup_WebServer_Handlers(&webServer); + WiFi.onEvent(onWiFiEvent); + WiFi.setHostname(mDnsName.c_str()); + WiFi.softAPConfig(local_IP, gateway, subnet); + WiFi.softAP(AP_SSID.c_str(), AP_Pass.c_str()); + Wifi_Start_MDNS(); + vTaskDelay(100); + webServer.begin(); + + // Choose WIFI Mode + if(commMode == COMM_WIFI_AP_BLE){ // AP Only + WiFi.mode(WIFI_AP); + while(1){ vTaskDelay(500 / portTICK_PERIOD_MS); } + }else{ // AP & Client (dslrbooth) + esp_wifi_set_ps(WIFI_PS_NONE); + WiFi.mode(WIFI_AP_STA); + Wifi_Client_Loop(); + } +} + +void Wifi_Client_Loop(){ + while(true){ + // Wifi status check loop + if(WiFi.status() == WL_CONNECTED){ + vTaskDelay(250); + continue; + } + + // Start WiFi Client + ESP_LOGD(tag, "Wifi trying stored creds: %s..%s",ssid.c_str(), passPhrase.c_str()); + WiFi.begin(ssid.c_str(), passPhrase.c_str()); + + //Wait until connected or timeout + ESP_LOGV(tag, "WiFi Connecting..."); + unsigned long startAttemptTime = millis(); + while(WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < WIFI_TIMEOUT_MS){ + vTaskDelay(100 / portTICK_PERIOD_MS); + } + + // Delay if wifi connection fails and continue + if(WiFi.status() != WL_CONNECTED){ + ESP_LOGW(tag, "WIFI Connection Failed!"); + for(int i = 0; i < WIFI_TIMEOUT_MS/100; i++){ + vTaskDelay(100); + } + continue; + } + } +} + +void Wifi_Start_MDNS(void) +{ + ESP_LOGV(tag, "Initializing MDNS: %s", mDnsName.c_str()); + if (!MDNS.begin(mDnsName.c_str())) { + ESP_LOGE(tag, "Error setting up MDNS responder!"); + }else{ + ESP_LOGV(tag, "You can access device via http://%s.local", mDnsName); + } +} + +void Wifi_Read_Credentials(const JsonObject &credJson){ + if(credJson.isNull()){ // set generic defaults on error + ESP_LOGE(tag, "Failed to find wifi key"); + + ssid = "Generic"; + passPhrase = "password"; + //strncpy(ssid, "Generic", sizeof(ssid)-1); + //strncpy(passPhrase, "password", sizeof(passPhrase)-1); + return; + } + else{ + // TODO Restore String + ssid = jsonConstrainString(tag, credJson, "ssid", "default"); + passPhrase = jsonConstrainString(tag, credJson, "pass", "password"); + //strncpy(ssid, jsonConstrainString(tag, credJson, "ssid", "default").c_str(), sizeof(ssid)-1); + //strncpy(passPhrase, jsonConstrainString(tag, credJson, "pass", "password").c_str(), sizeof(passPhrase)-1); + } +} + +void Wifi_Save_Credentials(String newSSID, String newPass) +{ + // Open the credentials file writing + File credsFile = LittleFS.open("/cfg/wifi.json", "r+"); + if(!credsFile){ + ESP_LOGE(tag, "Failed to open wifi.json\n"); + return; + } + + // Parse the JSON file + DynamicJsonDocument doc(1024); + DeserializationError error = deserializeJson(doc, credsFile); + if(!error){ + JsonObject cred1Json = doc.createNestedObject("cred1"); + + if(cred1Json){ + cred1Json["ssid"] = newSSID; + cred1Json["pass"] = newPass; + + // Clear file contents before saving the modified JSON + credsFile.seek(0); + + int written = serializeJson(doc, credsFile); // save to file + ESP_LOGD(tag, "Credentials saved... ssid: %s, pass: %s, size written: %d", newSSID, newPass, written); + } + credsFile.close(); + }else{ + ESP_LOGE(tag, "Deserialization error for wifi.json"); + } +} + +void Setup_WebServer_Handlers(AsyncWebServer* serv) +{ + serv->on("/dslrbooth", HTTP_GET, handleGET_DSLRBooth); + + serv->on("/", HTTP_GET, [](AsyncWebServerRequest *request){ + if(WiFi.getMode() == WIFI_AP && !WifiClientConnected){ + request->redirect("/wifi"); + }else{ + request->redirect("/home"); + } + }); + serv->on("/home", HTTP_GET, [](AsyncWebServerRequest *request){ + sendHtmlFile("/www/home.html", request, HomeHtmlProcessor); + }); + serv->on("/lights", HTTP_GET, [](AsyncWebServerRequest *request){ + sendHtmlFile("/www/lights.html", request, htmlProcessor); + }); + serv->on("/setup", HTTP_GET, [](AsyncWebServerRequest *request){ + if(!request->authenticate(http_username, http_password)){ + return request->requestAuthentication(); + } + sendHtmlFile("/www/setup.html", request, htmlProcessor); + }); + serv->on("/wifi", HTTP_GET, [](AsyncWebServerRequest *request){ + if(WiFi.getMode() == WIFI_MODE_APSTA){ + // TODO Disable navigation bar + } + sendHtmlFile("/www/wifi.html", request, htmlProcessor); + }); + serv->on("/files", HTTP_GET, [](AsyncWebServerRequest *request){ + if(!request->authenticate(http_username, http_password)){ + return request->requestAuthentication(); + } + sendHtmlFile("/www/files.html", request, htmlProcessor); + }); + serv->on("/upload", HTTP_POST, [](AsyncWebServerRequest *request) { request->send(200); }, handlePOST_Upload); + + serv->on("/download", HTTP_GET, [](AsyncWebServerRequest *request){ + if (request->hasParam("file")) { + String filename = request->getParam("file")->value(); + if (LittleFS.exists(filename)) { + fs::File file = LittleFS.open(filename, "r"); + if (file) { + request->send(LittleFS, filename, "application/octet-stream"); + file.close(); + return; + } + } + } + request->send(404, "text/plain", "File not found!"); + }); + serv->on("/delete", HTTP_GET, [](AsyncWebServerRequest *request){ + if(!request->authenticate(http_username, http_password)){ + return request->requestAuthentication(); + } + + String filename = request->getParam(param_delete_path)->value(); + if(filename !="choose"){ + LittleFS.remove(filename.c_str()); + ESP_LOGD(tag, "Deleted file: %s", filename.c_str()); + } + + request->redirect("/files"); + }); + serv->on("/edit", HTTP_GET, [](AsyncWebServerRequest *request){ + if(!request->authenticate(http_username, http_password)){ + return request->requestAuthentication(); + } + + String fileName = request->getParam(param_edit_path)->value(); + if(fileName =="new"){ + savePath = "/new.txt"; + } + else{ + savePath = fileName; + } + + ESP_LOGD(tag, "Edit file: %s", savePath.c_str()); + sendHtmlFile("/www/edit.html", request, htmlProcessor); + }); + serv->on("/save", HTTP_GET, [](AsyncWebServerRequest *request){ + if(!request->authenticate(http_username, http_password)){ + return request->requestAuthentication(); + } + String inputMessage((char*)0); + if (request->hasParam(param_edit_textarea)) { + inputMessage = request->getParam(param_edit_textarea)->value(); + } + if (request->hasParam(param_save_path)) { + savePath = request->getParam(param_save_path)->value(); + } + writeFile(LittleFS, savePath.c_str(), inputMessage.c_str()); + + request->redirect("/files"); + }); + serv->on("/update", HTTP_POST, handlePOST_Update, updateCallback); + + // Request Data + serv->on("/get", HTTP_GET, handleGET_Get); + // Post Data + serv->on("/post", HTTP_POST, handlePOST_Post, postFileUpload, postBody); + + serv->on("/generate_204", HTTP_GET, [](AsyncWebServerRequest *request){ + ESP_LOGD(tag, "/generate_204 ... redirect to /wifi"); + request->redirect("/wifi"); + }); + + serv->on("/hotspot-detect.html", HTTP_GET, [](AsyncWebServerRequest *request){ + ESP_LOGD(tag, "/hotspot-detect.html ... redirect to /wifi"); + request->redirect("/wifi"); + }); + + serv->on("/msftconnecttest.txt", HTTP_GET, [](AsyncWebServerRequest *request){ + ESP_LOGD(tag, "/msftconnecttest.txt ... redirect to /wifi"); + request->redirect("/wifi"); + }); + + serv->on("/reg-stick", HTTP_POST, handlePOST_RegStick); + + serv->on("/firmware", HTTP_GET, [](AsyncWebServerRequest *request){ + request->send_P(200, "text/html", firmware_html_page); + }); + + serv->onNotFound([](AsyncWebServerRequest *request){ + if(WiFi.getMode() == WIFI_MODE_APSTA){ + ESP_LOGD(tag, "OnNotFound Redirect to /wifi"); + request->redirect("/wifi"); + }else if(WiFi.getMode() == WIFI_MODE_AP){ + ESP_LOGD(tag, "OnNotFound Redirect to /home"); + request->redirect("/home"); + }else{ + request->send(404); + } + }); + + serv->on("/*", HTTP_GET, handleGET_SendFile); // send any file + +} + +void handlePOST_RegStick(AsyncWebServerRequest *request){ + +} + +bool getpostSuccess = false; +// +void handleGET_Get(AsyncWebServerRequest *request){ + if(request->hasParam("type", false, false)) { + const char* dataType = request->getParam("type", false, false)->value().c_str(); + + if(!strcmp(dataType, "app-events")){ // send json file + request->send(LittleFS, "/cfg/app-events.json", "application/json"); + return; + } + + if(!strcmp(dataType, "anim-profiles")){ // send json file + request->send(LittleFS, "/cfg/anim-profiles.json", "application/json"); + return; + } + + if(!strcmp(dataType, "anim-list")){ + request->send(LittleFS, "/cfg/anim-list.json", "application/json"); + return; + } + + if(!strcmp(dataType, "sys-summary")){ + JsonDocument doc; + CreateSysSummmaryPacket(doc); + String summary; + serializeJson(doc, summary); + request->send(200, "application/json", summary); + return; + } + + if(!strcmp(dataType, "wifi")){ + JsonDocument jsdoc; + jsdoc["ssid"] = ssid; + //jsdoc["pass"] = (int)strlen(passPhrase); + jsdoc["pass"] = passPhrase; + + String jsStr; + serializeJson(jsdoc, jsStr); + + request->send(200, "application/json", jsStr); + return; + } + } + + request->send(400, "text/plain", "Failed"); +} + +void CreateSysSummmaryPacket(JsonDocument &doc){ + JsonObject booth = doc.createNestedObject("booth"); + booth["mode"] = ""; + booth["app"] = sysProps.appName; + booth["strip1"] = (strip1) ? true : false, + booth["strip2"] = (strip2) ? true : false, + booth["front-light"] = true; + booth["rear-light"] = false; + booth["oled"] = (oled) ? true : false; + + //ESP.getHeapSize(), ESP.getFreeHeap(), ESP.getHeapSize()-ESP.getFreeHeap()); + + JsonObject Temperature = doc.createNestedObject("temperature"); + Temperature["temp"] = sysProps.t_sensor.temperature; + Temperature["sp1"] = sysProps.t_sensor.Setpoint1; + doc["system"]["firm-ver"] = FIRMWARE_VER; + + JsonObject chip = doc.createNestedObject("chip"); + chip["flash-size"] = ""; + chip["flash-free"] = ""; + chip["ram-size"] = ESP.getHeapSize(); + chip["ram-free"] = ESP.getFreeHeap(); + + + + // Get the Wi-Fi RSSI + wifi_ap_record_t wifi_ap_info; + esp_err_t rssi_result = esp_wifi_sta_get_ap_info(&wifi_ap_info); + int rssi = 0; int wifi_ch = 0; + String encryp = ""; + if (rssi_result == ESP_OK) { + rssi = wifi_ap_info.rssi; + wifi_ch = wifi_ap_info.primary; + + wifi_auth_mode_t auth_mode = wifi_ap_info.authmode; + switch (auth_mode) { + case WIFI_AUTH_OPEN: + encryp = "Open"; + break; + case WIFI_AUTH_WEP: + encryp = "WEP"; + break; + case WIFI_AUTH_WPA_PSK: + encryp = "WPA_PSK"; + break; + case WIFI_AUTH_WPA2_PSK: + encryp = "WPA2_PSK"; + break; + case WIFI_AUTH_WPA_WPA2_PSK: + encryp = "WPA_WPA2_PSK"; + break; + case WIFI_AUTH_WPA2_ENTERPRISE: + encryp = "WPA2_ENT"; + break; + default: + encryp = "UNKNOWN"; + break; + } + } + + JsonObject wifi = doc.createNestedObject("wifi"); + wifi["client-ip"] = "XXX.XXX.XXX.XXX"; + wifi["mac-addr"] = chipInfo.macStr; + wifi["ch"] = wifi_ch; + wifi["rssi"] = rssi; + wifi["encryp"] = encryp; + + JsonObject ble = doc.createNestedObject("ble"); + ble["en"] = (sysProps.appIndex == DSLRBOOTH_INDEX)? false : true; + ble["clients"] = (BTDeviceConnected)? 1 : 0; + ble["ssid"] = BLEDeviceName; + +} + +void handlePOST_Post(AsyncWebServerRequest *request){ + ESP_LOGD(tag, "posts request.."); + if(!getpostSuccess) { request->send(400, "text/plain", "Error"); } + + request->send(200, "text/plain", "Ok"); +} + +void postFileUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) { + getpostSuccess = false; + Serial.println("post file"); +} + + +// POST Requests +int packets, postTotal; +char postType[24]; +char *postData; +void postBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){ + if (!index){ + packets = 0; + int sectors = (total + 1023 + 64) / 1024; + postData = new char[1024 * sectors]; + } + + // Accumulate Data Chunks + memcpy(postData + index, data, len); + packets++; + + if((index + len) >= total){ + ESP_LOGD(tag, "index: %d, len: %d, total: %d", index, len, total); + ESP_LOGD(tag, "packets: %d", packets); + getpostSuccess = false; + + // Check if the request contains the necessary parameters + if (request->hasParam("type", false, false)) { + const char* dataType = request->getParam("type", false, false)->value().c_str(); + ESP_LOGD(tag, "data type: %s", dataType); + + if(!strcmp(dataType, "anim-profile-common") || !strcmp(dataType, "set-countdown")){ + JsonDocument profJson(1 * 1024); + DeserializationError error = deserializeJson(profJson, postData, total); + + if(!error){ + animProps.profileIndex = profJson["profile-index"].as(); + + JsonObject countdownJson = profJson["countdown"]; + animProps.frontLight.minDuty = countdownJson["min"].as(); + animProps.frontLight.maxDuty = countdownJson["max"].as(); + animProps.frontLight.holdTime = countdownJson["hold"].as(); + animProps.frontLight.rampTime = countdownJson["ramp"].as(); + + int indexChanged = animProps.profileIndex - countdownJson["profile-index"].as(); + animProps.profileIndex = countdownJson["profile-index"].as(); + if(indexChanged){ + load_animation_profileByIndex(animProps.profileIndex); + } + + // Save to disk/flash + if(dataType[0] == 'a'){ + if(updateJsonDocument(profJson, "/cfg/anim-profile-common.json")){ + Buzzer_Beep(150, 3000); + } + } + else{ + ESP_LOGD(tag, "Post:set-countdown, index: %d, min: %.1f, max: %.1f, hold: %d, ramp: %d", animProps.profileIndex, animProps.frontLight.minDuty, animProps.frontLight.maxDuty, animProps.frontLight.holdTime, animProps.frontLight.rampTime); + Buzzer_Beep(150, 3000); + } + }else{ + ESP_LOGE(tag, "Deserialization error for wifi.json"); + } + + getpostSuccess = true; + //goto done; + } + else if(!strncmp(dataType, "anim-profile", 12)){ + if (strlen(dataType) > 12) { + int profile_index = (dataType[12] - '0' - 1) % 8; // extract index, assuming dataType has enough characters + DynamicJsonDocument profJson(4 * 1024); + DeserializationError error = deserializeJson(profJson, postData, total); + + if(!error){ + JsonArray eventsArray = profJson["events"]; + for(int i = 0; i < eventsArray.size(); i++ ){ + animProps.event[i].selfIndex = i; + animProps.event[i].animIndex = eventsArray[i]["anim"].as(); + animProps.event[i].hue = eventsArray[i]["hue"].as(); + animProps.event[i].hueRange = eventsArray[i]["hue-range"].as(); + animProps.event[i].speed = eventsArray[i]["speed"].as(); + animProps.event[i].param1 = eventsArray[i]["param1"].as(); + animProps.event[i].param2 = eventsArray[i]["param2"].as(); + animProps.event[i].check1 = eventsArray[i]["check1"].as(); + animProps.event[i].check2 = eventsArray[i]["check2"].as(); + animProps.event[i].check3 = eventsArray[i]["check3"].as(); + animProps.event[i].check4 = eventsArray[i]["check4"].as(); + animProps.event[i].countDown = 0; + } + + ESP_LOGD(tag, "Extracted/Updated active profile to animProps."); + + // Save to disk/flash + String filePath = "/cfg/anim-profile" + String(profile_index + 1) + ".json"; + if(updateJsonDocument(profJson, filePath.c_str())){ + Buzzer_Beep(150, 3000); + } + + }else{ + ESP_LOGE(tag, "Deserialization error for wifi.json"); + } + + getpostSuccess = true; + } + } + else if(!strcmp(dataType, "play-anim")){ + DynamicJsonDocument animData(1024); + DeserializationError error = deserializeJson(animData, postData, total); + if(!error){ + ANIMATION_EVENT testEvent; + testEvent.selfIndex = animData["index"].as(); + testEvent.animIndex = animData["anim"].as(); + testEvent.hue = animData["hue"].as(); + testEvent.hueRange = animData["hue-range"].as(); + testEvent.speed = animData["speed"].as(); + testEvent.param1 = animData["param1"].as(); + testEvent.param2 = animData["param2"].as(); + testEvent.check1 = animData["check1"].as(); + testEvent.check2 = animData["check2"].as(); + testEvent.check3 = animData["check3"].as(); + testEvent.check4 = animData["check4"].as(); + + if(testEvent.selfIndex != 0){ + testEvent.selfIndex = 1; + } + ESP_LOGI(tag, "Post:play-anim, index: %d, anim: %d, hue: %d, rng: %d, speed: %d, par1: %d, par2: %d, chk1: %d, chk2: %d, chk3: %d, chk4: %d", testEvent.selfIndex, testEvent.animIndex, testEvent.hue, testEvent.hueRange, testEvent.speed, testEvent.param1, testEvent.param2, testEvent.check1, testEvent.check2, testEvent.check3, testEvent.check4); + testEvent.countDown = 0; // set to zero so param1 determines countdown time + testEvent.type = EV_TEST; + PostNewEvent(testEvent); // eventIndex always = 1 + Buzzer_Beep(150, 3000); + }else{ + ESP_LOGE(tag, "Deserialization error for play-anim"); + } + + getpostSuccess = true; + } + else if(!strcmp(dataType, "wifi")){ + JsonDocument wifiCreds; + DeserializationError error = deserializeJson(wifiCreds, postData, total); + if(!error){ + //read credentials + //const char* new_ssid = wifiCreds["ssid"]; + //const char* new_pass = wifiCreds["pass"]; + + String new_ssid = wifiCreds["ssid"]; + String new_pass = wifiCreds["pass"]; + ESP_LOGV(tag, "SSID: %s PASS: %s", new_ssid, new_pass); + // Save Credentials if different + //if(strcmp(new_ssid, ssid)!=0 || strcmp(new_pass, passPhrase)!=0){ + if(new_ssid != ssid || new_pass != passPhrase) + Wifi_Save_Credentials(new_ssid, new_pass); + StartDelayedRebooth; + } + }else{ + ESP_LOGE(tag, "Deserialization error for wifi"); + } + + getpostSuccess = true; + } + else if(!strcmp(dataType, "set-pixel")){ + DynamicJsonDocument js(1024); + DeserializationError error = deserializeJson(js, postData, total); + if(!error){ + ANIMATION_EVENT testEvent; + testEvent.selfIndex = 1; + testEvent.animIndex = 81; + testEvent.hue = js["hue"].as(); + testEvent.param1 = js["index"].as(); + testEvent.type = EV_TEST; + PostNewEvent(testEvent); // eventIndex always = 1 + ESP_LOGD(tag, "Post: set-pixel, hue: %d, pixel: %d", testEvent.hue, testEvent.param1); + Buzzer_Beep(150, 3000); + }else{ + ESP_LOGE(tag, "Deserialization error for set-pixel"); + } + + Buzzer_Beep(50, 3000); + getpostSuccess = true; + } + else if(!strcmp(dataType, "clear-strip")){ + ANIMATION_EVENT testEvent; + testEvent.selfIndex = 1; + testEvent.animIndex = 80; + ESP_LOGD(tag, "Post: clear-strip"); + testEvent.type = EV_TEST; + PostNewEvent(testEvent); // eventIndex always = 1 + + getpostSuccess = true; + Buzzer_Beep(50, 3000); + } + else if(!strcmp(dataType, "setup-save")){ + DynamicJsonDocument js(1024); + DeserializationError error = deserializeJson(js, postData, total); + if(!error){ + // If app index is different open app-events.json and update + if(sysProps.appIndex != js["appindex"].as()){ + File file = LittleFS.open("/cfg/app-events.json", "r+"); + if(!file){ + ESP_LOGE(tag, "Failed to open app-events.json"); + file.close(); + return; + } + + DynamicJsonDocument doc(2048); + DeserializationError error = deserializeJson(doc, file); + file.close(); + + if(!error){ + // Update index value + doc["index"] = js["appindex"].as(); + ESP_LOGD(tag, "New App Index = %d", doc["index"].as()); + + if(updateJsonDocument(doc, "/cfg/app-events.json")){ + Buzzer_Beep(150, 3000); + } + }else{ + ESP_LOGE(tag, "Deserialization error for app-events.json"); + } + } + }else{ + ESP_LOGE(tag, "Deserialization error for seteup-save"); + } + + // if any of the values are diiferent then open led-devices.json and update + //if app index is different open app-events.json and update + bool editJson = false; + //if(js["en1"].as() != strip1->size) { editJson = true; } + if(js["count1"].as() != strip1->size) { editJson = true; } + else if(js["shift1"].as() != strip1->shift) { editJson = true; } + else if(js["offset1"].as() != strip1->offset) { editJson = true; } + else if(js["rgb1"].as() != strip1->ledOrder) { editJson = true; } + else if(js["power1"].as() != strip1->powerDiv) { editJson = true; } + + if(editJson){ + File file = LittleFS.open("/cfg/led-devices.json", "r+"); + if(!file){ + ESP_LOGE(tag, "Failed to open led-devices.json"); + file.close(); + return; + } + + DynamicJsonDocument doc(2048); + DeserializationError error = deserializeJson(doc, file); + file.close(); + + if(!error){ + JsonObject jsStrip1 = doc.createNestedObject("strip1"); // nested so it doesn't create a copy + + jsStrip1["en"] = true; + jsStrip1["size"] = js["count1"].as(); + jsStrip1["shift"] = js["shift1"].as(); + jsStrip1["offset"] = js["offset1"].as(); + jsStrip1["power-div"] = js["power1"].as(); + jsStrip1["rgb-order"] = js["rgb1"]; + + // Save Json File + if(updateJsonDocument(doc, "/cfg/led-devices.json")){ + Buzzer_Beep(150, 3000); + } + }else{ + ESP_LOGE(tag, "Deserialization error for led-devices.json"); + } + } + + ESP_LOGD(tag, "Post: setup-save"); + getpostSuccess = true; + Buzzer_Beep(150, 3000); + } + else if(!strcmp(dataType, "restart")){ + StartDelayedRebooth; + + ESP_LOGD(tag, "Post: restart"); + getpostSuccess = true; + Buzzer_Beep(150, 3000); + } + } + + // Delete the allocated buffer (owned by the request->_tempObject) + delete[] postData; + } +} + +bool isInternetConnected() +{ + if (WiFi.status() == WL_CONNECTED) { // check if WiFi is connected + WiFiClient client; + const int httpPort = 80; + if (client.connect("www.google.com", httpPort)) { // try to connect to Google + client.stop(); + return true; // Internet access available + } + } + return false; // Internet access not available +} + +void handlePOST_Upload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) +{ + if (!index && request->hasParam("dir-path", true, false)) { + AsyncWebParameter* p = request->getParam("dir-path",true, false); + String path = p->value() + "/" + filename; + ESP_LOGD(tag, "upload path: %s", path.c_str()); + + request->_tempFile= LittleFS.open(path, "w"); + //fs::File file = LittleFS.open(path, "w"); + if (!request->_tempFile) { + ESP_LOGE(tag, "Failed to create file!"); + return; + } + }else{ + ESP_LOGE(tag, "dir-path Param not found.."); + } + + if(len && request->_tempFile){ + request->_tempFile.write(data, len); + } + + if(final){ + request->_tempFile.close(); + request->redirect("/files"); + ESP_LOGD(tag, "UploadEnd: %s, %u B", filename.c_str(), index+len); + } +} + +void handleGET_DSLRBooth(AsyncWebServerRequest *request) +{ + static int lastCountdown = 1000; + request->send(200, "text/plain"); // send this right away to avoid comm delays + + if(request->args() >= 1){ + int x = 0; + const char* event_type = request->arg( x ).c_str(); + + if(strcmp("countdown", event_type) == 0){ + int p = request->arg(1).toInt(); + if(p > 0 && p <= 100){ + animStatus.countStatus = p; + } + + // in case "coundown_start" was missed + if(animStatus.EventsIndex != ANIM_WHITEFILL_INDEX){ + animProps.event[0].countDown = lastCountdown; + animProps.event[0].type = EV_NORMAL; + PostNewEvent(animProps.event[0]); + } + } + else if(strcmp("countdown_start", event_type) == 0){ // index=0 + int count = request->arg(1).toInt(); // retrieve countdown value + //Log.traceln(" countdown = %d", count); + if(count < 1){count = 1;} + count *= 1000; + lastCountdown = count - 500; + animProps.event[0].countDown = lastCountdown; + animProps.event[0].type = EV_NORMAL; + PostNewEvent(animProps.event[0]); + } + else if(strcmp("sharing_screen", event_type) == 0){ + animProps.event[2].type = EV_NORMAL; + PostNewEvent(animProps.event[2]); + } + else if(strcmp("session_end", event_type) == 0){ //index=1 + animProps.event[1].type = EV_NORMAL; + PostNewEvent(animProps.event[1]); + } + else{ + // else if(strcmp_P("session_start", event_type) == 0){ + // TODO determine if ramp down is active depending on session type + + /* + const char* param1 = request->arg(1).c_str(); + if(strcmp("PrintOnly", param1)){ + animStatus.Animation = ANIM_START_PRINTONLY; + xTaskNotifyGive( Strip1_Task_Handle ); + //Serial.println("ses_print..\n\r"); + }else if(strcmp("PrintAndGIF", param1)){ + animStatus.Animation = ANIM_START_PRINTGIF; + xTaskNotifyGive( Strip1_Task_Handle ); + //Serial.println("ses_prnt_gif..\n\r"); + }else if(strcmp("OnlyGIF", param1)){ + animStatus.Animation = ANIM_START_GIFONLY; + xTaskNotifyGive( Strip1_Task_Handle ); + //Serial.println("ses_gif..\n\r"); + }else if(strcmp("Boomerang", param1)){ + animStatus.Animation = ANIM_START_BOOM; + xTaskNotifyGive( Strip1_Task_Handle ); + //Serial.println("ses_boom..\n\r"); + }else if(strcmp("Video", param1)){ + animStatus.Animation = ANIM_START_VIDEO; + xTaskNotifyGive( Strip1_Task_Handle ); + //Serial.println("ses_vid..\n\r"); + } + */ + } + } +} + +bool isFirmware = true; +bool updateSuccessful = false; +void handlePOST_Update(AsyncWebServerRequest *request) +{ + bool hasError = Update.hasError(); + String responseContent = hasError ? "failed" : "success"; + String responseType = hasError ? "text/plain" : "text/html"; + int responseCode = hasError ? 500 : 200; + + AsyncWebServerResponse *response = request->beginResponse(responseCode, responseType, responseContent); + response->addHeader("status", responseContent); + request->send(response); +} + +// handlePOST_Update Callback function to install the firmware or file system bin files +void updateCallback(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) +{ + // Do Once Code + if (!index) { + size_t fileSize = UPDATE_SIZE_UNKNOWN; + // Retrieve file-size parameter + if(request->hasParam("file-size", true, false)) { // make sure param is from POST request or it will not find it + fileSize = request->getParam("file-size", true, false)->value().toInt(); + } + + // register progress callback function + Update.onProgress(updateFirmwareProgress); + updateSuccessful = false; + + //file name checks + if (filename.startsWith("ata_fw")) { + isFirmware = true; + if (!Update.begin(fileSize, U_FLASH, BoardLED2)) { + ESP_LOGD(tag, "Firmware update failed to start"); + return; + } + ESP_LOGD(tag, "Updating Firmware with: %s Size:%d ", filename.c_str(), fileSize); + } + else if (filename.startsWith("ata_fs")) { + isFirmware = false; + if (!Update.begin(fileSize, U_SPIFFS, BoardLED2)) { + ESP_LOGD(tag, "LittleFS update failed to start"); + return; + } + ESP_LOGD(tag, "Updating File System with: %s Size:%d ", filename.c_str(), fileSize); + } + else { + ESP_LOGD(tag, "Unsupported file type"); + return; + } + } + + // Writing... + if (!Update.hasError()) { + if (Update.write(data, len) != len) { + Update.printError(Serial); + } + } + + // All Done... + if (final) { + vTaskDelay(100); + if(isFirmware) { // firmware update + if (Update.end(true)) { + RebootSystem = true; + updateSuccessful = true; + ESP_LOGD(tag, "firmware update successful: %d", index + len); + } + else { + Update.printError(Serial); + } + } + else{ // file system update + if(Update.end(true)) { + updateSuccessful = true; + ESP_LOGD(tag, "file system update successful!"); + } + else { + Update.printError(Serial); + } + } + } +} + +// Server requested files that aren't template processed +void handleGET_SendFile(AsyncWebServerRequest *request)// try this later "^/(img|favicon).*" +{ + String filePath = request->url(); + const char* ext = getFileExtension(filePath.c_str()); + const char* contentType = getFileType(ext); + + if( contentType == NULL ){ + request->send(404); + return; + } + + if(filePath.c_str()[0] != '/'){ + filePath = '/' + filePath; + } + + if (!strcmp(ext,"html") || !strcmp(ext, "htm") || !strcmp(ext, "css") || !strcmp(ext, "js")) { + if(!filePath.startsWith("/www/")){ + filePath = "/www" + filePath; + } + } + + ESP_LOGD(tag, "Sent: %s", filePath.c_str()); + request->send(LittleFS, filePath, contentType); +} + +String listDir(String directoryList[], int count) +{ + String listedFiles; + + for (int i = 0; i < count; i++) { + // directory html + listedFiles += "Dir: "; + listedFiles += directoryList[i]; + listedFiles += "/-\n"; + + filesDropdownOptions += "\n"; + + dirDropdownOptions += "\n"; + + File dir = LittleFS.open(directoryList[i]); + File file = dir.openNextFile(); + while (file) { + String fileName = file.name(); + if (!file.isDirectory()) { + //Serial.println(" File: " + String(file.name())); + listedFiles += "  "; + listedFiles += fileName; + listedFiles += ""; + listedFiles += convertFileSize(file.size()); + listedFiles += "\n"; + + filesDropdownOptions += "\n"; + } + file = dir.openNextFile(); + } + dir.close(); + } + + return listedFiles; +} + +char* readFile(fs::FS &fs, const char* path) { + File file = fs.open(path, "r"); + if (!file || file.isDirectory()) { + return nullptr; + } + + size_t fileSize = file.size(); + char* fileContent = new char[fileSize + 1]; // +1 for null-terminator + + size_t bytesRead = file.readBytes(fileContent, fileSize); + file.close(); + + fileContent[bytesRead] = '\0'; // Set null-terminating character + + if (bytesRead != fileSize) { + delete[] fileContent; + return nullptr; + } + + return fileContent; +} + +void writeFile(fs::FS &fs, const char * path, const char * message) +{ + File file = fs.open(path, "w"); + if(!file){ + return; + } + file.print(message); + file.close(); +} + +// Send html file with template processing {{VAR}} +void sendHtmlFile(const char* filePath, AsyncWebServerRequest *request, String (*callback)(const String&)) +{ + ESP_LOGD(tag, "Sent file: %s", filePath); + File file = LittleFS.open(filePath, "r"); + const char* htmlFile = readFile(LittleFS, filePath); + + //String processedData = varReplace(htmlFile, htmlProcessor); + String processedData = varReplace(htmlFile, callback); + request->send(200, "text/html", processedData); +} + +const char* convertFileSize(const size_t bytes) { + static char fileSizeBuffer[16]; // PreAllocated buffer for the file size + + if (bytes < 1024) { + snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%d B", bytes); + } else if (bytes < 1024*1024) { + snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%.2f kB", static_cast(bytes) / 1024.0); + } else if (bytes < 1024*1024*1024) { + snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%.2f MB", static_cast(bytes) / 1048576.0); + } else { + fileSizeBuffer[0] = '\0'; // Empty string if the file size is too large + } + + return fileSizeBuffer; +} + +// Callback template processor +String htmlProcessor(const String& var) +{ + if(var == "NAVBAR"){ return String("\n"); } + + if(var == "ALLOWED_EXTENSIONS_EDIT"){ return allowedExtensionsForEdit; } + + if(var == "FS_FREE_BYTES"){ return convertFileSize(LittleFS.totalBytes() - LittleFS.usedBytes()); } + + if(var == "FS_USED_BYTES"){ return convertFileSize(LittleFS.usedBytes()); } + + if(var == "FS_TOTAL_BYTES"){ return convertFileSize(LittleFS.totalBytes()); } + + if(var == "LISTED_FILES"){ + filesDropdownOptions = ""; // clear out + dirDropdownOptions = ""; // clear out + String directories[8]; + int dirCount = 0; + getAllDirectories(directories, dirCount); + + return listDir(directories, dirCount); + } + + if(var == "EDIT-DEL_FILES"){ return filesDropdownOptions; } + + if(var == "DIR_LIST"){ return dirDropdownOptions; } + + if(var == "SAVE_PATH_INPUT"){ + if(savePath == "/new.txt"){ + return String(""; + } + else{ + return String(""; + } + } + + if(var == "FIRM_VER"){ return FIRMWARE_VER; } + + return var; +} + +String HomeHtmlProcessor(const String& var) { + if(var == "NAVBAR"){ return String("\n"); } + + if (var == "APP_NAME") { + return sysProps.appName; + } + if (var == "OLED") { + return "No"; + } + if (var == "STRIP1") { + return (strip1) ? "Yes" : "No"; + } + if (var == "STRIP2") { + return (strip2) ? "Yes" : "No"; + } + if (var == "FRONT_LIGHT") { + return (animProps.frontLight.enabled) ? "Yes" : "No"; + } + if (var == "REAR_LIGHT") { + return (animProps.rearLight.enabled) ? "Yes" : "No"; + } + if (var == "FIRMWARE") { + return FIRMWARE_VER; + } + if (var == "BOOTH_T") { + return String(sysProps.t_sensor.temperature) + "F"; + } + if (var == "SETPOINT") { + return String(sysProps.t_sensor.Setpoint1) + "F"; + } + if (var == "FLASH_SIZE") { + return convertFileSize(ESP.getSketchSize()); + } + if (var == "FLASH_FREE") { + return convertFileSize(ESP.getFreeSketchSpace()); + } + if (var == "HEAP_SIZE") { + return convertFileSize(ESP.getHeapSize()); + } + if (var == "HEAP_FREE") { + return convertFileSize(ESP.getFreeHeap()); + } + if (var == "CPU_FREQ") { + return String(ESP.getCpuFreqMHz()) + "Mhz"; + } + if (var == "IP") { + return WiFi.localIP().toString(); + } + if (var == "MAC") { + return chipInfo.macStr; + } + if (var == "SSID") { + return WiFi.SSID(); + } + if (var == "RSSI") { + return String(WiFi.RSSI()); + } + if (var == "WIFI_CH") { + return String(WiFi.channel()); + } + if (var == "ENCRYP") { + return String(WiFi.encryptionType(0)); + } + if (var == "AP_SSID") { + return WiFi.softAPSSID(); + } + if (var == "AP_CLIENTS") { + return String(WiFi.softAPgetStationNum()); + } + if (var == "BLE") { + return (commMode == COMM_WIFI_AP_BLE) ? "Yes" : "No"; + } + if (var == "BLE_SSID") { + return (commMode == COMM_WIFI_AP_BLE) ? BLEDeviceName : ""; + } + if (var == "BLE_CLIENTS") { + return (BTDeviceConnected) ? "1" : "0"; + } + if (var == "AP_MAC") { + return getSoftAPMacAddress(); + } + + + // Return an empty string if the variable is not recognized + return var; +} + +String getSoftAPMacAddress() { + uint8_t mac[6]; + WiFi.softAPmacAddress(mac); + + String macString = ""; + for (int i = 0; i < 6; i++) { + macString += String(mac[i], HEX); + if (i < 5) macString += ":"; + } + return macString; +} + +// Finds segments between {{VAR}} and calls a callback function to replace VAR with new content +String varReplace(const String& input, String (*callback)(const String&)) { + if (input.isEmpty()) { + return input; + } + + String result; + int startPos = 0; + int start = input.indexOf("{{", startPos); + + while (start != -1) { + result += input.substring(startPos, start); + + int end = input.indexOf("}}", start + 2); + if (end == -1) { + break; + } + + String segment = input.substring(start + 2, end); + int segmentLength = segment.length(); + if (segmentLength <= 32) { + String replacement = callback(segment); + result += replacement; + } else { + result += input.substring(start, end + 2); // Include the original segment if it exceeds the limit + } + + startPos = end + 2; + start = input.indexOf("{{", startPos); + } + + result += input.substring(startPos); + return result; +} + +const char* getFileExtension(const char* filename) { + size_t dotPos = strlen(filename); + while (dotPos > 0 && filename[dotPos - 1] != '.') { + dotPos--; + } + + if (dotPos != 0 && dotPos < strlen(filename) - 1) { + return &filename[dotPos]; + } + + return ""; // Empty string if no extension found +} + +const char* getFileType(const char* ext) { + if (strcmp(ext, "png") == 0) { + return "image/png"; + } else if (strcmp(ext, "jpg") == 0 || strcmp(ext, "jpeg") == 0) { + return "image/jpeg"; + } else if (strcmp(ext, "gif") == 0) { + return "image/gif"; + } else if (strcmp(ext, "ico") == 0) { + return "image/x-icon"; + } else if (strcmp(ext, "txt") == 0) { + return "text/plain"; + } else if (strcmp(ext, "css") == 0) { + return "text/css"; + } else if (strcmp(ext, "htm") == 0 || strcmp(ext, "html") == 0) { + return "text/html"; + } else if (strcmp(ext, "js") == 0) { + return "text/javascript"; + } else if (strcmp(ext, "json") == 0) { + return "application/json"; + } else { + return ""; + } +} + +// Serial print firmware or file system update progress +void updateFirmwareProgress(size_t progress, size_t total) +{ + static int lastProg = -1; + int firmwareProgress = (progress * 100)/ total; + + if(firmwareProgress != lastProg){ + Serial.printf("Progress: %u%%\r", firmwareProgress); + lastProg = firmwareProgress; + //TODO Add buzzer tune while uploading firmware + //Buzzer_Play_Tune(TUNE_DOWNLOADING,1,true,false); + } +} + +
+ +