From 660fa5f1c75c409b83a73b82a5e86618806cc970 Mon Sep 17 00:00:00 2001 From: admin Date: Sun, 28 Sep 2025 23:18:18 -0700 Subject: [PATCH] 9-29 commit --- .clang-format | 14 + ToDo.txt | 10 - .../booths/{roamer-big.json => big-roam.json} | 12 +- data/booths/custom.json | 26 +- .../{helio-flare.json => flare-posh.json} | 24 +- data/booths/helio-posh.json | 124 -- data/booths/{lumia-m.json => m-lumia.json} | 18 +- data/booths/m1.json | 17 +- data/booths/marquee.json | 18 +- data/booths/{roamer.json => orig-roam.json} | 10 +- .../booths/{helio-sport.json => spectra.json} | 20 +- .../booths/{lumia-spectra.json => sport.json} | 18 +- data/booths/{light-stik.json => stik.json} | 19 +- data/booths/testbooth.json | 35 +- data/booths/{lumia-xl.json => xl-lumia.json} | 20 +- data/system/ble.json | 2 +- data/system/system.json | 2 +- data/www/ata-boothifier-upgrade.html | 112 +- data/www/usercfg.html | 1324 +++++++++++++++ docs/SP110E Info.xlsx | Bin 0 -> 87635 bytes docs/ToDo.txt | 24 + docs/~$SP110E Info.xlsx | Bin 0 -> 165 bytes esp32s3_atabooth_8mb.code-workspace | 3 - include/ATALights.h | 50 +- include/AppUpgrade.h | 6 +- include/BLE_SP110E.h | 5 + include/ColorPalettes.h | 54 +- include/PWM_Output.h | 2 + include/Ramp_Lights.h | 2 +- include/global.h | 2 + include/my_device.h | 4 + include/my_wifi.h | 2 +- include/system.h | 5 + platformio.ini | 6 + src/ATALights.cpp | 582 +++++-- src/Animations.cpp | 467 +++++- src/AppUpgrade.cpp | 456 +++--- src/BLE_SP110E.cpp | 152 +- src/BleServer.cpp | 94 +- src/BleSettings.cpp | 2 - src/PWM_Output.cpp | 36 +- src/Ramp_Lights.cpp | 25 +- src/common/fileSystem.cpp | 16 +- src/common/fileSystem.h | 2 +- src/global.cpp | 7 +- src/main.cpp | 44 +- src/my_board.cpp | 211 +-- src/my_buttons.cpp | 5 +- src/my_buzzer.cpp | 4 +- src/my_device.cpp | 307 ++-- src/my_oled.cpp | 121 +- src/my_tsensor.cpp | 10 +- src/my_wifi.cpp | 1315 ++++++++------- temporary/AppUpgrade copy.cpp | 0 temporary/AppUpgrade orig.cpp | 673 -------- temporary/AppUpgrade_orig.cpp | 716 -------- temporary/Temp/ATALights2.cpp | 178 -- temporary/Temp/BLE-FlashStick-Service.h | 4 - temporary/Temp/BLE-FlaskStick-Service.cpp | 157 -- temporary/Temp/BTSerial.cpp | 240 --- temporary/Temp/BTSerial.h | 16 - temporary/Temp/EventBoxTest.html | 82 - temporary/Temp/GenUpdate.py | 164 -- temporary/Temp/MyNeoPixelBus.cpp | 47 - temporary/Temp/UploadToMinio.py | 337 ---- temporary/Temp/appcontrol old.html | 623 ------- .../Temp/ata-boothifier-upgrade(copy).html | 0 temporary/Temp/ata-boothifier-upgrade.html | 508 ------ temporary/Temp/ata-boothifier-upgradeV2.html | 547 ------- temporary/Temp/bleconfig.html | 16 - temporary/Temp/colorPicket.html | 31 - temporary/Temp/command_processor.cpp | 71 - temporary/Temp/command_processor.h | 31 - temporary/Temp/eventTest.htm | 145 -- temporary/Temp/firmware_html.h | 215 --- temporary/Temp/githubCert.h | 30 - temporary/Temp/gridstyles.css | 6 - temporary/Temp/gridtest.html | 12 - temporary/Temp/hue-select-min.js | 1 - 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/lights-old.html | 755 --------- temporary/Temp/log.html | 10 - 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/neo_colors.h | 78 - temporary/Temp/otaupdate.cpp | 96 -- temporary/Temp/otaupdate.h | 14 - temporary/Temp/rf_transceiver.cpp | 44 - temporary/Temp/rf_transceiver.h | 23 - temporary/Temp/styles.css | 50 - temporary/Temp/update.json | 190 --- temporary/Temp/upgrade.html | 228 --- temporary/Temp/web_ble.html | 113 -- temporary/ata-boothifier-upgradeV3_old.html | 533 ------ 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/my_buzzer.cpp | 249 --- test/edit.html | 265 +++ test/files.html | 350 ++++ webSock/AppUpgrade.cpp | 522 ------ webSock/AppUpgrade.h | 156 -- webSock/my_wifi.cpp | 1164 ------------- webSock/my_wifi.h | 38 - webSock/upgrade.html | 193 --- 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 | 1450 ---------------- 171 files changed, 4689 insertions(+), 25496 deletions(-) create mode 100644 .clang-format delete mode 100644 ToDo.txt rename data/booths/{roamer-big.json => big-roam.json} (93%) rename data/booths/{helio-flare.json => flare-posh.json} (80%) delete mode 100644 data/booths/helio-posh.json rename data/booths/{lumia-m.json => m-lumia.json} (83%) rename data/booths/{roamer.json => orig-roam.json} (93%) rename data/booths/{helio-sport.json => spectra.json} (82%) rename data/booths/{lumia-spectra.json => sport.json} (83%) rename data/booths/{light-stik.json => stik.json} (86%) rename data/booths/{lumia-xl.json => xl-lumia.json} (85%) create mode 100644 data/www/usercfg.html create mode 100644 docs/SP110E Info.xlsx create mode 100644 docs/ToDo.txt create mode 100644 docs/~$SP110E Info.xlsx delete mode 100644 temporary/AppUpgrade copy.cpp delete mode 100644 temporary/AppUpgrade orig.cpp delete mode 100644 temporary/AppUpgrade_orig.cpp delete mode 100644 temporary/Temp/ATALights2.cpp delete mode 100644 temporary/Temp/BLE-FlashStick-Service.h delete mode 100644 temporary/Temp/BLE-FlaskStick-Service.cpp delete mode 100644 temporary/Temp/BTSerial.cpp delete mode 100644 temporary/Temp/BTSerial.h delete mode 100644 temporary/Temp/EventBoxTest.html delete mode 100644 temporary/Temp/GenUpdate.py delete mode 100644 temporary/Temp/MyNeoPixelBus.cpp delete mode 100644 temporary/Temp/UploadToMinio.py delete mode 100644 temporary/Temp/appcontrol old.html delete mode 100644 temporary/Temp/ata-boothifier-upgrade(copy).html delete mode 100644 temporary/Temp/ata-boothifier-upgrade.html delete mode 100644 temporary/Temp/ata-boothifier-upgradeV2.html delete mode 100644 temporary/Temp/bleconfig.html delete mode 100644 temporary/Temp/colorPicket.html delete mode 100644 temporary/Temp/command_processor.cpp delete mode 100644 temporary/Temp/command_processor.h delete mode 100644 temporary/Temp/eventTest.htm delete mode 100644 temporary/Temp/firmware_html.h delete mode 100644 temporary/Temp/githubCert.h delete mode 100644 temporary/Temp/gridstyles.css delete mode 100644 temporary/Temp/gridtest.html delete mode 100644 temporary/Temp/hue-select-min.js delete mode 100644 temporary/Temp/led_animation.cpp delete mode 100644 temporary/Temp/led_animation.h delete mode 100644 temporary/Temp/led_strip.cpp delete mode 100644 temporary/Temp/led_strip.h delete mode 100644 temporary/Temp/lights-old.html delete mode 100644 temporary/Temp/log.html delete mode 100644 temporary/Temp/luma_master.cpp delete mode 100644 temporary/Temp/luma_master.h delete mode 100644 temporary/Temp/my_wifi.cpp delete mode 100644 temporary/Temp/my_wifi.h delete mode 100644 temporary/Temp/neo_colors.h delete mode 100644 temporary/Temp/otaupdate.cpp delete mode 100644 temporary/Temp/otaupdate.h delete mode 100644 temporary/Temp/rf_transceiver.cpp delete mode 100644 temporary/Temp/rf_transceiver.h delete mode 100644 temporary/Temp/styles.css delete mode 100644 temporary/Temp/update.json delete mode 100644 temporary/Temp/upgrade.html delete mode 100644 temporary/Temp/web_ble.html delete mode 100644 temporary/ata-boothifier-upgradeV3_old.html delete mode 100644 temporary/bak/cfg/!instructions.txt delete mode 100644 temporary/bak/cfg/anim-list.json delete mode 100644 temporary/bak/cfg/anim-profiles.json delete mode 100644 temporary/bak/cfg/anim-settings.json delete mode 100644 temporary/bak/cfg/app-events.json delete mode 100644 temporary/bak/cfg/ble.json delete mode 100644 temporary/bak/cfg/buzzer.json delete mode 100644 temporary/bak/cfg/firmware.json delete mode 100644 temporary/bak/cfg/led-devices.json delete mode 100644 temporary/bak/cfg/luma-stiks.json delete mode 100644 temporary/bak/cfg/relays.json delete mode 100644 temporary/bak/cfg/system.json delete mode 100644 temporary/bak/cfg/touch-pins.json delete mode 100644 temporary/bak/cfg/trx433.json delete mode 100644 temporary/bak/cfg/wifi.json delete mode 100644 temporary/bak/www/edit.html delete mode 100644 temporary/bak/www/edit2.html delete mode 100644 temporary/bak/www/event-box.js delete mode 100644 temporary/bak/www/failed.html delete mode 100644 temporary/bak/www/files.html delete mode 100644 temporary/bak/www/global-style.css delete mode 100644 temporary/bak/www/home.html delete mode 100644 temporary/bak/www/hue-select.js delete mode 100644 temporary/bak/www/label.html delete mode 100644 temporary/bak/www/lights.html delete mode 100644 temporary/bak/www/navbar.html delete mode 100644 temporary/bak/www/ok.html delete mode 100644 temporary/bak/www/setup.html delete mode 100644 temporary/bak/www/wifi.html delete mode 100644 temporary/my_buzzer.cpp create mode 100644 test/edit.html create mode 100644 test/files.html delete mode 100644 webSock/AppUpgrade.cpp delete mode 100644 webSock/AppUpgrade.h delete mode 100644 webSock/my_wifi.cpp delete mode 100644 webSock/my_wifi.h delete mode 100644 webSock/upgrade.html delete mode 100644 z_old/HSVTable.cpp delete mode 100644 z_old/HSVTable.h delete mode 100644 z_old/LEDStrip.cpp delete mode 100644 z_old/LEDStrip.h delete mode 100644 z_old/color_tools.cpp delete mode 100644 z_old/color_tools.h delete mode 100644 z_old/fileSystem.cpp delete mode 100644 z_old/fileSystem.h delete mode 100644 z_old/my_board.cpp delete mode 100644 z_old/my_board.h delete mode 100644 z_old/my_buttons.cpp delete mode 100644 z_old/my_buttons.h delete mode 100644 z_old/my_buzzer.cpp delete mode 100644 z_old/neo_colors.h delete mode 100644 z_old/old/anim-list.json delete mode 100644 z_old/old/anim-profile-common.json delete mode 100644 z_old/old/anim-profile1.json delete mode 100644 z_old/old/anim-profile2.json delete mode 100644 z_old/old/anim-profile3.json delete mode 100644 z_old/old/anim-profile4.json delete mode 100644 z_old/old/anim-profile5.json delete mode 100644 z_old/old/anim-profile6.json delete mode 100644 z_old/old/anim-profile7.json delete mode 100644 z_old/old/anim-profile8.json delete mode 100644 z_old/old/anim-profiles copy.json delete mode 100644 z_old/old/anim-settings.json delete mode 100644 z_old/old/app-events.json delete mode 100644 z_old/old/ble.json delete mode 100644 z_old/old/board.json delete mode 100644 z_old/old/led-devices.json delete mode 100644 z_old/old/ramp-lights.json delete mode 100644 z_old/old/relays.json delete mode 100644 z_old/old/system.json delete mode 100644 z_old/old/touch-pins.json delete mode 100644 z_old/wifi_temp.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..5cbe254 --- /dev/null +++ b/.clang-format @@ -0,0 +1,14 @@ +BasedOnStyle: LLVM +IndentWidth: 4 +ColumnLimit: 100 +BraceWrapping: + AfterFunction: false + AfterControlStatement: false + AfterClass: false + AfterEnum: false + AfterStruct: false + AfterNamespace: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false diff --git a/ToDo.txt b/ToDo.txt deleted file mode 100644 index ac29ba7..0000000 --- a/ToDo.txt +++ /dev/null @@ -1,10 +0,0 @@ - -1) OTA switch to Minio - -2) Global variable ( Circular or Linear LEDS) - -3) Long Hold to switch - -4) Fix white flasing at booth - -5) \ No newline at end of file diff --git a/data/booths/roamer-big.json b/data/booths/big-roam.json similarity index 93% rename from data/booths/roamer-big.json rename to data/booths/big-roam.json index 1a15f18..4041ad6 100644 --- a/data/booths/roamer-big.json +++ b/data/booths/big-roam.json @@ -1,5 +1,7 @@ { + "profile": "big-roam", "mode": "roamer", + "limited-mode": false, "button": 0, "buttons": [ @@ -58,7 +60,7 @@ "en": true, "relay-index": 0, "button-index": 0, - "min": 0.0, + "min": 1.0, "max": 100.0, "step": 1.5, "rate": 30.0, @@ -66,10 +68,10 @@ "vision": true }, { - "en": false, + "en": true, "relay-index": 1, "button-index": 1, - "min": 0.0, + "min": 1.0, "max": 100.0, "step": 1.5, "rate": 30.0, @@ -106,9 +108,9 @@ "size": 168, "chip": "SK6812", "rgb-order": "rgb", - "shift":-5, + "shift":-42, "offset": 0, - "bright": 200, + "bright": 240, "power-div": 0, "i2s-ch": 0, "core": 1 diff --git a/data/booths/custom.json b/data/booths/custom.json index 9f7eb89..582a898 100644 --- a/data/booths/custom.json +++ b/data/booths/custom.json @@ -1,5 +1,7 @@ { + "id": "custom", "mode": 0, + "limited-mode": false, "buttons": [ { @@ -54,14 +56,26 @@ "ramp-lights": [ { - "en": true, - "relay-index": 0, - "button-index": 0 + "en": true, + "relay-index": 0, + "button-index": 0, + "min": 1.0, + "max": 100.0, + "step": 1.5, + "rate": 30.0, + "skip-count": 5, + "vision": true }, { - "en": true, - "relay-index": 1, - "button-index": 1 + "en": true, + "relay-index": 1, + "button-index": 1, + "min": 1.0, + "max": 100.0, + "step": 1.5, + "rate": 30.0, + "skip-count": 5, + "vision": true } ], "oled": { diff --git a/data/booths/helio-flare.json b/data/booths/flare-posh.json similarity index 80% rename from data/booths/helio-flare.json rename to data/booths/flare-posh.json index 50419d5..30e35bf 100644 --- a/data/booths/helio-flare.json +++ b/data/booths/flare-posh.json @@ -1,6 +1,8 @@ { - "mode": 0, - "buttons": + "id": "flare-posh", + "mode": 0, + "limited-mode": false, + "buttons": [ { "en": true @@ -56,12 +58,24 @@ { "en": true, "relay-index": 0, - "button-index": 0 + "button-index": 0, + "min": 1.0, + "max": 100.0, + "step": 1.5, + "rate": 30.0, + "skip-count": 5, + "vision": true }, { - "en": false, + "en": true, "relay-index": 1, - "button-index": 1 + "button-index": 1, + "min": 1.0, + "max": 100.0, + "step": 1.5, + "rate": 30.0, + "skip-count": 5, + "vision": true } ], "oled": { diff --git a/data/booths/helio-posh.json b/data/booths/helio-posh.json deleted file mode 100644 index d43fabd..0000000 --- a/data/booths/helio-posh.json +++ /dev/null @@ -1,124 +0,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 - }, - { - "en": false, - "relay-index": 1, - "button-index": 1 - } - ], - "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-m.json b/data/booths/m-lumia.json similarity index 83% rename from data/booths/lumia-m.json rename to data/booths/m-lumia.json index d576c73..ff73390 100644 --- a/data/booths/lumia-m.json +++ b/data/booths/m-lumia.json @@ -1,4 +1,6 @@ { + "id": "m-lumia", + "limited-mode": false, "mode": 0, "buttons": [ @@ -56,12 +58,24 @@ { "en": true, "relay-index": 0, - "button-index": 0 + "button-index": 0, + "min": 1.0, + "max": 100.0, + "step": 1.5, + "rate": 30.0, + "skip-count": 5, + "vision": true }, { "en": true, "relay-index": 1, - "button-index": 1 + "button-index": 1, + "min": 1.0, + "max": 100.0, + "step": 1.5, + "rate": 30.0, + "skip-count": 5, + "vision": true } ], "oled": { diff --git a/data/booths/m1.json b/data/booths/m1.json index b4fae80..5e8798d 100644 --- a/data/booths/m1.json +++ b/data/booths/m1.json @@ -1,4 +1,5 @@ { + "id": "m1", "mode": 0, "buttons": [ @@ -56,12 +57,24 @@ { "en": true, "relay-index": 0, - "button-index": 0 + "button-index": 0, + "min": 1.0, + "max": 100.0, + "step": 1.5, + "rate": 30.0, + "skip-count": 5, + "vision": true }, { "en": true, "relay-index": 1, - "button-index": 1 + "button-index": 1, + "min": 1.0, + "max": 100.0, + "step": 1.5, + "rate": 30.0, + "skip-count": 5, + "vision": true } ], "oled": { diff --git a/data/booths/marquee.json b/data/booths/marquee.json index b4fae80..dae0d81 100644 --- a/data/booths/marquee.json +++ b/data/booths/marquee.json @@ -1,5 +1,7 @@ { + "id": "marquee", "mode": 0, + "limited-mode": false, "buttons": [ { @@ -56,12 +58,24 @@ { "en": true, "relay-index": 0, - "button-index": 0 + "button-index": 0, + "min": 1.0, + "max": 100.0, + "step": 1.5, + "rate": 30.0, + "skip-count": 5, + "vision": true }, { "en": true, "relay-index": 1, - "button-index": 1 + "button-index": 1, + "min": 1.0, + "max": 100.0, + "step": 1.5, + "rate": 30.0, + "skip-count": 5, + "vision": true } ], "oled": { diff --git a/data/booths/roamer.json b/data/booths/orig-roam.json similarity index 93% rename from data/booths/roamer.json rename to data/booths/orig-roam.json index 64faa59..f79dab6 100644 --- a/data/booths/roamer.json +++ b/data/booths/orig-roam.json @@ -1,5 +1,7 @@ { + "id": "orig-roam", "mode": "roamer", + "limited-mode": false, "button": 0, "buttons": [ @@ -58,19 +60,21 @@ "en": true, "relay-index": 0, "button-index": 0, - "min": 0.0, + "min": 1.0, "max": 100.0, "step": 1.5, + "rate": 30.0, "skip-count": 5, "vision": true }, { - "en": false, + "en": true, "relay-index": 1, "button-index": 1, - "min": 0.0, + "min": 1.0, "max": 100.0, "step": 1.5, + "rate": 30.0, "skip-count": 5, "vision": true } diff --git a/data/booths/helio-sport.json b/data/booths/spectra.json similarity index 82% rename from data/booths/helio-sport.json rename to data/booths/spectra.json index d04844e..b0f38cb 100644 --- a/data/booths/helio-sport.json +++ b/data/booths/spectra.json @@ -1,5 +1,7 @@ { + "id": "spectra", "mode": 0, + "limited-mode": false, "buttons": [ { @@ -56,12 +58,24 @@ { "en": true, "relay-index": 0, - "button-index": 0 + "button-index": 0, + "min": 1.0, + "max": 100.0, + "step": 1.5, + "rate": 30.0, + "skip-count": 5, + "vision": true }, { - "en": false, + "en": true, "relay-index": 1, - "button-index": 1 + "button-index": 1, + "min": 1.0, + "max": 100.0, + "step": 1.5, + "rate": 30.0, + "skip-count": 5, + "vision": true } ], "oled": { diff --git a/data/booths/lumia-spectra.json b/data/booths/sport.json similarity index 83% rename from data/booths/lumia-spectra.json rename to data/booths/sport.json index b4fae80..f4408ad 100644 --- a/data/booths/lumia-spectra.json +++ b/data/booths/sport.json @@ -1,5 +1,7 @@ { + "id": "sport", "mode": 0, + "limited-mode": false, "buttons": [ { @@ -56,12 +58,24 @@ { "en": true, "relay-index": 0, - "button-index": 0 + "button-index": 0, + "min": 1.0, + "max": 100.0, + "step": 1.5, + "rate": 30.0, + "skip-count": 5, + "vision": true }, { "en": true, "relay-index": 1, - "button-index": 1 + "button-index": 1, + "min": 1.0, + "max": 100.0, + "step": 1.5, + "rate": 30.0, + "skip-count": 5, + "vision": true } ], "oled": { diff --git a/data/booths/light-stik.json b/data/booths/stik.json similarity index 86% rename from data/booths/light-stik.json rename to data/booths/stik.json index 21c6e2e..5e3390c 100644 --- a/data/booths/light-stik.json +++ b/data/booths/stik.json @@ -1,4 +1,5 @@ { + "profile": "stik", "mode": "stik", "buttons": [ @@ -56,13 +57,25 @@ { "en": true, "relay-index": 0, - "button-index": 0 + "button-index": 0, + "min": 1.0, + "max": 100.0, + "step": 1.5, + "rate": 30.0, + "skip-count": 5, + "vision": true }, { "en": true, "relay-index": 1, - "button-index": 1 - } + "button-index": 1, + "min": 1.0, + "max": 100.0, + "step": 1.5, + "rate": 30.0, + "skip-count": 5, + "vision": true + } ], "oled": { "en": false, diff --git a/data/booths/testbooth.json b/data/booths/testbooth.json index 9f33075..21a19df 100644 --- a/data/booths/testbooth.json +++ b/data/booths/testbooth.json @@ -1,4 +1,5 @@ { + "profile": "testbooth", "mode": 0, "buttons": [ @@ -54,24 +55,26 @@ "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": true, + "relay-index": 0, + "button-index": 0, + "min": 1.0, + "max": 100.0, + "step": 1.5, + "rate": 30.0, + "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 + "en": true, + "relay-index": 1, + "button-index": 1, + "min": 1.0, + "max": 100.0, + "step": 1.5, + "rate": 30.0, + "skip-count": 5, + "vision": true } ], "oled": { diff --git a/data/booths/lumia-xl.json b/data/booths/xl-lumia.json similarity index 85% rename from data/booths/lumia-xl.json rename to data/booths/xl-lumia.json index 76d799f..88ae171 100644 --- a/data/booths/lumia-xl.json +++ b/data/booths/xl-lumia.json @@ -1,5 +1,7 @@ { + "id": "xl-lumia", "mode": 0, + "limited-mode": false, "buttons": [ { @@ -56,12 +58,24 @@ { "en": true, "relay-index": 0, - "button-index": 0 + "button-index": 0, + "min": 1.0, + "max": 100.0, + "step": 1.5, + "rate": 30.0, + "skip-count": 5, + "vision": true }, { - "en": true, + "en": false, "relay-index": 1, - "button-index": 1 + "button-index": 1, + "min": 1.0, + "max": 100.0, + "step": 1.5, + "rate": 30.0, + "skip-count": 5, + "vision": true } ], "oled": { diff --git a/data/system/ble.json b/data/system/ble.json index 82da5c8..c43135f 100644 --- a/data/system/ble.json +++ b/data/system/ble.json @@ -1,5 +1,5 @@ { - "name": "ATALIGHTS", + "name": "SP110E-ATA", "unique": true, "lights-service": "FFE0", "lights-char": "FFE1", diff --git a/data/system/system.json b/data/system/system.json index 83bdb32..c01e52c 100644 --- a/data/system/system.json +++ b/data/system/system.json @@ -1,4 +1,4 @@ { "boardfile": "/boards/board15.json", - "configfile": "/booths/roamer-big.json" + "configfile": "/booths/big-roam.json" } \ No newline at end of file diff --git a/data/www/ata-boothifier-upgrade.html b/data/www/ata-boothifier-upgrade.html index 53868db..64c5009 100644 --- a/data/www/ata-boothifier-upgrade.html +++ b/data/www/ata-boothifier-upgrade.html @@ -589,67 +589,67 @@ IMPORTANT NOTES: try{ bleDevice = await navigator.bluetooth.requestDevice({ - filters:[{ namePrefix: el.inDeviceName.value || BLE_SERVER_NAME }], - optionalServices:[BLE_SERVICE_UUID] - }); + filters:[{ namePrefix: el.inDeviceName.value || BLE_SERVER_NAME }], + optionalServices:[BLE_SERVICE_UUID] + }); - const server = await bleDevice.gatt.connect(); - const service = await server.getPrimaryService(BLE_SERVICE_UUID); - bleCharacteristic1 = await service.getCharacteristic(BLE_CHARACTERISTIC1_UUID); - bleCharacteristic2 = await service.getCharacteristic(BLE_CHARACTERISTIC2_UUID); - await bleCharacteristic2.startNotifications(); - let prevProgressFound = false; - bleCharacteristic2.addEventListener('characteristicvaluechanged', e => { - try{ - //const view = e.target.value; // DataView - //const bytes = new Uint8Array(view.buffer, view.byteOffset, view.byteLength); - - // Debug info - //let debugInfo = `Received ${bytes.length} bytes: `; - //for (let i = 0; i < bytes.length; i++) { - // debugInfo += bytes[i].toString(16).padStart(2, '0') + ' '; - // } - //console.log(debugInfo); - - // Try to decode as text - let txt = ''; - try { - //txt = new TextDecoder().decode(bytes); - txt = new TextDecoder().decode(e.target.value); - // Remove null terminators and trim - const nullIdx = txt.indexOf('\0'); - if (nullIdx !== -1) txt = txt.slice(0, nullIdx); - txt = txt.trim(); - } catch (decodeErr) { - console.error('Text decode error:', decodeErr); - // Fallback to showing hex if text decode fails - txt = '[Binary data]'; - } - - // Show both raw and processed data in console - //console.log('--> Raw bytes:', bytes); - //console.log('--> As text:', txt); - - //logMessage(`--> (${bytes.length} bytes) ${txt}`); - // progress messages handling variable captured from outer scope + const server = await bleDevice.gatt.connect(); + const service = await server.getPrimaryService(BLE_SERVICE_UUID); + bleCharacteristic1 = await service.getCharacteristic(BLE_CHARACTERISTIC1_UUID); + bleCharacteristic2 = await service.getCharacteristic(BLE_CHARACTERISTIC2_UUID); + await bleCharacteristic2.startNotifications(); + let prevProgressFound = false; + bleCharacteristic2.addEventListener('characteristicvaluechanged', e => { + try{ + //const view = e.target.value; // DataView + //const bytes = new Uint8Array(view.buffer, view.byteOffset, view.byteLength); + + // Debug info + //let debugInfo = `Received ${bytes.length} bytes: `; + //for (let i = 0; i < bytes.length; i++) { + // debugInfo += bytes[i].toString(16).padStart(2, '0') + ' '; + // } + //console.log(debugInfo); + + // Try to decode as text + let txt = ''; + try { + //txt = new TextDecoder().decode(bytes); + txt = new TextDecoder().decode(e.target.value); + // Remove null terminators and trim + const nullIdx = txt.indexOf('\0'); + if (nullIdx !== -1) txt = txt.slice(0, nullIdx); + txt = txt.trim(); + } catch (decodeErr) { + console.error('Text decode error:', decodeErr); + // Fallback to showing hex if text decode fails + txt = '[Binary data]'; + } + + // Show both raw and processed data in console + //console.log('--> Raw bytes:', bytes); + //console.log('--> As text:', txt); + + //logMessage(`--> (${bytes.length} bytes) ${txt}`); + // progress messages handling variable captured from outer scope - if(prevProgressFound && txt.includes('progress')){ - logMessage(`${txt}`, true); // overwrite last line for progress updates - } - else{ - logMessage(`${txt}`); - prevProgressFound = false; // reset if current message isn't progress - } + if(prevProgressFound && txt.includes('progress')){ + logMessage(`${txt}`, true); // overwrite last line for progress updates + } + else{ + logMessage(`${txt}`); + prevProgressFound = false; // reset if current message isn't progress + } - if(txt.includes('progress')){ - prevProgressFound = true; - } + if(txt.includes('progress')){ + prevProgressFound = true; + } - } catch(err) { - console.error('Processing error', err); - logMessage('--> Error processing message: ' + err.message); - } + } catch(err) { + console.error('Processing error', err); + logMessage('--> Error processing message: ' + err.message); + } }); bleConnected=true; diff --git a/data/www/usercfg.html b/data/www/usercfg.html new file mode 100644 index 0000000..39598df --- /dev/null +++ b/data/www/usercfg.html @@ -0,0 +1,1324 @@ + + + + + + + ATA Lights Config + + + + + +
+
+ 🌡️ Temp: --.- °F +
+

ATA Lights Config

+
+ ⚡ V-IN: --.- V +
+
+ +
+
+
+
BLE Comm: Disconnected
+
+ +
+ + + + + +
+ + + +
+ + +
+
+ Communication + +
+ + +
+ +
+
+
+
+ + +
+
+ +
+ Core Settings + +
+
Limited Mode
+
+ + +
+
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
px
+
+ +
+ + +
+
+
+ + +
+ Fan / Temperature + +
+ + +
°C
+
+ +
+ + +
°C
+
+
+ + +
+ Front White Light Range + +
+ + +
%
+
+ +
+ + +
%
+
+
+ + +
+ Rear White Light Range + +
+ + +
%
+
+ +
+ + +
%
+
+
+
+ + +
+
+ +
+
+ + +
+
+ + + +
+ + +
+
+ Tools + +
+ + +
+ +
+
+ +
+ + +
+ +
+
+ +
+ + +
+ +
+
+ +
+ + +
+ +
+
+ +
+ + +
+ +
+
+ +
+ +
+
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/docs/SP110E Info.xlsx b/docs/SP110E Info.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..c00563fdd5dbec82128100ebc2a009e19d1f7aa8 GIT binary patch literal 87635 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 literal 0 HcmV?d00001 diff --git a/docs/ToDo.txt b/docs/ToDo.txt new file mode 100644 index 0000000..50f6811 --- /dev/null +++ b/docs/ToDo.txt @@ -0,0 +1,24 @@ + +Board to do: +2) make sure no fixed indexes to pwmOutput and other peripherals + +3) Implement sending packet with current values to ble appearance + +4) figure out how to compare profile comparison before savoing to system.json + +5) made mode string instead of number. + +6) Comments on json file Enable? + #define ARDUINOJSON_ENABLE_COMMENTS 1 + #include + + permits: // or /* lsslslslslsl */ + + +7) Use same struct of setting for to and from in ble + +8) learn about Blob in BLE + +######### Configuration Page ############ +done + \ No newline at end of file diff --git a/docs/~$SP110E Info.xlsx b/docs/~$SP110E Info.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..8b381dfba25e5d1fc587a989ad1d415e6880f1f6 GIT binary patch literal 165 ycmd;eNi54uQ3yy($;`<~R3H}cGPp1#GL$i7GNdplFa!YEDL{4(5GDfo)P?{!JQLXf literal 0 HcmV?d00001 diff --git a/esp32s3_atabooth_8mb.code-workspace b/esp32s3_atabooth_8mb.code-workspace index 2ce8fdd..c92c6d8 100644 --- a/esp32s3_atabooth_8mb.code-workspace +++ b/esp32s3_atabooth_8mb.code-workspace @@ -2,9 +2,6 @@ "folders": [ { "path": "." - }, - { - "path": "../esp32s3_module_8mb" } ], "settings": { diff --git a/include/ATALights.h b/include/ATALights.h index 4545449..ef1cc89 100644 --- a/include/ATALights.h +++ b/include/ATALights.h @@ -5,13 +5,21 @@ #include "ColorPalettes.h" #include "PWM_Output.h" -#define PIXEL_INDEX -3 +#define SHIFT_INDEX -3 #define SOLID_COLOR_INDEX -2 #define OFF_INDEX -1 #define WITH_GAPS true #define NO_GAPS false +enum CHIP_TYPE { + CHIP_WS2812B = 0, + CHIP_SK6812, + CHIP_WS2811, + CHIP_WS2815, + CHIP_UNKNOWN +}; + extern uint32_t whiteTimeout; typedef struct { @@ -39,19 +47,53 @@ typedef struct { int shift; int offset; int powerDiv; - int effSize; + //int effSize; uint8_t bright; uint8_t i2sCh; uint8_t core; uint8_t pin; }LEDSTRIP_SETTINGS; +typedef struct __attribute__((packed)) { + uint8_t cmd; + uint8_t limitedMode; + float temperature; + float vIn; + char profile[10]; + + uint8_t ledChipset1; + char rgbOrder1[4]; // 3 chars + null terminator + uint8_t ledCount1; + uint8_t ledShift1; + uint8_t ledBrightness1; + + uint8_t ledChipset2; + char rgbOrder2[4]; // 3 chars + null terminator + uint8_t ledCount2; + uint8_t ledShift2; + uint8_t ledBrightness2; + + uint8_t frontLightMin; + uint8_t frontLightMax; + uint8_t rearLightMin; + uint8_t rearLightMax; + uint8_t fanLowerTemp; + uint8_t fanUpperTemp; +} USER_SETTINGS; + +enum BOOTH_PROFILE { + PROFILE_CUSTOM=0, + PROFILE_ROAMER, + PROFILE_STIK, + PROFILE_COUNT +}; + extern LEDSTRIP_SETTINGS ledSettings[2]; void RGB_Lights_Control_Task(void *parameters); -void Init_RGB_Lights_Task(void); +void Init_RGB_Lights_Task(bool upgrade); void Init_RGB_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder, const String& chipType, uint8_t bright); @@ -72,6 +114,8 @@ void createFirePalette(CRGBPalette16& palette, const COLOR_PACK& colorPack); void loadColorPack(COLOR_PACK& dest, const COLOR_PACK& src); +void SetAndSaveUserSettings(USER_SETTINGS &userSettings); + //void Init_Ramp_Front_Light_Task(void); diff --git a/include/AppUpgrade.h b/include/AppUpgrade.h index a4696f7..e478038 100644 --- a/include/AppUpgrade.h +++ b/include/AppUpgrade.h @@ -8,6 +8,7 @@ #include #include "AppVersion.h" + //#define DEFAULT_MANIFEST_URL "https://storage.googleapis.com/boothifier/latest/" #define DEFAULT_MANIFEST_URL "https://minio.boothwizard.com/boothifier/latest/" #define BUFFER_SIZE 2048 // Reduced from 4096 to use less memory @@ -171,6 +172,7 @@ class AppUpdater { fs::FS& fileSystem; UpdateStatus status; std::unique_ptr downloadBuffer; + size_t downloadBufferSize = 0; /**< Actual size allocated for downloadBuffer */ bool updateAvailable = false; UpdateMode updateMode = UpdateMode::UPDATE_BOTH; // Default to updating both files and firmware @@ -223,7 +225,7 @@ void firmwareUpdateTask(void* param); void startFirmwareUpdateTask(AsyncEventSource* eventSrc); -void loadUpdateJson(void); +bool loadUpdateJson(void); void updateProgress(AppUpdater::UpdateStatus status, int percentage, const char* message); @@ -243,3 +245,5 @@ void setUpdateModeFilesOnly(); void setUpdateModeFirmwareOnly(); void setUpdateModeBoth(); + + diff --git a/include/BLE_SP110E.h b/include/BLE_SP110E.h index db2a470..28e5983 100644 --- a/include/BLE_SP110E.h +++ b/include/BLE_SP110E.h @@ -19,6 +19,11 @@ #define SET_SPEED 0x03 #define SET_AUTO_MODE 0x06 +// My custom commands +#define SET_USER_SETTINGS 0xB0 +#define SET_SHIFT 0xB1 + + // Initializes the BLE server, services, and advertising void Init_BLE_SP110E(NimBLEServer* pServer); diff --git a/include/ColorPalettes.h b/include/ColorPalettes.h index 2df61e1..d8b06bc 100644 --- a/include/ColorPalettes.h +++ b/include/ColorPalettes.h @@ -28,9 +28,9 @@ const COLOR_PACK colorPack_RED_WHITE PROGMEM = { 2, { CRGB::Red, CRGB::White } } const COLOR_PACK colorPack_ORANGE_WHITE PROGMEM = { 2, { CRGB::DarkOrange, CRGB::White } }; const COLOR_PACK colorPack_GREEN_WHITE PROGMEM = { 2, { CRGB::Green, CRGB::White } }; const COLOR_PACK colorPack_BLUE_WHITE PROGMEM = { 2, { CRGB::Blue, CRGB::White } }; -const COLOR_PACK colorPack_PINK_WHITE PROGMEM = { 2, { CRGB::Pink, CRGB::White } }; +const COLOR_PACK colorPack_MAGENTA_WHITE PROGMEM = { 2, { CRGB::Magenta, CRGB::White } }; const COLOR_PACK colorPack_PURPLE_WHITE PROGMEM = { 2, { CRGB::DarkViolet, CRGB::White } }; -const COLOR_PACK colorPack_PURPLE_PINK PROGMEM = { 2, { CRGB::DarkViolet, CRGB::Pink } }; +const COLOR_PACK colorPack_PURPLE_MAGENTA PROGMEM = { 2, { CRGB::DarkViolet, CRGB::Magenta } }; const COLOR_PACK colorPack_PURPLE_YELLOW PROGMEM = { 2, { CRGB::Purple, CRGB::Yellow } }; @@ -45,11 +45,10 @@ const COLOR_PACK colorPack_BLUE_RED PROGMEM = { 2, { CRGB::Blue, CRGB::Red } }; const COLOR_PACK colorPack_ORANGE_BLUE PROGMEM = { 2, { CRGB::DarkOrange, CRGB::Blue } }; const COLOR_PACK colorPack_RED_GREEN PROGMEM = { 2, { CRGB::Red, CRGB::Green } }; const COLOR_PACK colorPack_CYAN_RED PROGMEM = { 2, { CRGB::Cyan, CRGB::Red } }; -const COLOR_PACK colorPack_MAGENTA_GREEN PROGMEM = { 2, { CRGB::Magenta, CRGB::Green } }; // Warm/cool combinations const COLOR_PACK colorPack_ORANGE_CYAN PROGMEM = { 2, { CRGB::DarkOrange, CRGB::Cyan } }; -const COLOR_PACK colorPack_PINK_GREEN PROGMEM = { 2, { CRGB::Pink, CRGB::Green } }; +const COLOR_PACK colorPack_MAGENTA_GREEN PROGMEM = { 2, { CRGB::Magenta, CRGB::Green } }; const COLOR_PACK colorPack_VIOLET_LIME PROGMEM = { 2, { CRGB::DarkViolet, CRGB::Lime } }; // Analogous combinations @@ -67,28 +66,28 @@ const COLOR_PACK double_colorPacks[] PROGMEM = { colorPack_RED_WHITE, colorPack_RED_YELLOW, colorPack_BLUE_WHITE, - colorPack_PINK_WHITE, - colorPack_GREEN_WHITE, colorPack_PURPLE_WHITE, - colorPack_GREEN_YELLOW, - colorPack_PURPLE_PINK, - colorPack_CYAN_RED, - + colorPack_MAGENTA_WHITE, + colorPack_GREEN_WHITE, colorPack_ORANGE_WHITE, + colorPack_PURPLE_MAGENTA, + colorPack_GREEN_YELLOW, + // 9 pack + - colorPack_PURPLE_PINK, + colorPack_CYAN_RED, + colorPack_PURPLE_MAGENTA, colorPack_PURPLE_YELLOW, - colorPack_BLUE_YELLOW, - colorPack_BLUE_GREEN, colorPack_BLUE_RED, colorPack_ORANGE_BLUE, + // 16 pack + + colorPack_RED_GREEN, - colorPack_MAGENTA_GREEN, colorPack_ORANGE_CYAN, - colorPack_PINK_GREEN, colorPack_VIOLET_LIME, colorPack_RED_ORANGE, colorPack_BLUE_PURPLE, @@ -101,12 +100,12 @@ const COLOR_PACK double_colorPacks[] PROGMEM = { // 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_RAINBOW PROGMEM = { 7, { CRGB::Red, CRGB::DarkOrange, CRGB::LightYellow, CRGB::Green, CRGB::Blue, CRGB::DarkViolet, CRGB::MediumVioletRed } }; // Triple Color Packs, Common Flags const COLOR_PACK colorPack_RED_WHITE_BLUE PROGMEM = { 3, { CRGB::Red, CRGB::White, CRGB::Blue } }; const COLOR_PACK colorPack_RED_WHITE_GREEN PROGMEM = { 3, { CRGB::Red, CRGB::White, CRGB::Green } }; const COLOR_PACK colorPack_RED_YELLOW_BLUE PROGMEM = { 3, { CRGB::Red, CRGB::Yellow, CRGB::Blue } }; -const COLOR_PACK colorPack_RED_ORANGE_YELLOW PROGMEM = { 3, { CRGB::Red, CRGB::DarkOrange, CRGB::Yellow } }; +const COLOR_PACK colorPack_RED_ORANGE_YELLOW PROGMEM = { 3, { CRGB::Red, CRGB::DarkOrange, CRGB::Yellow } }; const COLOR_PACK colorPack_RED_YELLOW_GREEN PROGMEM = { 3, { CRGB::Red, CRGB::Yellow, CRGB::Green } }; const COLOR_PACK colorPack_RED_PURPLE_BLUE PROGMEM = { 3, { CRGB::Red, CRGB::Purple, CRGB::Blue } }; @@ -114,13 +113,11 @@ const COLOR_PACK colorPack_GREEN_WHITE_RED PROGMEM = { 3, { CRGB::Green, CRGB:: const COLOR_PACK colorPack_GREEN_WHITE_ORANGE PROGMEM = { 3, { CRGB::Green, CRGB::White, CRGB::DarkOrange } }; const COLOR_PACK colorPack_GREEN_WHITE_BLUE PROGMEM = { 3, { CRGB::Green, CRGB::White, CRGB::Blue } }; - -const COLOR_PACK colorPack_BLUE_WHITE_GREEN PROGMEM = { 3, { CRGB::Blue, CRGB::White, CRGB::Green } }; const COLOR_PACK colorPack_BLUE_YELLOW_GREEN PROGMEM = { 3, { CRGB::Blue, CRGB::Yellow, CRGB::Green } }; const COLOR_PACK colorPack_BLUE_YELLOW_RED PROGMEM = { 3, { CRGB::Blue, CRGB::Yellow, CRGB::Red } }; // Additional triple color combinations -const COLOR_PACK colorPack_PURPLE_PINK_CYAN PROGMEM = { 3, { CRGB::Purple, CRGB::Pink, CRGB::Cyan } }; +const COLOR_PACK colorPack_MAGENTA_WHITE_CYAN PROGMEM = { 3, { CRGB::Magenta, CRGB::White, CRGB::Cyan } }; const COLOR_PACK colorPack_ORANGE_YELLOW_LIME PROGMEM = { 3, { CRGB::DarkOrange, CRGB::Yellow, CRGB::Lime } }; const COLOR_PACK colorPack_MAGENTA_YELLOW_CYAN PROGMEM = { 3, { CRGB::Magenta, CRGB::Yellow, CRGB::Cyan } }; const COLOR_PACK colorPack_LIME_CYAN_MAGENTA PROGMEM = { 3, { CRGB::Lime, CRGB::Cyan, CRGB::Magenta } }; @@ -149,27 +146,30 @@ const COLOR_PACK tripple_colorPacks[] PROGMEM = { colorPack_GREEN_WHITE_ORANGE, colorPack_BLUE_YELLOW_GREEN, colorPack_RED_GREEN_BLUE, - colorPack_PURPLE_PINK_CYAN, + colorPack_MAGENTA_WHITE_CYAN, colorPack_YELLOW_ORANGE_RED, colorPack_BLUE_CYAN_LIME, - colorPack_PINK_PURPLE_MAGENTA, - colorPack_RED_YELLOW_BLUE, + // 9 pack + + colorPack_RED_ORANGE_YELLOW, colorPack_RED_YELLOW_GREEN, colorPack_RED_PURPLE_BLUE, colorPack_GREEN_WHITE_RED, colorPack_GREEN_WHITE_BLUE, - colorPack_BLUE_WHITE_GREEN, colorPack_BLUE_YELLOW_RED, + colorPack_GREEN_CYAN_BLUE, + // 16 pack + + colorPack_ORANGE_YELLOW_LIME, colorPack_MAGENTA_YELLOW_CYAN, colorPack_LIME_CYAN_MAGENTA, colorPack_RED_ORANGE_PINK, colorPack_PURPLE_BLUE_CYAN, - colorPack_GREEN_CYAN_BLUE, colorPack_YELLOW_PURPLE_ORANGE, - colorPack_PINK_LIME_PURPLE + }; const COLOR_PACK colorPack_grad_blueish PROGMEM = { 3, { CRGB::Blue, CRGB::Cyan, CRGB::Green } }; @@ -200,7 +200,7 @@ 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 } }; -// 9 Single Color Packs +// 8 Single Color Packs const COLOR_PACK single_colorPacks[] PROGMEM = { colorPack_Single_White, colorPack_Single_Red, diff --git a/include/PWM_Output.h b/include/PWM_Output.h index 8cf92c2..21d5978 100644 --- a/include/PWM_Output.h +++ b/include/PWM_Output.h @@ -38,6 +38,8 @@ class PWM_Output { float standardFactor; float visionFactor; + int8_t pin; + bool initialized = false; }; diff --git a/include/Ramp_Lights.h b/include/Ramp_Lights.h index 4210ff6..edbdabc 100644 --- a/include/Ramp_Lights.h +++ b/include/Ramp_Lights.h @@ -18,7 +18,7 @@ private: float currentValue; bool IsOn = false; RAMP_STATE rampState; - int tickCount; + int tickCount = 0; void tick(); void singleClick(); diff --git a/include/global.h b/include/global.h index f29adc0..71f5d70 100644 --- a/include/global.h +++ b/include/global.h @@ -60,3 +60,5 @@ void Log_CPU_Load(void); void print_task_watermarks(void); +float updateLowpass(float currentValue, float newValue, float alpha); + diff --git a/include/my_device.h b/include/my_device.h index a9ae4f9..f6a6581 100644 --- a/include/my_device.h +++ b/include/my_device.h @@ -9,6 +9,10 @@ extern SYS_SETTINGS sys_settings; extern PWM_Output *pwmOutputs[4]; extern RAMP_LIGHT *rampLight1; extern RAMP_LIGHT *rampLight2; +extern String booth_file_path; +extern float PowerVin; +extern float PowerVinAlpha; +extern float prevPowerVin; void Init_ADC(void); float readBoardInputVoltage(void); diff --git a/include/my_wifi.h b/include/my_wifi.h index 2745c16..3de48b2 100644 --- a/include/my_wifi.h +++ b/include/my_wifi.h @@ -23,7 +23,7 @@ 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&)); +bool 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); diff --git a/include/system.h b/include/system.h index ad74af9..8e96366 100644 --- a/include/system.h +++ b/include/system.h @@ -5,6 +5,9 @@ #include #include "ATALights.h" + +enum LED_CHIPSET { LED_CHIPSET_WS2812B=0, LED_CHIPSET_SK6812=1, LED_CHIPSET_WS2811_400=2, LED_CHIPSET_WS2815=3, LED_CHIPSET_NONE=255 }; + typedef struct { bool enabled; }BTN_SETTINGS; @@ -79,6 +82,8 @@ typedef struct { enum BOOTH_MODE { BOOTH_MODE_NONE=0, BOOTH_MODE_ROAMER=1, BOOTH_MODE_STIK=2 }; typedef struct { + String profile; + bool limitedMode; BOOTH_MODE mode; BOARD_PINS boardPins; BTN_SETTINGS btnSettings[3]; diff --git a/platformio.ini b/platformio.ini index 9ff1f0c..04331e5 100644 --- a/platformio.ini +++ b/platformio.ini @@ -24,6 +24,7 @@ lib_deps = jeremycole/I2C Temperature Sensors derived from the LM75 @ ^1.0.3 mathertel/OneButton @ ^2.6.1 h2zero/NimBLE-Arduino @ ^1.4.1 + #h2zero/NimBLE-Arduino @ ^2.3.6 adafruit/Adafruit SSD1306 @ ^2.5.7 fastled/FastLED @ ^3.9.4 marian-craciunescu/ESP32Ping@^1.7 @@ -35,6 +36,11 @@ build_flags = #-D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE -D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_INFO -D CONFIG_ARDUHAL_LOG_COLORS=1 + #-Os + #-ffunction-sections + #-fdata-sections + #-Wl,--gc-sections + upload_port = COM5 debug_init_break = tbreak setup monitor_port = COM5 diff --git a/src/ATALights.cpp b/src/ATALights.cpp index faeacde..1ee214a 100644 --- a/src/ATALights.cpp +++ b/src/ATALights.cpp @@ -9,16 +9,16 @@ #include "system.h" #include "ColorPalettes.h" #include "global.h" -//#include #include "PWM_Output.h" #include "my_device.h" #include "system.h" #include "my_device.h" +#include "LittleFS.h" #define FASTLED_CORE 0 static const char* tag = "strips"; -uint32_t whiteTimeout = 0; +//uint32_t whiteTimeout = 0; TaskHandle_t Animation_Task_Handle; TaskHandle_t Ramp_Front_Light_Task_Handle; @@ -27,7 +27,13 @@ volatile bool AnimationLooping = false; ANIM_EVENT prevAnimEvent = {0}; QueueHandle_t animationQueue = xQueueCreate( 4, sizeof( ANIM_EVENT ) ); -bool fillAnimationActive = false; +bool upgradeMode = false; + +#define LIMITED_ANIMATION true + +#define DEFAULT_ANIMATION 23 + +//bool fillAnimationActive = false; void RGB_Lights_Set_Animation(int animIndex, uint8_t red, uint8_t grn, uint8_t blu){ @@ -47,7 +53,8 @@ void RGB_Lights_Set_Animation(int animIndex, uint8_t red, uint8_t grn, uint8_t b } void RGB_Animations_ON(){ - RGB_Lights_Set_Animation(prevAnimEvent.AnimationIndex, prevAnimEvent.data.red, prevAnimEvent.data.grn, prevAnimEvent.data.blu); + //RGB_Lights_Set_Animation(prevAnimEvent.AnimationIndex, prevAnimEvent.data.red, prevAnimEvent.data.grn, prevAnimEvent.data.blu); + RGB_Lights_Set_Animation(DEFAULT_ANIMATION, 0, 0, 0); // set comet rainbow animation } void RGB_Animations_OFF(){ @@ -63,7 +70,9 @@ void Lights_Set_White(uint8_t val){ //pwmOut[0]->setOutput(val / 2.5f); } -void Init_RGB_Lights_Task(void){ + +void Init_RGB_Lights_Task(bool upgrade){ + upgradeMode = upgrade; ledSettings[0].leds = new CRGB[ledSettings[0].size]; Init_RGB_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); @@ -234,31 +243,61 @@ inline int calcPixelIndex(LEDSTRIP_SETTINGS& strip, int index) { return (x +strip.offset) % strip.effSize; } */ +// ...existing code... +inline void setPixel1(LEDSTRIP_SETTINGS& leds, int pixelIndex, const CRGB col) { + // Guard against invalid configuration that can cause crashes (null pointer or zero size) + if (leds.size <= 0 || leds.leds == nullptr) { + ESP_LOGW(tag, "setPixel1: invalid strip (Size=%d, leds=%p)", leds.size, (void*)leds.leds); + return; + } + // Use a signed type so negative pixelIndex is preserved + int x = pixelIndex + leds.shift; + int n = leds.size; + + // If strip.effSize is power of 2: (n & (n - 1)) == 0 + int idx; + if ((n & (n - 1)) == 0) { + // Masking is safe if we treat x as unsigned for masking, + // this yields the correct wrapped index for two's-complement + idx = static_cast( (static_cast(x)) & (static_cast(n) - 1u) ); + } else { + // General modulo: C++ '%' can be negative, so normalize + idx = x % n; + if (idx < 0) idx += n; + } + + // Normalize final position robustly (handles negative offsets) + int pos = (idx + leds.offset) % n; + if (pos < 0) pos += n; + + leds.leds[pos] = col; +} +/* 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; + // If strip.size is power of 2, use faster bit masking + if ((leds.size & (leds.size - 1)) == 0) { + x = (x < 0) ? ((x + leds.size) & (leds.size - 1)) : (x & (leds.size - 1)); + leds.leds[(x + leds.offset) & (leds.size - 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; + x = (x < 0) ? ((x + ledSettings[0].size) % ledSettings[0].size) : (x % ledSettings[0].size); + ledSettings[0].leds[(x + ledSettings[0].offset) % ledSettings[0].size] = 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; + // If strip.size is power of 2, use faster bit masking + if ((ledSettings[1].size & (ledSettings[1].size - 1)) == 0) { + x = (x < 0) ? ((x + ledSettings[1].size) & (ledSettings[1].size - 1)) : (x & (ledSettings[1].size - 1)); + ledSettings[1].leds[(x + ledSettings[1].offset) & (ledSettings[1].size - 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; + x = (x < 0) ? ((x + ledSettings[1].size) % ledSettings[1].size) : (x % ledSettings[1].size); + ledSettings[1].leds[(x + ledSettings[1].offset) % ledSettings[1].size] = col; } } @@ -286,7 +325,7 @@ void Init_RGB_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder, if(pin == 3){ // First level: Chip type selection - if (chipUpper == "WS2812B" || chipUpper == "SK6812") { + if (chipUpper == "WS2812B") { switch(rgbOrder) { case RGB: FastLED.addLeds(leds, size); break; case RBG: FastLED.addLeds(leds, size); break; @@ -308,7 +347,7 @@ void Init_RGB_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder, default: FastLED.addLeds(leds, size); break; } } - else if (chipUpper == "WS2811_400") { + else if (chipUpper == "WS2811") { switch(rgbOrder) { case RGB: FastLED.addLeds(leds, size); break; case RBG: FastLED.addLeds(leds, size); break; @@ -361,7 +400,7 @@ void Init_RGB_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder, default: FastLED.addLeds(leds, size); break; } } - else if (chipUpper == "WS2811_400") { + else if (chipUpper == "WS2811") { switch(rgbOrder) { case RGB: FastLED.addLeds(leds, size); break; case RBG: FastLED.addLeds(leds, size); break; @@ -392,7 +431,7 @@ void Init_RGB_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder, } else if(pin == 46){ // First level: Chip type selection - if (chipUpper == "WS2812B" || chipUpper == "SK6812") { + if (chipUpper == "WS2812B") { switch(rgbOrder) { case RGB: FastLED.addLeds(leds, size); break; case RBG: FastLED.addLeds(leds, size); break; @@ -414,7 +453,7 @@ void Init_RGB_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder, default: FastLED.addLeds(leds, size); break; } } - else if (chipUpper == "WS2811_400") { + else if (chipUpper == "WS2811") { switch(rgbOrder) { case RGB: FastLED.addLeds(leds, size); break; case RBG: FastLED.addLeds(leds, size); break; @@ -439,7 +478,7 @@ void Init_RGB_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder, 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); + FastLED.addLeds(leds, size); } ESP_LOGI(tag, "Initialized %s LED strip with %d LEDs on pin %d", chipType.c_str(), size, pin); } @@ -449,147 +488,392 @@ void Init_RGB_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder, void RGB_Lights_Control_Task(void *parameters){ + // Wait for other tasks to initialize + vTaskDelay(pdMS_TO_TICKS(200)); // wait for everything to settle + RGB_Lights_Set_Brightness(ledSettings[0].bright); + + if(upgradeMode){ + // fill with 3 colors equally magenta, cyan, lime + RGB_Lights_Set_Brightness(64); + fill_gradient_RGB(ledSettings[0].leds, ledSettings[0].size, CRGB(CRGB::Magenta), CRGB(CRGB::Cyan), CRGB(CRGB::Lime), CRGB(CRGB::Magenta)); + FastLED.show(); + }else{ + RGB_Lights_Set_Animation(DEFAULT_ANIMATION, 0, 0, 0); // set comet rainbow animation + } ANIM_EVENT AnimEvent; - CRGB col; - COLOR_PACK colorPack; - CRGBPalette16 firePalette; + + if (LIMITED_ANIMATION){ + while (true) { + if (xQueueReceive(animationQueue, &AnimEvent, portMAX_DELAY) == pdTRUE) { + ESP_LOGD(tag, "New Animation Event: Index: %d", AnimEvent.AnimationIndex); + switch (AnimEvent.AnimationIndex) { - // Wait for other tasks to initialize (including front light task) - vTaskDelay(pdMS_TO_TICKS(500)); // wait for everything to settle - - RGB_Lights_Set_Brightness(48); - RGB_Lights_Set_Animation(23, 0, 0, 0); // set comet rainbow animation - - - while (true) { - if (xQueueReceive(animationQueue, &AnimEvent, portMAX_DELAY) == pdTRUE) { - ESP_LOGI(tag, "New Animation Event: Index: %d", AnimEvent.AnimationIndex); - switch (AnimEvent.AnimationIndex) { - case -3: // Set Pixel by index - if (AnimEvent.data.data[7] >= 0 && AnimEvent.data.data[7] < ledSettings[0].size) { - ledSettings[0].leds[AnimEvent.data.data[7]] = CRGB(AnimEvent.data.red, AnimEvent.data.grn, AnimEvent.data.blu); + case -3: // Set Shift + if (AnimEvent.data.data[0] >= 0 && AnimEvent.data.data[0] < ledSettings[0].size) { + fill_solid(ledSettings[0].leds, ledSettings[0].size, CRGB::Black); + ledSettings[0].shift = AnimEvent.data.data[0]; + vTaskDelay(25 / portTICK_PERIOD_MS); + setPixel1(ledSettings[0], 0, CRGB::White * FastLED.getBrightness() / 255); + FastLED.show(); + ESP_LOGD(tag, "Set Shift: %d", ledSettings[0].shift); + } else { + ESP_LOGW(tag, "Pixel index out of range: %d", AnimEvent.data.data[0]); + } + break; + case -2: // Fill Static Color + fill_solid(ledSettings[0].leds, ledSettings[0].size, CRGB(AnimEvent.data.red, AnimEvent.data.grn, AnimEvent.data.blu)); FastLED.show(); - } else { - ESP_LOGW(tag, "Pixel index out of range: %d", AnimEvent.data.data[7]); + ESP_LOGD(tag, "Static Color"); + break; + case -1: + FastLED.clear(); + FastLED.show(); + ESP_LOGD(tag, "LEDs Off"); + break; + case 0: + fill_solid(ledSettings[0].leds, ledSettings[0].size, CRGB::Black); + FastLED.show(); + break; + case 1 ... 5: { // Timed Fill Animations + int timeDuration = AnimEvent.AnimationIndex * 1000 - 400; + int whiteDelay = timeDuration - 1000; + if (whiteDelay < 800) whiteDelay = 800; // minimum 8 seconds + Anim_TimedFill_Flash(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, pwmOutputs[0], &sys_settings.pwmOutSettings[0], whiteDelay, 20000, CRGB::Black, CRGB::White, timeDuration, ledSettings[0].shift); + break; } - 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: - fill_solid(ledSettings[0].leds, ledSettings[0].size, CRGB::Black); - FastLED.show(); - break; - case 1: case 2: case 3: case 4: case 5:{ // Timed Fill Animations - int timeDuration = AnimEvent.AnimationIndex * 1000; - int whiteDelay = timeDuration - 1000; - Anim_TimedFill_Flash(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, pwmOutputs[0], &sys_settings.pwmOutSettings[0], whiteDelay, 10000, CRGB::Black, CRGB::White, timeDuration, ledSettings[0].shift); - break; - } - case 6: - // RGB White and Dedicated White all on with timeout and brightness reduction - if (pwmOutputs[0] != nullptr) { + case 6: + // RGB White and Dedicated White all on with timeout and brightness reduction Anim_SolidWhite(AnimationLooping, ledSettings[0].leds, pwmOutputs[0], ledSettings[0].size, 240, 30000); + break; + case 7: + Anim_Rainbow(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, 60); + break; + case 8 ... 12: + Anim_GradientRotate(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, gradient_colorPack[AnimEvent.AnimationIndex - 8], 80); + break; + case 13 ... 17: { // Fire Animations + COLOR_PACK fp = fireColorPacks[AnimEvent.AnimationIndex - 13]; // copy const pack to mutable + CRGBPalette16 firePalette; + createFirePalette(firePalette, fp); + Anim_Fire(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, 60, firePalette, ledSettings[0].shift); + break; } - break; - case 7: - Anim_Rainbow(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, 60); - break; - case 8: case 9: case 10: case 11: case 12: - Anim_GradientRotate(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, gradient_colorPack[AnimEvent.AnimationIndex - 8], 80); - break; - case 13: case 14: case 15: case 16: case 17: { // Fire Animations - COLOR_PACK fp = fireColorPacks[AnimEvent.AnimationIndex - 13]; // copy const pack to mutable - createFirePalette(firePalette, fp); - Anim_Fire(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, 60, firePalette, ledSettings[0].shift); - break; + case 18 ... 20: // Sparkle/Twinkle + Anim_Sparkle(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[0], 40, 20); + break; + case 21: + Anim_ColorBreath(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 7000, 90); + break; + case 22: // Rain Animation + Anim_Morph(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 20, 40); + break; + + // Comets + case 23: + Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 40, 85, RANDOM_DECAY, 1); + break; + case 24 ... 31: + Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, single_colorPacks[AnimEvent.AnimationIndex - 24], 40, 85, RANDOM_DECAY, 6); + break; + case 32 ... 40: + Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 32], 40, 85, RANDOM_DECAY, 3); + break; + case 41 ... 49: + Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 41], 40, 85, RANDOM_DECAY, 2); + break; + + // Snakes + case 50: + Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 50); + break; + case 51 ... 58: + Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, single_colorPacks[AnimEvent.AnimationIndex - 51], 60, 6); + break; + case 59 ... 67: + Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 59], 60, 3); + break; + case 68 ... 76: + Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 68], 60, 2); + break; + + // Sectors + case 77: + Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, NO_GAPS, 1, 40, 90); + break; + case 78 ... 86: + Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 78], NO_GAPS, 3, 40, 90); + break; + case 87 ... 95: + Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 87], NO_GAPS, 2, 40, 90); + break; + + // Dashes + case 96: + Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, WITH_GAPS, 1, 40, 90); + break; + case 97 ... 104: + Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, single_colorPacks[AnimEvent.AnimationIndex - 97], WITH_GAPS, 6, 40, 90); + break; + case 105 ... 113: + Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 105], WITH_GAPS, 3, 40, 90); + break; + case 114 ... 122: + Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 114], WITH_GAPS, 2, 40, 90); + break; + default: + ESP_LOGW(tag, "Loop default"); + break; } - case 18: case 19: case 20:// Sparkle/Twinkle - Anim_Sparkle(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[0], 40, 20); - break; - case 21: - //Anim_ColorBreath(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 7000, 90); - Anim_Morph(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 20, 40); - break; - case 22: // Rain Animation - break; - - // Comets - case 23: - Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 40, 85, RANDOM_DECAY, 1); - break; - case 24: case 25: case 26: case 27: case 28: case 29: case 30: case 31: - Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, single_colorPacks[AnimEvent.AnimationIndex - 24], 40, 85, RANDOM_DECAY, 6); - break; - case 32: case 33: case 34: case 35: case 36: case 37: case 38: case 39: case 40: - Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 32], 40, 85, RANDOM_DECAY, 3); - break; - case 41: case 42: case 43: case 44: case 45: case 46: case 47: case 48: case 49: - Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 41], 40, 85, RANDOM_DECAY, 2); - break; - - // Snakes - case 50: - Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 50); - break; - case 51: case 52: case 53: case 54: case 55: case 56: case 57: case 58: - Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, single_colorPacks[AnimEvent.AnimationIndex - 51], 60, 6); - break; - case 59: case 60: case 61: case 62: case 63: case 64: case 65: case 66: case 67: - Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 59], 60, 3); - break; - case 68: case 69: case 70: case 71: case 72: case 73: case 74: case 75: case 76: - Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 68], 60, 2); - break; - - // Sectors - case 77: - Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, NO_GAPS, 1, 40, 90); - break; - case 78: case 79: case 80: case 81: case 82: case 83: case 84: case 85: case 86: - Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 78], NO_GAPS, 3, 40, 90); - break; - case 87: case 88: case 89: case 90: case 91: case 92: case 93: case 94: case 95: - Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 87], NO_GAPS, 2, 40, 90); - break; - - // Dashes - case 96: - Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, WITH_GAPS, 1, 40, 90); - break; - case 97:case 98:case 99: case 100: case 101: case 102: case 103: case 104: - Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, single_colorPacks[AnimEvent.AnimationIndex - 97], WITH_GAPS, 6, 40, 90); - break; - case 105: case 106: case 107: case 108: case 109: case 110: case 111: case 112: case 113: - Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 105], WITH_GAPS, 3, 40, 90); - break; - case 114: case 115: case 116: case 117: case 118: case 119: case 120: case 121: case 122: - Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 114], WITH_GAPS, 2, 40, 90); - break; - - default: - ESP_LOGW(tag, "Loop default"); - break; + AnimationLooping = false; + prevAnimEvent = AnimEvent; + ESP_LOGD(tag, "Going to Queue to Wait"); } + } + } else { + // Extended Animation Set + 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 Shift + if (AnimEvent.data.data[0] >= 0 && AnimEvent.data.data[0] < ledSettings[0].size) { + fill_solid(ledSettings[0].leds, ledSettings[0].size, CRGB::Black); + ledSettings[0].shift = AnimEvent.data.data[0]; - AnimationLooping = false; - prevAnimEvent = AnimEvent; - ESP_LOGD(tag, "Going to Queue to Wait"); + ESP_LOGD(tag, "Set Shift: %d", ledSettings[0].shift); + vTaskDelay(25 / portTICK_PERIOD_MS); + setPixel1(ledSettings[0], 0, CRGB::White); + + FastLED.show(); + } else { + ESP_LOGW(tag, "Pixel index out of range: %d", AnimEvent.data.data[0]); + } + break; + case -2: // Fill Static Color + fill_solid(ledSettings[0].leds, ledSettings[0].size, CRGB(AnimEvent.data.red, AnimEvent.data.grn, AnimEvent.data.blu)); + FastLED.show(); + ESP_LOGD(tag, "Static Color"); + break; + case -1: + FastLED.clear(); + FastLED.show(); + ESP_LOGD(tag, "LEDs Off"); + break; + case 0: + fill_solid(ledSettings[0].leds, ledSettings[0].size, CRGB::Black); + FastLED.show(); + break; + case 1 ... 5: { // Timed Fill Animations + int timeDuration = AnimEvent.AnimationIndex * 1000 - 400; + int whiteDelay = timeDuration - 1000; + if (whiteDelay < 800) whiteDelay = 800; // minimum 8 seconds + Anim_TimedFill_Flash(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, pwmOutputs[0], &sys_settings.pwmOutSettings[0], whiteDelay, 10000, CRGB::Black, CRGB::White, timeDuration, ledSettings[0].shift); + break; + } + case 6: + // RGB White and Dedicated White all on with timeout and brightness reduction + Anim_SolidWhite(AnimationLooping, ledSettings[0].leds, pwmOutputs[0], ledSettings[0].size, 240, 30000); + break; + case 7: + Anim_Rainbow(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, 60); + break; + case 8 ... 12: + Anim_GradientRotate(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, gradient_colorPack[AnimEvent.AnimationIndex - 8], 80); + break; + case 13 ... 17: { // Fire Animations + COLOR_PACK fp = fireColorPacks[AnimEvent.AnimationIndex - 13]; // copy const pack to mutable + CRGBPalette16 firePalette; + createFirePalette(firePalette, fp); + Anim_Fire(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, 60, firePalette, ledSettings[0].shift); + break; + } + case 18 ... 20: // Sparkle/Twinkle + Anim_Sparkle(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[0], 40, 20); + break; + case 21: + Anim_ColorBreath(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 7000, 90); + break; + case 22: // Rain Animation + Anim_Morph(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 20, 40); + break; + + // Comets + case 23: + Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 40, 85, RANDOM_DECAY, 1); + break; + case 24 ... 31: + Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, single_colorPacks[AnimEvent.AnimationIndex - 24], 40, 85, RANDOM_DECAY, 6); + break; + case 32 ... 47: + Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 32], 40, 85, RANDOM_DECAY, 3); + break; + case 48 ... 63: + Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 48], 40, 85, RANDOM_DECAY, 2); + break; + + // Snakes + case 64: + Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 50); + break; + case 65 ... 72: + Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, single_colorPacks[AnimEvent.AnimationIndex - 65], 60, 6); + break; + case 73 ... 88: + Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 73], 60, 3); + break; + case 89 ... 104: + Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 89], 60, 2); + break; + + // Sectors + case 105: + Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, NO_GAPS, 1, 40, 90); + break; + case 106 ... 121: + Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 106], NO_GAPS, 3, 40, 90); + break; + case 122 ... 137: + Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 122], NO_GAPS, 2, 40, 90); + break; + + // Dashes + case 138: + Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, WITH_GAPS, 1, 40, 90); + break; + case 139 ... 146: + Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, single_colorPacks[AnimEvent.AnimationIndex - 139], WITH_GAPS, 6, 40, 90); + break; + case 147 ... 162: + Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 147], WITH_GAPS, 3, 40, 90); + break; + case 163 ... 178: + Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 163], WITH_GAPS, 2, 40, 90); + break; + default: + ESP_LOGW(tag, "Loop default"); + break; + } + + AnimationLooping = false; + prevAnimEvent = AnimEvent; + ESP_LOGD(tag, "Going to Queue to Wait"); + } } } } +void SetAndSaveUserSettings(USER_SETTINGS &userSettings) { + + // Apply the settings to sys_settings + sys_settings.limitedMode = userSettings.limitedMode; + + sys_settings.ledStripSettings[0]->chip = userSettings.ledChipset1; + sys_settings.ledStripSettings[0]->shift = userSettings.ledShift1; + sys_settings.ledStripSettings[0]->bright = userSettings.ledBrightness1; + RGB_Lights_Set_Brightness(userSettings.ledBrightness1); + + sys_settings.ledStripSettings[0]->chip = userSettings.ledChipset2; + sys_settings.ledStripSettings[0]->shift = userSettings.ledShift2; + sys_settings.ledStripSettings[0]->bright = userSettings.ledBrightness2; + + sys_settings.tSensorSettings.setpoint1 = userSettings.fanLowerTemp; + sys_settings.tSensorSettings.setpoint2 = userSettings.fanUpperTemp; + sys_settings.rampLightSettings[0].min = userSettings.frontLightMin; + sys_settings.rampLightSettings[0].max = userSettings.frontLightMax; + sys_settings.rampLightSettings[1].min = userSettings.rearLightMin; + sys_settings.rampLightSettings[1].max = userSettings.rearLightMax; + + if (!LittleFS.begin()) { + ESP_LOGE(tag, "Failed to mount file system"); + return; + } + + // First, open the existing file for reading so we preserve unrelated keys + File file = LittleFS.open(booth_file_path, "r"); + JsonDocument jsonDocument; + + if (file) { + // Try to deserialize existing content; if it fails, start with an empty document + DeserializationError err = deserializeJson(jsonDocument, file); + if (err) { + ESP_LOGW(tag, "Failed to parse existing JSON (%s), starting fresh", err.c_str()); + jsonDocument.clear(); + } + file.close(); + } else { + ESP_LOGW(tag, "Settings file not found, a new one will be created"); + } + + // Update only the keys we care about + jsonDocument["limied-mode"] = userSettings.limitedMode; + + jsonDocument["ramp-lights"][0]["min"] = userSettings.frontLightMin; + jsonDocument["ramp-lights"][0]["max"] = userSettings.frontLightMax; + jsonDocument["ramp-lights"][1]["min"] = userSettings.rearLightMin; + jsonDocument["ramp-lights"][1]["max"] = userSettings.rearLightMax; + + String chipName = "Unknown"; + if (userSettings.ledChipset1 == CHIP_WS2812B) chipName = "WS2812B"; + else if (userSettings.ledChipset1 == CHIP_SK6812) chipName = "SK6812"; + else if (userSettings.ledChipset1 == CHIP_WS2811) chipName = "WS2811"; + else if (userSettings.ledChipset1 == CHIP_WS2815) chipName = "WS2815"; + jsonDocument["strips"][0]["chip"] = chipName; + userSettings.rgbOrder1[3] = '\0'; // Ensure null-terminated string + jsonDocument["strips"][0]["rgb-order"] = String(userSettings.rgbOrder1); + jsonDocument["strips"][0]["shift"] = userSettings.ledShift1; + jsonDocument["strips"][0]["bright"] = userSettings.ledBrightness1; + jsonDocument["strips"][0]["count"] = userSettings.ledCount1; + + + if (userSettings.ledChipset2 == CHIP_WS2812B) chipName = "WS2812B"; + else if (userSettings.ledChipset2 == CHIP_SK6812) chipName = "SK6812"; + else if (userSettings.ledChipset2 == CHIP_WS2811) chipName = "WS2811"; + else if (userSettings.ledChipset2 == CHIP_WS2815) chipName = "WS2815"; + jsonDocument["strips"][1]["chip"] = chipName; + userSettings.rgbOrder2[3] = '\0'; // Ensure null-terminated string + jsonDocument["strips"][1]["rgb-order"] = String(userSettings.rgbOrder2); + jsonDocument["strips"][1]["shift"] = userSettings.ledShift2; + jsonDocument["strips"][1]["bright"] = userSettings.ledBrightness2; + jsonDocument["strips"][1]["count"] = userSettings.ledCount2; + + jsonDocument["t-sensor"]["sp1"] = userSettings.fanLowerTemp; + jsonDocument["t-sensor"]["sp2"] = userSettings.fanUpperTemp; + + //jsonDocument["stripsr"][0]["chip"] = settings.ledChipset; + + // Now open the file for writing (overwrite) and serialize back + File out = LittleFS.open(booth_file_path, "w"); + if (!out) { + ESP_LOGE(tag, "Failed to open file for writing"); + return; + } + + if (serializeJson(jsonDocument, out) == 0) { + ESP_LOGE(tag, "Failed to write to file"); + } else { + ESP_LOGI(tag, "User settings saved successfully"); + } + + out.close(); + + + // Modify system.json with booth type + String sysProf = String(sys_settings.profile); + String userProf = String(userSettings.profile); + sysProf.toLowerCase(); + userProf.toLowerCase(); + ESP_LOGI(tag, "System Profile: '%s', User Profile: '%s'", sysProf.c_str(), userProf.c_str()); + + if (userProf.length() > 0 && sysProf.indexOf(userProf) >= 0) { + // userSettings.profile is contained in sys_settings.profile (case-insensitive) + } + +} + + void createFirePalette(CRGBPalette16& palette, const COLOR_PACK& colorPack) { for (uint8_t i = 0; i < 16; i++) { if (i < 3) palette[i] = CRGB::Black; diff --git a/src/Animations.cpp b/src/Animations.cpp index 430ac81..b59d15e 100644 --- a/src/Animations.cpp +++ b/src/Animations.cpp @@ -27,6 +27,7 @@ void Animation_Init(void){ } // 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"); @@ -65,7 +66,62 @@ 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); + + // compute desired loop delay in milliseconds and clamp + int loopDelayMs = max(MaxSpeed - speed, MinLoopDelay); + const int MAX_LOOP_DELAY_MS = 60 * 1000; // 60s safety cap + if (loopDelayMs > MAX_LOOP_DELAY_MS) loopDelayMs = MAX_LOOP_DELAY_MS; + + // Convert ms -> RTOS ticks + TickType_t loopDelayTicks = pdMS_TO_TICKS(loopDelayMs); + + 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; + } catch (...) { + ESP_LOGE("Animation_Loop", "Callback unknown exception"); + break; + } + + if(!loop_active_flag) break; + + // compute elapsed ticks since callback start and remaining ticks + elapsedTicks = xTaskGetTickCount() - xLastWakeTime; + delayTicks = (elapsedTicks < loopDelayTicks) ? (loopDelayTicks - elapsedTicks) : 0; + + if (delayTicks == 0) { + // yield a tick to avoid busy-spin + vTaskDelay(1); + if (ulTaskNotifyTake(pdTRUE, 0)) break; // notified -> exit + } else { + if (ulTaskNotifyTake(pdTRUE, delayTicks)) { break; } // notified -> exit + } + } + + loop_active_flag = false; +} + +/* void Animation_Loop_Variable(bool volatile& loop_active_flag, std::function callback) { if (!callback) { ESP_LOGE("Animation_Loop", "Invalid callback function"); @@ -105,8 +161,74 @@ void Animation_Loop_Variable(bool volatile& loop_active_flag, std::function callback) { + if (!callback) { + ESP_LOGE("Animation_Loop", "Invalid callback function"); + return; + } + + loop_active_flag = true; + + // clear any pending notification + ulTaskNotifyTake(pdTRUE, 0); + + TickType_t xLastWakeTime; + TickType_t elapsedTicks; + TickType_t delayTicks; + + // Define sensible upper bound for delay (ms) to avoid indefinite blocking due to bad callback + const int MAX_LOOP_DELAY_MS = 60 * 1000; // 60 seconds + + while (loop_active_flag) { + xLastWakeTime = xTaskGetTickCount(); + + int loopDelayMs = 0; + try { + loopDelayMs = callback(); // callback returns desired delay in milliseconds + } catch (const std::exception& e) { + ESP_LOGE("Animation_Loop", "Callback exception: %s", e.what()); + break; + } catch (...) { + ESP_LOGE("Animation_Loop", "Callback unknown exception"); + break; + } + + if (!loop_active_flag) break; + + // sanitize returned ms value and enforce minimum/maximum + if (loopDelayMs < (int)MinLoopDelay) loopDelayMs = MinLoopDelay; + if (loopDelayMs > MAX_LOOP_DELAY_MS) loopDelayMs = MAX_LOOP_DELAY_MS; + + // convert ms -> RTOS ticks (safe conversion) + TickType_t loopDelayTicks = pdMS_TO_TICKS(loopDelayMs); + + // compute elapsed ticks since callback start + elapsedTicks = xTaskGetTickCount() - xLastWakeTime; + + // compute remaining delay (in ticks) + delayTicks = (elapsedTicks < loopDelayTicks) ? (loopDelayTicks - elapsedTicks) : 0; + + if (delayTicks == 0) { + // Ensure we yield at least one tick to avoid busy-looping + vTaskDelay(1); + // Check if notified during the yield + if (ulTaskNotifyTake(pdTRUE, 0)) break; + } else { + // Wait for either a notification (termination) or the timeout + if (ulTaskNotifyTake(pdTRUE, delayTicks)) { + // notified => exit loop + 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); @@ -159,8 +281,72 @@ void Animation_Loop_Duration(bool volatile& loop_active_flag, int speed, TickTyp loop_active_flag = false; } +*/ + +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); + + // treat durationMs as milliseconds (convert to ticks) + const TickType_t durationTicks = pdMS_TO_TICKS(durationMs); + + ulTaskNotifyTake(pdTRUE, 0); // Clear any pending notifications + TickType_t startTicks = xTaskGetTickCount(); + TickType_t xLastWakeTime; + + const int MAX_LOOP_DELAY_MS = 60 * 1000; // safety cap + while (loop_active_flag) { + xLastWakeTime = xTaskGetTickCount(); + + // Call animation function + int speedIncrease = 0; + try { + speedIncrease = callback(); + } catch (const std::exception& e) { + ESP_LOGE("Animation_Loop_Duration", "Callback exception: %s", e.what()); + break; + } catch (...) { + ESP_LOGE("Animation_Loop_Duration", "Callback unknown exception"); + break; + } + + if(!loop_active_flag) break; + + // Calculate combined speed with bounds protection + int totalSpeed = constrain(speed + speedIncrease, 0, MaxSpeed); + + // Calculate delay with minimum protection (ms) + int loopDelayMs = MaxSpeed - totalSpeed; + loopDelayMs = max(loopDelayMs, MinLoopDelay); + if (loopDelayMs > MAX_LOOP_DELAY_MS) loopDelayMs = MAX_LOOP_DELAY_MS; + TickType_t loopDelayTicks = pdMS_TO_TICKS(loopDelayMs); + + // Calculate remaining time with overflow protection + TickType_t elapsedTicks = xTaskGetTickCount() - xLastWakeTime; + TickType_t delayTicks = (elapsedTicks < loopDelayTicks) ? (loopDelayTicks - elapsedTicks) : 0; + + // Delay and Check for termination request + if (delayTicks == 0) { + vTaskDelay(1); + if (ulTaskNotifyTake(pdTRUE, 0)) break; + } else { + if (ulTaskNotifyTake(pdTRUE, delayTicks)) break; + } + + // Check if duration reached and wait for loop_active_flag + if (durationTicks > 0) { + TickType_t totalElapsed = xTaskGetTickCount() - startTicks; + if (totalElapsed >= durationTicks) { + break; // Auto-terminate when duration reached + } + } + } + + 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; @@ -211,6 +397,66 @@ void Animation_Loop_Cycles(bool volatile& loop_active_flag, int speed, uint32_t loop_active_flag = false; } +*/ + +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 xLastWakeTime; + TickType_t elapsedTicks; + TickType_t delayTicks; + + const int MAX_LOOP_DELAY_MS = 60 * 1000; // safety cap + for(;;) { + xLastWakeTime = xTaskGetTickCount(); + + int speedIncrease = 0; + try { + speedIncrease = callback(); // Call animation function + } catch (const std::exception& e) { + ESP_LOGE("Animation_Loop_Cycles", "Callback exception: %s", e.what()); + break; + } catch (...) { + ESP_LOGE("Animation_Loop_Cycles", "Callback unknown exception"); + break; + } + + if(!loop_active_flag) break; + + // Calculate combined speed with bounds protection + int totalSpeed = constrain(speed + speedIncrease, 0, MaxSpeed); + + // Calculate delay with minimum protection (ms) + int loopDelayMs = MaxSpeed - totalSpeed; + loopDelayMs = max(loopDelayMs, MinLoopDelay); + if (loopDelayMs > MAX_LOOP_DELAY_MS) loopDelayMs = MAX_LOOP_DELAY_MS; + TickType_t loopDelayTicks = pdMS_TO_TICKS(loopDelayMs); + + // Calculate remaining time with overflow protection + elapsedTicks = xTaskGetTickCount() - xLastWakeTime; + delayTicks = (elapsedTicks < loopDelayTicks) ? (loopDelayTicks - elapsedTicks) : 0; + + // Delay and Check for termination request + if (delayTicks == 0) { + vTaskDelay(1); + if (ulTaskNotifyTake(pdTRUE, 0)) break; + } else { + if (ulTaskNotifyTake(pdTRUE, delayTicks)) break; + } + + // Check if cycles reached and exit + if (loop_cycle_count >= loop_cycles) { + break; + } + + loop_cycle_count++; + } + + loop_active_flag = false; +} /******************************************************************************** @@ -399,76 +645,6 @@ void Anim_Color_Sectors(bool volatile& activeFlag, CRGB* leds, int size, const C //#define COMET_FADE_FACTOR COMET_FADE_FACTOR2 #define MAX_COMETS 8 // 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 { - const int loopTick = 30; // ms per animation tick - Animation_Loop(activeFlag, loopTick, [&]() -> 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 minSpeed, int maxSpeed, bool randomDecay, int cometMultiplier) { // Validate inputs int numComets = colorPack.size; @@ -479,8 +655,8 @@ void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PA return; } - // Calculate comet size - int cometSize = (size / totalComets) * COMET_SIZE_FACTOR; + // Calculate comet size (use float math and round to avoid integer-division truncation) + int cometSize = (int)(((float)size * (float)COMET_SIZE_FACTOR) / (float)totalComets + 0.5f); if (cometSize < 1) cometSize = 1; // Set fade factor @@ -500,9 +676,14 @@ void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PA int pos; CRGB color; int speed = minSpeed; - int loopDuration = (size * CYCLES_PER_DIRECTION); + int loopDuration = size * CYCLES_PER_DIRECTION; + // Defensive: ensure loopDuration isn't zero to avoid divide-by-zero later + if (loopDuration < 1) loopDuration = 1; int thirdLoop = loopDuration / 3; - int twoThirdLoop = (2 * loopDuration) / 3; + // Defensive: ensure thirdLoop is at least 1 for progress calculations + if (thirdLoop < 1) thirdLoop = 1; + // Keep twoThirdLoop consistent with thirdLoop to avoid rounding surprises + int twoThirdLoop = 2 * thirdLoop; try { Animation_Loop_Variable(activeFlag, [&]() -> int { // Fade all LEDs @@ -558,7 +739,11 @@ void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PA FastLED.show(); - return max(MaxSpeed - speed, MinLoopDelay); + // Compute delay to return to Animation_Loop_Variable. + // Ensure we never return a value less than MinLoopDelay. + int computed = MaxSpeed - speed; + if (computed < MinLoopDelay) computed = MinLoopDelay; + return computed; }); fill_solid(leds, size, CRGB::Black); @@ -569,6 +754,131 @@ void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PA ESP_LOGE("Anim_Comets", "Unknown exception in Animation_Loop"); } } +*/ +void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colorPack, int minSpeed, int maxSpeed, bool randomDecay, int cometMultiplier) { + // 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; + } + + // Sanitize speed inputs to known bounds + minSpeed = constrain(minSpeed, 0, MaxSpeed); + maxSpeed = constrain(maxSpeed, 0, MaxSpeed); + if (minSpeed > maxSpeed) { int t=minSpeed; minSpeed=maxSpeed; maxSpeed=t; } + + // Calculate comet size (use float math and round to avoid integer-division truncation) + int cometSize = (int)(((float)size * (float)COMET_SIZE_FACTOR) / (float)totalComets + 0.5f); + if (cometSize < 1) cometSize = 1; + + // Set fade factor + uint8_t fadeFactor = map(totalComets, 1, MAX_COMETS, COMET_FADE_FACTOR1, COMET_FADE_FACTOR2); + fadeFactor = constrain(fadeFactor, COMET_FADE_FACTOR1, COMET_FADE_FACTOR2); + + // Initialize comet positions with fixed array, evenly distributed + int cometPositions[MAX_COMETS] = {0}; + for (int i = 0; i < totalComets; i++) { + // Even distribution even when size not divisible by totalComets + cometPositions[i] = (i * size) / totalComets; + } + + // Animation loop + bool direction = true; // true = forward, false = backward + int loopCounter = 0; + int pos; + CRGB color; + int speed = minSpeed; + + // Cache size/limits locally for speed + const int localSize = size; + const int localTotalComets = totalComets; + int loopDuration = localSize * CYCLES_PER_DIRECTION; + // Defensive: ensure loopDuration isn't zero to avoid divide-by-zero later + if (loopDuration < 1) loopDuration = 1; + int thirdLoop = loopDuration / 3; + if (thirdLoop < 1) thirdLoop = 1; + int twoThirdLoop = 2 * thirdLoop; + + try { + Animation_Loop_Variable(activeFlag, [&]() -> int { + // Fade all LEDs (keep this O(N) operation but keep it tight) + for (int i = 0; i < localSize; 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 < localTotalComets; i++) { + if (direction) { + cometPositions[i] = (cometPositions[i] + 1) % localSize; + } else { + cometPositions[i] = (cometPositions[i] - 1 + localSize) % localSize; + } + + // Draw comet with solid color (safe modulo) + color = colorPack.col[i % colorPack.size]; + for (int j = 0; j < cometSize; j++) { + // Tail follows the direction of movement + pos = direction ? (cometPositions[i] - j) : (cometPositions[i] + j); + pos = (pos % localSize + localSize) % localSize; // safe modulus + leds[pos] += color; + } + } + + // Speed ramping: 1/3 aggressive ramp up, 1/3 constant, 1/3 aggressive ramp down + if (loopCounter < thirdLoop) { + // First third: ease-out quartic implemented without pow() + float progress = (float)loopCounter / (float)thirdLoop; // 0.0 to 1.0 + float oneMinus = 1.0f - progress; + float eased = 1.0f - (oneMinus * oneMinus * oneMinus * oneMinus); // 1 - (1-t)^4 + speed = minSpeed + (int)((maxSpeed - minSpeed) * eased); + } + else if (loopCounter < twoThirdLoop) { + // Middle third: constant at maxSpeed + speed = maxSpeed; + } + else { + // Last third: ease-in quartic implemented without pow() + float progress = (float)(loopCounter - twoThirdLoop) / (float)thirdLoop; // 0.0 to 1.0 + if (progress < 0.0f) progress = 0.0f; if (progress > 1.0f) progress = 1.0f; + float eased = (progress * progress * progress * progress); // t^4 + speed = maxSpeed - (int)((maxSpeed - minSpeed) * eased); + } + + if (++loopCounter >= loopDuration) { + direction = !direction; + loopCounter = 0; + speed = minSpeed; + } + + FastLED.show(); + + // Compute delay to return to Animation_Loop_Variable. + // Ensure we never return a value less than MinLoopDelay. + int computed = MaxSpeed - speed; + if (computed < MinLoopDelay) computed = MinLoopDelay; + return computed; + }); + + fill_solid(leds, size, CRGB::Black); + + } 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"); + } + + // NOTE: For production/stability consider: + // - Replacing volatile activeFlag with std::atomic or task-notify mechanism. + // - If FastLED.show() blocks too long for very long strips consider chunked updates or an alternate driver. + // - If pow() usage was widespread, replace with fixed-point or inline multiplies as done above. +} void Anim_TimedFill(bool volatile& activeFlag, CRGB* leds, int size, CRGB baseCol, CRGB fillCol, int totalDurationMs, int shift = 0) @@ -698,6 +1008,8 @@ void Anim_TimedFill_Flash(bool volatile& activeFlag, CRGB* leds, int size, PWM_O if (flashElapsed >= flashTimeout) { // Flash timeout expired, set PWM to minimum pwmOut->setOutput(pwmMin); + // exit the loop + activeFlag = false; ESP_LOGI("Anim_TimedFill_Flash", "Flash timeout expired, PWM set to minimum"); } // Continue in infinite loop regardless of timeout status @@ -754,9 +1066,8 @@ void Anim_SolidWhite(bool volatile& activeFlag, CRGB* leds, PWM_Output* pwmOut, // No animation, just maintain the solid white state return 0; }); - if(pwmOut){ - pwmOut->setOutput(0); // Turn off PWM output - } + + if(pwmOut){ pwmOut->setOutput(0); }// Turn off PWM output FastLED.setBrightness(origBright); // Restore original brightness } diff --git a/src/AppUpgrade.cpp b/src/AppUpgrade.cpp index c6b6f28..ba10eac 100644 --- a/src/AppUpgrade.cpp +++ b/src/AppUpgrade.cpp @@ -32,6 +32,7 @@ AppUpdater::AppUpdater(fs::FS& fs, Version localVersion, const char* bucket, con if (buffer_size < 1024) buffer_size = 1024; // Absolute minimum is 1KB downloadBuffer.reset(new uint8_t[buffer_size]); + downloadBufferSize = buffer_size; baseUrl = bucket ? String(bucket) : String(DEFAULT_MANIFEST_URL); // Ensure baseUrl ends with a single '/' @@ -81,6 +82,7 @@ AppUpdater::ManifestCheckResult AppUpdater::checkManifest() { http.end(); if(attempt+1 < HTTP_RETRY_COUNT) vTaskDelay(pdMS_TO_TICKS(HTTP_RETRY_DELAY_MS)); } + if(payload.isEmpty()){ ESP_LOGE(TAG, "Failed to fetch manifest after retries"); return ManifestCheckResult::ERROR_FETCH_FAILED; @@ -159,6 +161,7 @@ bool AppUpdater::updateFile(const char* remotePath, const char* localPath, const } else { ESP_LOGI(TAG, "Local file does not exist: %s", localPath); } + if(skip){ ESP_LOGI(TAG, "File already up to date: %s", localPath); updateProgress(UpdateStatus::FILE_SKIPPED, 100, localPath); @@ -179,9 +182,12 @@ bool AppUpdater::updateFile(const char* remotePath, const char* localPath, const http.end(); if(attempt+1 < HTTP_RETRY_COUNT) vTaskDelay(pdMS_TO_TICKS(HTTP_RETRY_DELAY_MS)); } + if (httpCode != HTTP_CODE_OK) { ESP_LOGE(TAG, "Download failed for %s: HTTP code %d", localPath, httpCode); - updateProgress(UpdateStatus::ERROR, 0, String(String("Download failed: ") + localPath).c_str()); + char buf[128]; + snprintf(buf, sizeof(buf), "Download failed: %s", localPath); + updateProgress(UpdateStatus::ERROR, 0, buf); http.end(); return false; } @@ -215,33 +221,36 @@ bool AppUpdater::updateFile(const char* remotePath, const char* localPath, const bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, const char* localPath, const char* remotePath, const char* expectedMd5) { const int MAX_RETRIES = 2; // Maximum number of retries for MD5 failure - + for (int retry = 0; retry <= MAX_RETRIES; retry++) { + HTTPClient retryHttp; // kept alive for the duration of this iteration if used + WiFiClient* activeStream = stream; + size_t activeContentLength = contentLength; + if (retry > 0) { ESP_LOGW(TAG, "Retrying download of %s (attempt %d/%d)", localPath, retry, MAX_RETRIES); // Need to re-fetch the file for retry - use the REMOTE path, not local path String url = buildUrl(remotePath); - HTTPClient http; - http.begin(url); - int httpCode = http.GET(); + retryHttp.begin(url); + int httpCode = retryHttp.GET(); if (httpCode != HTTP_CODE_OK) { ESP_LOGE(TAG, "Retry download failed: %d", httpCode); - http.end(); + retryHttp.end(); continue; // Try next retry if available } - stream = http.getStreamPtr(); - contentLength = http.getSize(); + activeStream = retryHttp.getStreamPtr(); + activeContentLength = retryHttp.getSize(); } - + MD5Builder md5; md5.begin(); size_t totalRead = 0; - + // Create temporary filename in the same directory as the target file String targetDir = String(localPath); int lastSlash = targetDir.lastIndexOf('/'); String tempPath; - + if (lastSlash >= 0) { // Extract directory and filename String directory = targetDir.substring(0, lastSlash + 1); @@ -251,62 +260,84 @@ bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, con // File is in root directory tempPath = "/temp_" + String(localPath) + ".download"; } - + // Clean up any existing temp file first if (fileSystem.exists(tempPath.c_str())) { ESP_LOGW(TAG, "Removing existing temp file: %s", tempPath.c_str()); fileSystem.remove(tempPath.c_str()); } - + ESP_LOGI(TAG, "Using temp file path: %s for target: %s", tempPath.c_str(), localPath); - + ESP_LOGI(TAG, "verifyAndSaveFile: downloadBufferSize=%u", (unsigned)downloadBufferSize); + + // Check available space if content length is known + if (activeContentLength > 0) { + size_t freeBytes = LittleFS.totalBytes() - LittleFS.usedBytes(); + // Leave a small headroom (5%) to avoid filling filesystem completely + if (activeContentLength > freeBytes * 95 / 100) { + ESP_LOGE(TAG, "Not enough LittleFS space for %s (%u bytes needed, %u bytes free)", + localPath, (unsigned)activeContentLength, (unsigned)freeBytes); + if (retry > 0) retryHttp.end(); + continue; + } + } + // Open temporary file for writing (LittleFS will create directories automatically) File file = fileSystem.open(tempPath.c_str(), FILE_WRITE, true); // true = create if not exists if (!file) { ESP_LOGE(TAG, "Failed to open temporary file for writing: %s", tempPath.c_str()); - + // Try to diagnose the issue - ESP_LOGE(TAG, "LittleFS info - Used: %u bytes, Total: %u bytes", - LittleFS.usedBytes(), LittleFS.totalBytes()); - + ESP_LOGE(TAG, "LittleFS info - Used: %u bytes, Total: %u bytes", + (unsigned)LittleFS.usedBytes(), (unsigned)LittleFS.totalBytes()); + // Check if we're out of space if (LittleFS.usedBytes() >= LittleFS.totalBytes() * 0.95) { ESP_LOGE(TAG, "LittleFS nearly full - may not have space for temp file"); } - + + if (retry > 0) retryHttp.end(); return false; } - - //updateProgress(UpdateStatus::DOWNLOADING, 0, localPath); - - if (contentLength > 0) { + + // Defensive: ensure download buffer exists + if (!downloadBuffer || downloadBufferSize == 0) { + ESP_LOGE(TAG, "No download buffer available"); + file.close(); + fileSystem.remove(tempPath.c_str()); + if (retry > 0) retryHttp.end(); + return false; + } + + if (activeContentLength > 0) { // Single pass with known content length - while (totalRead < contentLength) { - if(g_UpdateCancelFlag){ file.close(); fileSystem.remove(tempPath.c_str()); return false; } - size_t available = stream->available(); + while (totalRead < activeContentLength) { + if(g_UpdateCancelFlag){ file.close(); fileSystem.remove(tempPath.c_str()); if (retry > 0) retryHttp.end(); return false; } + size_t available = activeStream->available(); if (available) { - size_t readLen = stream->readBytes(downloadBuffer.get(), std::min(available, size_t(BUFFER_SIZE))); - + size_t readLen = activeStream->readBytes(downloadBuffer.get(), std::min(available, downloadBufferSize)); + // 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()); + if (retry > 0) retryHttp.end(); return false; } - + md5.add(downloadBuffer.get(), readLen); totalRead += readLen; - updateProgress(UpdateStatus::DOWNLOADING, (totalRead * 80) / contentLength , localPath); + updateProgress(UpdateStatus::DOWNLOADING, (totalRead * 80) / activeContentLength , localPath); } yield(); } } else { // Unknown content length: read until stream ends for (;;) { - if(g_UpdateCancelFlag){ file.close(); fileSystem.remove(tempPath.c_str()); return false; } - size_t readLen = stream->readBytes(downloadBuffer.get(), BUFFER_SIZE); + if(g_UpdateCancelFlag){ file.close(); fileSystem.remove(tempPath.c_str()); if (retry > 0) retryHttp.end(); return false; } + size_t readLen = activeStream->readBytes(downloadBuffer.get(), downloadBufferSize); if (readLen == 0) { break; } @@ -314,6 +345,7 @@ bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, con ESP_LOGE(TAG, "Failed to write to temporary file"); file.close(); fileSystem.remove(tempPath.c_str()); + if (retry > 0) retryHttp.end(); return false; } md5.add(downloadBuffer.get(), readLen); @@ -326,44 +358,45 @@ bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, con yield(); } } - + file.close(); md5.calculate(); String calculatedMd5 = md5.toString(); - + // Verify MD5 hash updateProgress(UpdateStatus::VERIFYING, 90, localPath); - - ESP_LOGI(TAG, "MD5 verification for %s: Expected='%s', Calculated='%s'", + + ESP_LOGI(TAG, "MD5 verification for %s: Expected='%s', Calculated='%s'", localPath, expectedMd5, calculatedMd5.c_str()); - + // Compare MD5 case-insensitively (in case there are case differences) String expectedMd5Lower = String(expectedMd5); expectedMd5Lower.toLowerCase(); String calculatedMd5Lower = calculatedMd5; calculatedMd5Lower.toLowerCase(); - + if (!calculatedMd5Lower.equals(expectedMd5Lower)) { - ESP_LOGE(TAG, "MD5 mismatch for %s (attempt %d/%d). Expected: %s, Got: %s", + ESP_LOGE(TAG, "MD5 mismatch for %s (attempt %d/%d). Expected: %s, Got: %s", localPath, retry+1, MAX_RETRIES+1, expectedMd5, calculatedMd5.c_str()); - ESP_LOGE(TAG, "Length comparison - Expected: %d chars, Got: %d chars", - strlen(expectedMd5), calculatedMd5.length()); + ESP_LOGE(TAG, "Length comparison - Expected: %d chars, Got: %d chars", + (int)strlen(expectedMd5), (int)calculatedMd5.length()); fileSystem.remove(tempPath.c_str()); - + if (retry < MAX_RETRIES) { // Will retry in next loop iteration + if (retry > 0) retryHttp.end(); continue; } - + // Special case for certain file types - allow them to be used even with MD5 mismatch // This is a fallback option for non-critical files like HTML pages bool isNonCriticalFile = false; - if (String(localPath).endsWith(".html") || - String(localPath).endsWith(".css") || + if (String(localPath).endsWith(".html") || + String(localPath).endsWith(".css") || String(localPath).endsWith(".js")) { isNonCriticalFile = true; } - + if (isNonCriticalFile) { ESP_LOGW(TAG, "Using file %s despite MD5 mismatch (non-critical file)", localPath); // We'll still keep this file but report it as a verification failure @@ -383,26 +416,29 @@ bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, con } } } - + // Rename the temp file to the final location if (fileSystem.exists(localPath)) { fileSystem.remove(localPath); } if (!fileSystem.rename(tempPath.c_str(), localPath)) { - ESP_LOGE(TAG, "Failed to rename temporary file for non-critical use: %s -> %s", + ESP_LOGE(TAG, "Failed to rename temporary file for non-critical use: %s -> %s", tempPath.c_str(), localPath); fileSystem.remove(tempPath.c_str()); + if (retry > 0) retryHttp.end(); return false; } + if (retry > 0) retryHttp.end(); // Return false to indicate verification failure, but the file will still be used return false; } - + + if (retry > 0) retryHttp.end(); return false; } - + updateProgress(UpdateStatus::VERIFYING, 95, localPath); - + // Ensure target directory exists before rename String dirPath = String(localPath); int targetLastSlash = dirPath.lastIndexOf('/'); @@ -419,7 +455,7 @@ bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, con } } } - + // Replace original file with verified temp file if (fileSystem.exists(localPath)) { fileSystem.remove(localPath); @@ -427,13 +463,15 @@ bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, con if (!fileSystem.rename(tempPath.c_str(), localPath)) { ESP_LOGE(TAG, "Failed to rename temporary file: %s -> %s", tempPath.c_str(), localPath); fileSystem.remove(tempPath.c_str()); + if (retry > 0) retryHttp.end(); return false; } - + updateProgress(UpdateStatus::VERIFYING, 100, localPath); + if (retry > 0) retryHttp.end(); return true; } - + return false; // All retries failed } @@ -450,7 +488,7 @@ String AppUpdater::getLocalMD5(const char* filePath){ 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))); + readLen = file.readBytes(reinterpret_cast(downloadBuffer.get()), std::min(fileSize - totalRead, downloadBufferSize)); md5Builder.add(downloadBuffer.get(), readLen); totalRead += readLen; } @@ -569,6 +607,7 @@ bool AppUpdater::updateApp() { 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; } @@ -662,12 +701,12 @@ bool AppUpdater::updateApp() { while (remaining > 0 && failedReads < MAX_FAILED_READS) { // Check for cancellation - if(g_UpdateCancelFlag) { - ESP_LOGE(TAG, "Update cancelled by user"); - Update.abort(); - http.end(); - return false; - } + if(g_UpdateCancelFlag) { + ESP_LOGE(TAG, "Update cancelled by user"); + Update.abort(); + http.end(); + return false; + } // Check WiFi status every 5 seconds if (millis() - lastWatchdogKick > 5000) { @@ -735,8 +774,9 @@ bool AppUpdater::updateApp() { // Send periodic progress updates to keep client informed if (millis() - lastWatchdogKick > 5000) { int percent = (totalReceived * 100) / firmwareSize; - updateProgress(UpdateStatus::DOWNLOADING, percent, - String("Firmware: " + String(percent) + "% - waiting for data...").c_str()); + char buf[64]; + snprintf(buf, sizeof(buf), "Firmware: %d%% - waiting for data...", percent); + updateProgress(UpdateStatus::DOWNLOADING, percent, buf); } delay(100); // Short delay to prevent CPU hogging @@ -782,8 +822,9 @@ bool AppUpdater::updateApp() { lastProgressTime = millis(); // Just update with received byte count since we don't know total - updateProgress(UpdateStatus::DOWNLOADING, 0, - String("Firmware: " + String(totalReceived / 1024) + "KB received").c_str()); + char buf2[64]; + snprintf(buf2, sizeof(buf2), "Firmware: %uKB received", (unsigned)(totalReceived / 1024)); + updateProgress(UpdateStatus::DOWNLOADING, 0, buf2); } else { emptyReads++; delay(100); @@ -878,115 +919,122 @@ void firmwareUpdateTask(void* parameter) { esp_task_wdt_init(60, true); // 60 second timeout, panic on timeout esp_task_wdt_add(NULL); // Add current task to watchdog - try { - loadUpdateJson(); - - esp_task_wdt_reset(); // Reset watchdog timer after JSON loading - - // Initialize updater with smart pointer - std::unique_ptr updater(new AppUpdater( - LittleFS, localVersion, updateUrl.c_str(), "manifest.json", "firmware.bin")); - updater->setProgressCallback(updateProgress); - - ESP_LOGI(TAG, "Starting update check from: %s", updateUrl.c_str()); - - // Check and perform updates - auto manifestResult = updater->checkManifest(); - - if (manifestResult != AppUpdater::ManifestCheckResult::UPDATE_AVAILABLE) { - // Handle different error cases - std::string errorMsg; - switch (manifestResult) { - case AppUpdater::ManifestCheckResult::ERROR_FETCH_FAILED: - errorMsg = "Failed to fetch manifest"; - break; - case AppUpdater::ManifestCheckResult::ERROR_TOO_LARGE: - errorMsg = "Manifest file too large"; - break; - case AppUpdater::ManifestCheckResult::ERROR_PARSE_FAILED: - errorMsg = "Failed to parse manifest"; - break; - case AppUpdater::ManifestCheckResult::ERROR_NO_FILES_SECTION: - errorMsg = "Manifest missing files section"; - break; - case AppUpdater::ManifestCheckResult::ERROR_NO_VERSION: - errorMsg = "Manifest missing version section"; - break; - case AppUpdater::ManifestCheckResult::VERSION_CURRENT: - errorMsg = "Current version is up to date"; - // This is not actually an error - ESP_LOGI(TAG, "No update needed: %s", errorMsg.c_str()); - updateProgress(AppUpdater::UpdateStatus::MESSAGE, 0, errorMsg.c_str()); - // Don't throw, just exit gracefully - break; - default: - errorMsg = "Unknown manifest check error"; - } - - if (manifestResult != AppUpdater::ManifestCheckResult::VERSION_CURRENT) { - ESP_LOGE(TAG, "Manifest check failed: %s", errorMsg.c_str()); - updateProgress(AppUpdater::UpdateStatus::ERROR, 0, errorMsg.c_str()); - } - } - - if (updater->IsUpdateAvailable()) { - bool filesUpdated = true; - bool firmwareUpdated = false; // Initialize to false - only set to true if firmware is actually updated - - // Update files based on update mode - if (g_UpdateMode == UpdateMode::UPDATE_FILES_ONLY || g_UpdateMode == UpdateMode::UPDATE_BOTH) { - ESP_LOGI(TAG, "Update mode includes files, updating files..."); - filesUpdated = updater->updateFilesArray(); - if (!filesUpdated) { - ESP_LOGW(TAG, "Some files failed to update"); - if (g_UpdateMode == UpdateMode::UPDATE_FILES_ONLY) { - ESP_LOGE(TAG, "Files-only update failed"); - updateProgress(AppUpdater::UpdateStatus::ERROR, 0, "Failed to update files"); - // Skip to cleanup since this is files-only mode and it failed - goto cleanup; - } else { - ESP_LOGW(TAG, "File update failed, but continuing with firmware update"); - } - } - } else { - ESP_LOGI(TAG, "Skipping file updates (mode: firmware only)"); - } - - // Update firmware based on update mode - if (g_UpdateMode == UpdateMode::UPDATE_FIRMWARE_ONLY || g_UpdateMode == UpdateMode::UPDATE_BOTH) { - ESP_LOGI(TAG, "Update mode includes firmware, updating firmware..."); - firmwareUpdated = updater->updateApp(); - if (!firmwareUpdated) { - ESP_LOGE(TAG, "Failed to update firmware"); - updateProgress(AppUpdater::UpdateStatus::ERROR, 0, "Failed to update firmware"); - // Skip to cleanup since firmware update failed - goto cleanup; - } - } else { - ESP_LOGI(TAG, "Skipping firmware update (mode: files only)"); - } - - // Determine if we need to restart - bool needsRestart = (g_UpdateMode == UpdateMode::UPDATE_FIRMWARE_ONLY || g_UpdateMode == UpdateMode::UPDATE_BOTH) && firmwareUpdated; - - if (needsRestart) { - ESP_LOGI(TAG, "Firmware update successful, restarting..."); - sendUpdateMessage("Restarting... ", true, 100); - vTaskDelay(2000); - ESP.restart(); - } else { - ESP_LOGI(TAG, "Update completed successfully (no restart required)"); - updateProgress(AppUpdater::UpdateStatus::COMPLETE, 100, "Update completed successfully"); - } - } - - } catch (const std::exception& e) { - ESP_LOGE(TAG, "Update failed with exception: %s", e.what()); - updateProgress(AppUpdater::UpdateStatus::ERROR, 0, e.what()); - } catch (...) { - ESP_LOGE(TAG, "Update failed with unknown exception"); - updateProgress(AppUpdater::UpdateStatus::ERROR, 0, "Unknown error during update"); + // Load update.json; proceed only if successful + if (!loadUpdateJson()) { + ESP_LOGE(TAG, "Failed to load update.json, aborting update task"); + // Clean up watchdog and exit task + esp_task_wdt_delete(NULL); + Update_Task_Handle = NULL; + vTaskDelete(NULL); + return; } + + esp_task_wdt_reset(); // Reset watchdog timer after JSON loading + + // Initialize updater with smart pointer + std::unique_ptr updater(new AppUpdater( + LittleFS, localVersion, updateUrl.c_str(), "manifest.json", "firmware.bin")); + updater->setProgressCallback(updateProgress); + + ESP_LOGI(TAG, "Starting update check from: %s", updateUrl.c_str()); + + // Check and perform updates + auto manifestResult = updater->checkManifest(); + + if (manifestResult != AppUpdater::ManifestCheckResult::UPDATE_AVAILABLE) { + // Handle different error cases + std::string errorMsg; + switch (manifestResult) { + case AppUpdater::ManifestCheckResult::ERROR_FETCH_FAILED: + errorMsg = "Failed to fetch manifest"; + break; + case AppUpdater::ManifestCheckResult::ERROR_TOO_LARGE: + errorMsg = "Manifest file too large"; + break; + case AppUpdater::ManifestCheckResult::ERROR_PARSE_FAILED: + errorMsg = "Failed to parse manifest"; + break; + case AppUpdater::ManifestCheckResult::ERROR_NO_FILES_SECTION: + errorMsg = "Manifest missing files section"; + break; + case AppUpdater::ManifestCheckResult::ERROR_NO_VERSION: + errorMsg = "Manifest missing version section"; + break; + case AppUpdater::ManifestCheckResult::VERSION_CURRENT: + errorMsg = "Current version is up to date"; + // This is not actually an error + ESP_LOGI(TAG, "No update needed: %s", errorMsg.c_str()); + updateProgress(AppUpdater::UpdateStatus::MESSAGE, 0, errorMsg.c_str()); + // Don't throw, just exit gracefully + break; + default: + errorMsg = "Unknown manifest check error"; + } + + if (manifestResult != AppUpdater::ManifestCheckResult::VERSION_CURRENT) { + ESP_LOGE(TAG, "Manifest check failed: %s", errorMsg.c_str()); + updateProgress(AppUpdater::UpdateStatus::ERROR, 0, errorMsg.c_str()); + } + } + + if (updater->IsUpdateAvailable()) { + bool filesUpdated = true; + bool firmwareUpdated = false; // Initialize to false - only set to true if firmware is actually updated + + // Update files based on update mode + if (g_UpdateMode == UpdateMode::UPDATE_FILES_ONLY || g_UpdateMode == UpdateMode::UPDATE_BOTH) { + ESP_LOGI(TAG, "Update mode includes files, updating files..."); + filesUpdated = updater->updateFilesArray(); + if (!filesUpdated) { + ESP_LOGW(TAG, "Some files failed to update"); + if (g_UpdateMode == UpdateMode::UPDATE_FILES_ONLY) { + ESP_LOGE(TAG, "Files-only update failed"); + updateProgress(AppUpdater::UpdateStatus::ERROR, 0, "Failed to update files"); + // Clean up and exit task + esp_task_wdt_delete(NULL); + Update_Task_Handle = NULL; + vTaskDelete(NULL); + return; + } else { + ESP_LOGW(TAG, "File update failed, but continuing with firmware update"); + } + } + } else { + ESP_LOGI(TAG, "Skipping file updates (mode: firmware only)"); + } + + // Update firmware based on update mode + if (g_UpdateMode == UpdateMode::UPDATE_FIRMWARE_ONLY || g_UpdateMode == UpdateMode::UPDATE_BOTH) { + ESP_LOGI(TAG, "Update mode includes firmware, updating firmware..."); + firmwareUpdated = updater->updateApp(); + if (!firmwareUpdated) { + ESP_LOGE(TAG, "Failed to update firmware"); + updateProgress(AppUpdater::UpdateStatus::ERROR, 0, "Failed to update firmware"); + // Clean up and exit task + esp_task_wdt_delete(NULL); + Update_Task_Handle = NULL; + vTaskDelete(NULL); + return; + } + } else { + ESP_LOGI(TAG, "Skipping firmware update (mode: files only)"); + } + + // Determine if we need to restart + bool needsRestart = (g_UpdateMode == UpdateMode::UPDATE_FIRMWARE_ONLY || g_UpdateMode == UpdateMode::UPDATE_BOTH) && firmwareUpdated; + + if (needsRestart) { + ESP_LOGI(TAG, "Firmware update successful, restarting..."); + sendUpdateMessage("Restarting... ", true, 100); + vTaskDelay(2000); + ESP.restart(); + } else { + ESP_LOGI(TAG, "Update completed successfully (no restart required)"); + updateProgress(AppUpdater::UpdateStatus::COMPLETE, 100, "Update completed successfully"); + } + } + + // No C++ exceptions used - errors are handled inline and via return codes cleanup: // Clean up watchdog before exit @@ -1007,7 +1055,12 @@ void startVersionCheckTask() { void versionCheckTask(void* parameter){ if(updateUrl == ""){ - loadUpdateJson(); + if(!loadUpdateJson()){ + ESP_LOGE(TAG, "versionCheckTask: failed to load update.json"); + versionCheckTask_Handle = NULL; + vTaskDelete(NULL); + return; + } } AppUpdater updater(LittleFS, localVersion, updateUrl.c_str(), "manifest.json", "firmware.bin"); @@ -1025,35 +1078,36 @@ void versionCheckTask(void* parameter){ vTaskDelete(NULL); } -void loadUpdateJson(void) { - try { - ESP_LOGD(TAG, "loadUpdateJaon function..."); - if(updateUrl == "") { - String updateJsonPath = "/system/update.json"; +bool loadUpdateJson(void) { + ESP_LOGD(TAG, "loadUpdateJson 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 folderName = jsonConstrainString(TAG, jObj, "folder", "latest/"); - String baseUrl = jsonConstrainString(TAG, jObj, "baseurl", "https://s3-minio.boothwizard.com/boothifier/"); - updateUrl = baseUrl + folderName; - - ESP_LOGD(TAG, "updateUrl: %s", updateUrl.c_str()); + // Read and parse update.json + File file = LittleFS.open(updateJsonPath); + if (!file) { + ESP_LOGE(TAG, "Failed to open update.json"); + return false; } - } catch (const std::exception& e) { - ESP_LOGE(TAG, "Update failed: %s", e.what()); + + JsonDocument doc; + DeserializationError error = deserializeJson(doc, file); + file.close(); + + if (error) { + ESP_LOGE(TAG, "Failed to parse update.json: %s", error.c_str()); + return false; + } + + // Get update configuration + JsonObject jObj = doc.as(); + String folderName = jsonConstrainString(TAG, jObj, "folder", "latest/"); + String baseUrl = jsonConstrainString(TAG, jObj, "baseurl", "https://s3-minio.boothwizard.com/boothifier/"); + updateUrl = baseUrl + folderName; + + ESP_LOGD(TAG, "updateUrl: %s", updateUrl.c_str()); } + return true; } void updateProgress(AppUpdater::UpdateStatus newStatus, int percentage, const char* message = nullptr) { diff --git a/src/BLE_SP110E.cpp b/src/BLE_SP110E.cpp index 15ed1b0..f6d5b04 100644 --- a/src/BLE_SP110E.cpp +++ b/src/BLE_SP110E.cpp @@ -5,6 +5,9 @@ #include "ATALights.h" #include "BleSettings.h" #include +#include "my_device.h" +#include "global.h" // for get_chip_mac +#include "esp_log.h" static const char *tag = "BLE_SP110E"; @@ -33,7 +36,6 @@ 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; @@ -133,6 +135,78 @@ public: process_BLE_SP110E_Command(data, procLen, pCharacteristic); } + // Respond to read requests by returning the current led_status structure + void onRead(NimBLECharacteristic* pCharacteristic) override { + if (!pCharacteristic) return; + + USER_SETTINGS userSettings = {}; + + userSettings.cmd = 0; // Indicate this is a status response + userSettings.limitedMode = sys_settings.limitedMode ? 1 : 0; + userSettings.profile[9] = '\0'; // Ensure null-terminated + { + // sys_settings.profile is an Arduino String; use c_str() to obtain a const char* + const char* src = sys_settings.profile.c_str(); + // copy up to size-1 characters to ensure null-termination + size_t maxCopy = sizeof(userSettings.profile) - 1; + size_t srcLen = strlen(src); + size_t copyLen = srcLen < maxCopy ? srcLen : maxCopy; + if (copyLen) memcpy(userSettings.profile, src, copyLen); + // null terminate right after copied data + userSettings.profile[copyLen] = '\0'; + // ensure the remaining bytes are zeroed out + for (size_t i = copyLen + 1; i < sizeof(userSettings.profile); ++i) userSettings.profile[i] = '\0'; + } + + userSettings.temperature = boardTemperature; // Placeholder, implement actual temperature reading if needed + userSettings.vIn = PowerVin; // Placeholder, implement actual voltage reading if needed + + // Determine chipset with case‐insensitive substring match + std::string chip = std::string(sys_settings.ledStripSettings[0]->chip.c_str()); + std::string chipLower; + chipLower.resize(chip.size()); + std::transform(chip.begin(), chip.end(), chipLower.begin(), [](unsigned char c){ return std::tolower(c); }); + if (chipLower.find("WS2812b") != std::string::npos) userSettings.ledChipset1 = 0; + else if (chipLower.find("SK6812") != std::string::npos) userSettings.ledChipset1 = 1; + else if (chipLower.find("WS2811") != std::string::npos) userSettings.ledChipset1 = 2; + else if (chipLower.find("WS2815") != std::string::npos) userSettings.ledChipset1 = 3; + else userSettings.ledChipset1 = 0; + + + strncpy(userSettings.rgbOrder1, sys_settings.ledStripSettings[0]->rgbOrder.c_str(), 4); + userSettings.ledCount1 = sys_settings.ledStripSettings[0]->size; + userSettings.ledShift1 = sys_settings.ledStripSettings[0]->shift; + userSettings.ledBrightness1= sys_settings.ledStripSettings[0]->bright; + + std::string chip2 = std::string(sys_settings.ledStripSettings[1]->chip.c_str()); + std::string chipLower2; + chipLower2.resize(chip2.size()); + std::transform(chip2.begin(), chip2.end(), chipLower2.begin(), + [](unsigned char c){ return std::tolower(c); }); + if (chipLower2.find("WS2812b") != std::string::npos) userSettings.ledChipset2 = 0; + else if (chipLower2.find("SK6812") != std::string::npos) userSettings.ledChipset2 = 1; + else if (chipLower2.find("WS2811") != std::string::npos) userSettings.ledChipset2 = 2; + else if (chipLower2.find("WS2815") != std::string::npos) userSettings.ledChipset2 = 3; + else userSettings.ledChipset2 = 0; + + strncpy(userSettings.rgbOrder2, sys_settings.ledStripSettings[1]->rgbOrder.c_str(), 4); + userSettings.ledCount2 = sys_settings.ledStripSettings[1]->size; + userSettings.ledShift2 = sys_settings.ledStripSettings[1]->shift; + userSettings.ledBrightness2= sys_settings.ledStripSettings[1]->bright; + + userSettings.frontLightMin = sys_settings.rampLightSettings[0].min; + userSettings.frontLightMax = sys_settings.rampLightSettings[0].max; + userSettings.rearLightMin = sys_settings.rampLightSettings[1].min; + userSettings.rearLightMax = sys_settings.rampLightSettings[1].max; + userSettings.fanLowerTemp = sys_settings.tSensorSettings.setpoint1; + userSettings.fanUpperTemp = sys_settings.tSensorSettings.setpoint2; + + // Provide the full INFO_PACK as the characteristic value so readers get device info + pCharacteristic->setValue((uint8_t *)&userSettings, sizeof(USER_SETTINGS)); + // Do not automatically notify on read (notify is for subscriptions), but keep a log + ESP_LOGD(tag, "onRead: returning INFO_PACK (%zu bytes)", sizeof(INFO_PACK)); + } + private: static void logBytes(const uint8_t* data, size_t len) { if (!data) return; @@ -222,20 +296,24 @@ void sendToAllClients(const uint8_t *data, size_t len) { void process_BLE_SP110E_Command(const uint8_t* val, uint8_t len, NimBLECharacteristic* bleChar) { - if (!val) { - ESP_LOGE(tag, "Null command data received"); - return; - } - - if (len < 4) { - ESP_LOGW(tag, "Command too short: %d bytes, expected at least 4", len); - return; + //uint8_t response[sizeof(INFO_PACK)]; // Use a single response buffer + + if (!val) { ESP_LOGE(tag, "Null command data received"); return;} + if (len < 4) { ESP_LOGW(tag, "Command too short: %d bytes, expected at least 4", len); return; } + + //ESP_LOGI(tag, "USER_SETTING size is: %d", sizeof(USER_SETTINGS)); + + uint8_t command = 0; + if (len == 4){ + command = val[3]; + }else if (len == sizeof(USER_SETTINGS)){ + command = val[0]; + //ESP_LOGI(tag, "Command received: 0x%02X, length: %d", command, len); + //ESP_LOGI(tag, "byte1: %d byte2: %d byte3: %d byte4: %d byte5: %d byte6: %d byte7: %d byte8: %d", + // val[1], val[2], val[3], val[4], val[5], val[6], val[7], val[8]); } - uint8_t command = val[3]; - //ESP_LOGI(tag, "Command received: 0x%02X, length: %d", command, len); - - uint8_t response[sizeof(INFO_PACK)]; // Use a single response buffer + ESP_LOGI(tag, "Command received: 0x%02X, data0: %d, data1: %d, data2: %d, length: %d", command, val[0], val[1], val[2], len); // Handle different commands switch (command) { @@ -273,7 +351,7 @@ void process_BLE_SP110E_Command(const uint8_t* val, uint8_t len, NimBLECharacter break; case SET_SPEED: led_status.speed = val[0]; - ESP_LOGI(tag, "Mode set to %d", led_status.speed); + ESP_LOGI(tag, "Speed set to %d", led_status.speed); break; case GET_CHECK_DEVICE: // This prepends a checksum led_status.checksum = calculateChecksum(val); @@ -307,9 +385,42 @@ void process_BLE_SP110E_Command(const uint8_t* val, uint8_t len, NimBLECharacter case SET_DEVICE_NAME: ESP_LOGI(tag, "Set Device Name"); break; + case SET_SHIFT: + RGB_Lights_Set_Animation(SHIFT_INDEX, val[0], val[1], val[2]); + ESP_LOGI(tag, "Set Shift to %d", val[0]); + break; + case SET_USER_SETTINGS:{ + USER_SETTINGS userSettings; + + if (len < sizeof(USER_SETTINGS)) { + ESP_LOGW(tag, "SET_USER_SETTINGS command too short: %d bytes, expected at least %d", len, sizeof(USER_SETTINGS)); + break; + } + memcpy(&userSettings, &val[0], sizeof(USER_SETTINGS) ); + + // Print out all the received settings for debugging + /* + ESP_LOGI(tag, "Received User Settings:"); + ESP_LOGI(tag, " Limited Mode: %d", userSettings.limitedMode); + + ESP_LOGI(tag, " LED Chip Type: %d", userSettings.ledChipset1); + ESP_LOGI(tag, " LED Color Order: %s", userSettings.rgbOrder1); + ESP_LOGI(tag, " LED Count: %d", userSettings.ledCount1); + ESP_LOGI(tag, " LED Shift: %d", userSettings.ledShift1); + ESP_LOGI(tag, " LED Brightness: %d", userSettings.ledBrightness1); + + ESP_LOGI(tag, " Fan Lower Temp: %d", userSettings.fanLowerTemp); + ESP_LOGI(tag, " Fan Upper Temp: %d", userSettings.fanUpperTemp); + */ + + SetAndSaveUserSettings(userSettings); + + } + break; + default: ESP_LOGW(tag, "Unknown command: 0x%02X", command); - break; + break; } } @@ -331,7 +442,7 @@ void Init_BLE_SP110E(NimBLEServer* pServer) { // Create FFE1 Characteristic with WRITE and NOTIFY properties pSP110ECharacteristic = pService->createCharacteristic( BTSP110ECharacteristicUUID.c_str(), - NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY + NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY ); // Register the callback with the characteristic @@ -422,11 +533,8 @@ void BLE_LightStick_Client_Task(void *parameter) { // Get the characteristic. pRemoteCharacteristic = pRemoteService->getCharacteristic(BTStickCharacteristicUUID.c_str()); 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(); + pRemoteCharacteristic->subscribe(true, [](NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) { + ESP_LOGD(tag, "Notification received: %zu bytes", length); process_BLE_SP110E_Command(pData, length, nullptr); }); } else { @@ -483,7 +591,7 @@ class MyAdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks { NimBLEDevice::getScan()->stop(); myDevice = advertisedDevice; masterFound = true; - Serial.println("Device found!"); + ESP_LOGE(tag, "Device found!"); } } }; \ No newline at end of file diff --git a/src/BleServer.cpp b/src/BleServer.cpp index 76fb565..0165181 100644 --- a/src/BleServer.cpp +++ b/src/BleServer.cpp @@ -38,35 +38,87 @@ class ServerCallbacks : public NimBLEServerCallbacks { class ServerCallbacks : public NimBLEServerCallbacks { public: - void onConnect(NimBLEServer* /*pServer*/) override { - ESP_LOGI(tag, "Client connected"); - ensureAdvertising("onConnect"); + ServerCallbacks() : connectedClients(0) {} + + void onConnect(NimBLEServer* pServer) override { + // Use server's connected count to avoid double-handling between overloads + int count = 0; + if (pServer) count = pServer->getConnectedCount(); + connectedClients = count; + ESP_LOGI(tag, "Client connected (count=%d) [no-desc overload]", connectedClients); + if (connectedClients == 1) { + NimBLEAdvertising* adv = NimBLEDevice::getAdvertising(); + if (adv && adv->isAdvertising()) { + if (adv->stop()) ESP_LOGI(tag, "Advertising stopped after client connected"); + else ESP_LOGE(tag, "Failed to stop advertising after connect"); + } + } else if (connectedClients > 1) { + ESP_LOGW(tag, "Additional client connected while one is active (no-desc)"); + } Buzzer_Play_Tune(TUNE_CONNECTED, 1); } + // This overload provides connection descriptor details (conn handle) so we can reject extra clients. + void onConnect(NimBLEServer* pServer, ble_gap_conn_desc* desc) override { + // desc contains the connection handle for this new client + int connHandle = desc ? desc->conn_handle : -1; + // Use server's connected count to determine how many clients are attached + int count = 0; + if (pServer) count = pServer->getConnectedCount(); + connectedClients = count; + ESP_LOGI(tag, "Client connected (count=%d) conn_handle=%d", connectedClients, connHandle); + + // If this is the first client, stop advertising so no additional clients can connect + if (connectedClients == 1) { + NimBLEAdvertising* adv = NimBLEDevice::getAdvertising(); + if (adv && adv->isAdvertising()) { + if (adv->stop()) ESP_LOGI(tag, "Advertising stopped after client connected"); + else ESP_LOGE(tag, "Failed to stop advertising after connect"); + } + } else if (connectedClients > 1) { + // Reject this new connection immediately to enforce single-client policy + ESP_LOGW(tag, "Rejecting extra client conn_handle=%d (server count=%d)", connHandle, connectedClients); + if (pServer) { + try { + pServer->disconnect(connHandle); + ESP_LOGI(tag, "Disconnected extra client conn_handle=%d", connHandle); + } catch (...) { + ESP_LOGE(tag, "Exception while disconnecting extra client conn_handle=%d", connHandle); + } + } + // Update connectedClients after rejecting (get fresh count if possible) + if (pServer) connectedClients = pServer->getConnectedCount(); + } + //Buzzer_Play_Tune(TUNE_CONNECTED, 1); + } + void onDisconnect(NimBLEServer* /*pServer*/) override { - ESP_LOGI(tag, "Client disconnected"); - ensureAdvertising("onDisconnect"); + if (connectedClients > 0) connectedClients--; + ESP_LOGI(tag, "Client disconnected (count=%d)", connectedClients); + + // If there are no clients left, restart advertising so a new client can connect + if (connectedClients == 0) { + NimBLEAdvertising* adv = NimBLEDevice::getAdvertising(); + if (!adv) { + ESP_LOGE(tag, "Advertising object unavailable on disconnect"); + return; + } + if (adv->isAdvertising()) { + ESP_LOGD(tag, "Advertising already running on disconnect"); + return; + } + if (adv->start()) { + ESP_LOGI(tag, "Advertising restarted after client disconnect"); + } else { + ESP_LOGE(tag, "Failed to start advertising after disconnect"); + } + } + Buzzer_Play_Tune(TUNE_DISCONNECTED, 1); } private: - void ensureAdvertising(const char* reason) { - NimBLEAdvertising* adv = NimBLEDevice::getAdvertising(); - if (!adv) { - ESP_LOGE(tag, "[%s] Advertising object unavailable", reason); - return; - } - if (adv->isAdvertising()) { - ESP_LOGD(tag, "[%s] Advertising already running", reason); - return; - } - if (adv->start()) { - ESP_LOGI(tag, "[%s] Advertising (re)started", reason); - } else { - ESP_LOGE(tag, "[%s] Failed to start advertising", reason); - } - } + int connectedClients; }; diff --git a/src/BleSettings.cpp b/src/BleSettings.cpp index 33dfebf..3425158 100644 --- a/src/BleSettings.cpp +++ b/src/BleSettings.cpp @@ -70,8 +70,6 @@ void Load_BLE_Settings(const String &configPath) { BTDeviceName += macSuffix[0]; // Add first character BTDeviceName += macSuffix[1]; // Add second character - BTDeviceName = "SP110E"; - ESP_LOGI(tag, "Loaded BLE config: name=%s svc=%s char1=%s stick=%s upg_svc=%s upg1=%s upg2=%s", BTDeviceName.c_str(), BTServiceUUID.c_str(), BTSP110ECharacteristicUUID.c_str(), BTStickCharacteristicUUID.c_str(), BTUpgradeServiceUUID.c_str(), diff --git a/src/PWM_Output.cpp b/src/PWM_Output.cpp index c6ca700..62f4f2e 100644 --- a/src/PWM_Output.cpp +++ b/src/PWM_Output.cpp @@ -3,11 +3,7 @@ #include "global.h" -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 -}; +static const char* tag = "pwmout"; PWM_Output::PWM_Output(int8_t pin, uint8_t ch, uint8_t res, uint32_t freq, float maxDuty, bool visionCorrected){ this->currDuty = 0; @@ -15,17 +11,21 @@ PWM_Output::PWM_Output(int8_t pin, uint8_t ch, uint8_t res, uint32_t freq, float this->maxDuty = maxDuty; this->visionCorrected = visionCorrected; this->freq = freq; + this->pin = pin; 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); + // continue but mark as uninitialized to prevent usage + this->initialized = false; return; } ledcAttachPin(pin, ch); setOutput(this->currDuty); + this->initialized = true; } void PWM_Output::setMaxDuty(float duty) { @@ -41,6 +41,10 @@ void PWM_Output::setMaxDuty(float duty) { // Range is 0 to 100% void PWM_Output::setOutput(float duty){ + if(!this->initialized){ + ESP_LOGW(tag, "setOutput called on uninitialized PWM_Output (ch=%d)", this->ch); + return; + } // Clamp the duty cycle to the range [0.0, maxDuty] if (duty < 0.0) { duty = 0.0; @@ -50,15 +54,14 @@ void PWM_Output::setOutput(float duty){ // calculate correct duty value int outDutyVal; - if(this->visionCorrected){ - outDutyVal = linearizeOutput(duty); - } - else{ + if(this->visionCorrected){ + outDutyVal = linearizeOutput(duty); + } else { outDutyVal = static_cast(duty * this->standardFactor); } - // Clamp to valid resolution range [0, 2^res - 1] - int maxVal = static_cast(binaryPow[this->res]); + // Clamp to valid resolution range [0, (1<res >= 31) ? INT32_MAX : ((1 << this->res) - 1); if (outDutyVal < 0) outDutyVal = 0; if (outDutyVal > maxVal) outDutyVal = maxVal; @@ -72,8 +75,10 @@ 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; + if(newFreq > 0){ + this->freq = newFreq; + } else { + ESP_LOGW(tag, "ledcChangeFrequency failed for ch:%d requested:%u", this->ch, fq); } } } @@ -84,8 +89,9 @@ void PWM_Output::setResolution(uint8_t res){ if(this->res > 16) this->res = 16; // Use the clamped resolution when computing factors - this->standardFactor = binaryPow[this->res] * 0.01f; - this->visionFactor = binaryPow[this->res] * 0.0001f; + int maxVal = (this->res >= 31) ? INT32_MAX : ((1 << this->res) - 1); + this->standardFactor = static_cast(maxVal) * 0.01f; + this->visionFactor = static_cast(maxVal) * 0.0001f; ESP_LOGD(tag, "factor=%f, vision=%f", this->standardFactor, this->visionFactor); } diff --git a/src/Ramp_Lights.cpp b/src/Ramp_Lights.cpp index b696ce9..a91e14b 100644 --- a/src/Ramp_Lights.cpp +++ b/src/Ramp_Lights.cpp @@ -14,9 +14,12 @@ RAMP_LIGHT::RAMP_LIGHT(OneButton* button, PWM_Output* pwmOutput, float min, floa 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; + + if(min < 0.0) this->min = 0.0; + if(max > 100.0) this->max = 100.0; + if(this->max < this->min) this->max = this->min; + if(step <= 0.0) this->step = 1.0; + currentValue = this->min; IsOn = false; rampState = RampingUp; } @@ -52,11 +55,17 @@ void RAMP_LIGHT::longPressStop(){ void RAMP_LIGHT::duringLongPress(){ if(IsOn){ if (tickCount > 0 && --tickCount == 0) { - // When ramping down, currentValue may go below min if step is large; constrain ensures bounds. - // Ensure currentValue stays within [min, max] bounds for safe PWM operation - currentValue = constrain(currentValue, min, max); - pwmOutput->setOutput(currentValue); - //ESP_LOGD(tag, "duty: %f, sent val: %d, actual val: %d", currentValue, pwmOutput->currOutVal, pwmOutput->getOutVal()); + // Adjust currentValue based on ramp direction + if(rampState == RampingUp){ + currentValue += step; + } else { + currentValue -= step; + } + + // Constrain and apply + currentValue = constrain(currentValue, this->min, this->max); + if(pwmOutput) pwmOutput->setOutput(currentValue); + ESP_LOGD(tag, "duty: %f, sent val: %d, actual val: %d", currentValue, pwmOutput? pwmOutput->currOutVal : -1, pwmOutput? pwmOutput->getOutVal() : -1); tickCount = TickDelayCount; } } diff --git a/src/common/fileSystem.cpp b/src/common/fileSystem.cpp index 73c4d6a..3120d67 100644 --- a/src/common/fileSystem.cpp +++ b/src/common/fileSystem.cpp @@ -23,23 +23,31 @@ void Init_File_System(void){ //printAllSystemFiles(); } -void getAllDirectories(String (&directoryList)[MAX_DIRECTORIES], int &count){ +void getAllDirectories(String directoryList[], 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; } + // Initialize with root directory + if (MAX_DIRECTORIES <= 0) { + ESP_LOGW("FileSystem", "MAX_DIRECTORIES is not set or invalid"); + root.close(); + 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()); + if (count < MAX_DIRECTORIES){ // Ensure we don't overflow the caller's array + String entry = String(file.name()); + if (!entry.startsWith("/")) entry = "/" + entry; + directoryList[count] = entry; count++; - }else{ + } else { ESP_LOGW("FileSystem", "Directory list array is full"); break; } diff --git a/src/common/fileSystem.h b/src/common/fileSystem.h index 13a1414..1a5cfbc 100644 --- a/src/common/fileSystem.h +++ b/src/common/fileSystem.h @@ -15,7 +15,7 @@ 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 getAllDirectories(String directoryList[], int& count); void printFilesInDirectories(const String directoryList[], int count); diff --git a/src/global.cpp b/src/global.cpp index b0bdf77..fa1447b 100644 --- a/src/global.cpp +++ b/src/global.cpp @@ -412,4 +412,9 @@ void print_task_watermarks(void) { } else { ESP_LOGE(tag, "Failed to get current task handle"); } -} \ No newline at end of file +} + + +float updateLowpass(float currentValue, float newValue, float alpha) { + return (alpha * newValue) + ((1 - alpha) * currentValue); +} diff --git a/src/main.cpp b/src/main.cpp index 028827f..89f0ca5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -82,6 +82,9 @@ TimerHandle_t upgradeHeartbeatTimer = NULL; TimerHandle_t diagnosticsTimer = NULL; #define diagnosticsInterval 60000 // ms +TimerHandle_t analogInputTimer = NULL; +#define analogInputInterval 3000 // ms + void setupLogLevels(esp_log_level_t logLevel); @@ -120,6 +123,13 @@ void DiagnosticsCallback(TimerHandle_t xTimer) { #endif } +void AnalogInputCallback(TimerHandle_t xTimer) { + float v = readBoardInputVoltage(); + PowerVin = updateLowpass(prevPowerVin, v*1.01, PowerVinAlpha); + prevPowerVin = PowerVin; + //ESP_LOGI(tag, "Input Voltage = %f", PowerVin); +} + void checkLEDCChannels() { @@ -167,7 +177,7 @@ void setup() printAllSystemFiles(); } - String board_file_path, booth_file_path; + String board_file_path; Get_Board_and_Booth_File_Paths("/system/system.json", board_file_path, booth_file_path); // Load Board Pins @@ -205,15 +215,10 @@ void setup() // Initialize Temperature Sensor Init_TSensor(72, &sys_settings.tSensorSettings); - - float val = readBoardInputVoltage(); - ESP_LOGI(tag, "Input Volage = %f", val); - // Initialize BLE & Wifi // If button 1 is held during boot, enable upgrade mode Load_BLE_Settings("/system/ble.json"); - if (digitalRead(sys_settings.boardPins.btn[0]) == LOW) - { + if (digitalRead(sys_settings.boardPins.btn[0]) == LOW){ setStatusPin1(true); ESP_LOGW(tag, "Upgrade Mode Triggered"); ESP_LOGW(tag, "Enabling BLE and Update Service"); @@ -223,8 +228,7 @@ void setup() UpgradeMode = true; upgradeHeartbeatTimer = xTimerCreate("UpgradeHeartbeat", pdMS_TO_TICKS(5000), pdTRUE, NULL, UpgradeHeartbeatCallback); } - else - { + else { ESP_LOGI(tag, "Enabling BLE, No Update Service"); Init_BleServer(true, false); // Dont start the Upgrade service } @@ -236,20 +240,18 @@ void setup() } #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_RGB_Lights_Task(); - //vTaskDelay(100); - //Init_Ramp_Front_Light_Task(); + Init_RGB_Lights_Task(UpgradeMode); #endif // Create and start software timers buttonScanTimer = xTimerCreate("ButtonScan", pdMS_TO_TICKS(buttonScanInterval), pdTRUE, NULL, ButtonScanCallback); temperatureTimer = xTimerCreate("Temperature", pdMS_TO_TICKS(sys_settings.tSensorSettings.intervalMs), pdTRUE, NULL, TemperatureCallback); statusLedTimer = xTimerCreate("StatusLED", pdMS_TO_TICKS(statusLedInterval), pdTRUE, NULL, StatusLedCallback); + analogInputTimer = xTimerCreate("AnalogInput", pdMS_TO_TICKS(analogInputInterval), pdTRUE, NULL, AnalogInputCallback); #if FREERTOs_DIAGNOSTICS @@ -257,10 +259,11 @@ void setup() #endif // Start the timers - if (buttonScanTimer) xTimerStart(buttonScanTimer, 25); + //if (buttonScanTimer) xTimerStart(buttonScanTimer, 100); if (temperatureTimer && sys_settings.tSensorSettings.enabled) xTimerStart(temperatureTimer, 100); if (statusLedTimer) xTimerStart(statusLedTimer, 0); if (upgradeHeartbeatTimer && UpgradeMode) xTimerStart(upgradeHeartbeatTimer, upgradeHeartbeatInterval); + if (analogInputTimer) xTimerStart(analogInputTimer, 0); #if FREERTOS_DIAGNOSTICS if (diagnosticsTimer) xTimerStart(diagnosticsTimer, diagnosticsInterval); @@ -315,9 +318,20 @@ void loop() } } #endif + + /* + for (int i = 0; i < 3; i++) { + if (boardButtons[i] != NULL) { + boardButtons[i]->tick(); + } + } + vTaskDelay(buttonScanInterval); + */ // Small delay to prevent busy-waiting - vTaskDelay(pdMS_TO_TICKS(100)); + //vTaskDelay(pdMS_TO_TICKS(100)); + + vTaskDelay(portMAX_DELAY); } void setupLogLevels(esp_log_level_t logLevel) diff --git a/src/my_board.cpp b/src/my_board.cpp index bf7a7fc..b73d6fc 100644 --- a/src/my_board.cpp +++ b/src/my_board.cpp @@ -4,124 +4,143 @@ #include #include -#include "global.h" #include "JsonConstrain.h" +#include "global.h" #include "system.h" -static const char* tag = "board"; +static const char *tag = "board"; -BOARD_PINS* thisBoardPins; +BOARD_PINS *thisBoardPins = nullptr; // Basic validator for ESP32-S3 GPIOs; rejects common reserved/USB/strap pins static bool isValidGpio(int pin) { - if (pin < 0 || pin > 48) return false; - switch (pin) { + if (pin < 0 || pin > 48) + return false; + switch (pin) { case 19: // USB D- case 20: // USB D+ case 45: // strapping case 46: // strapping - return false; + return false; default: - return true; - } + return true; + } } -bool Load_Board_Pins(BOARD_PINS& boardPins, const String& path){ - // Default initialize to -1 to avoid stale values on partial loads - memset(&boardPins, -1, sizeof(boardPins)); - thisBoardPins = &boardPins; - File file = LittleFS.open(path); +bool Load_Board_Pins(BOARD_PINS &boardPins, const String &path) { + // Default initialize to -1 to avoid stale values on partial loads + memset(&boardPins, -1, sizeof(boardPins)); + thisBoardPins = &boardPins; + File file = LittleFS.open(path); - if (!file) { - ESP_LOGE(tag, "Error opening %s...", path.c_str()); - return false; - } + if (!file) { + ESP_LOGE(tag, "Error opening %s...", path.c_str()); + return false; + } - JsonDocument doc; - DeserializationError error = deserializeJson(doc, file); - file.close(); + JsonDocument doc; + DeserializationError error = deserializeJson(doc, file); + file.close(); - if (error) { - ESP_LOGE(tag, "%s deserialize error!..", path.c_str()); - return false; - } + if (error) { + ESP_LOGE(tag, "%s deserialize error: %s", path.c_str(), error.c_str()); + return false; + } - 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); + 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); - // Validate pins against reserved GPIOs - auto clampPin = [](int v){ return isValidGpio(v) ? v : -1; }; - boardPins.rgb1 = clampPin(boardPins.rgb1); - boardPins.rgb2 = clampPin(boardPins.rgb2); - for (int i=0;i<3;i++) boardPins.btn[i] = clampPin(boardPins.btn[i]); - boardPins.buzzer = clampPin(boardPins.buzzer); - for (int i=0;i<5;i++) boardPins.touch[i] = clampPin(boardPins.touch[i]); - boardPins.shield = clampPin(boardPins.shield); - for (int i=0;i<4;i++) boardPins.relay[i] = clampPin(boardPins.relay[i]); - for (int i=0;i<2;i++) boardPins.stat[i] = clampPin(boardPins.stat[i]); - boardPins.adc1 = clampPin(boardPins.adc1); - boardPins.oled_dc = clampPin(boardPins.oled_dc); - boardPins.oled_rst = clampPin(boardPins.oled_rst); - boardPins.oled_mosi = clampPin(boardPins.oled_mosi); - boardPins.oled_sck = clampPin(boardPins.oled_sck); - boardPins.oled_cs = clampPin(boardPins.oled_cs); - for (int i=0;i<2;i++) boardPins.ext[i] = clampPin(boardPins.ext[i]); - boardPins.rf433tx = clampPin(boardPins.rf433tx); - boardPins.rf433rx = clampPin(boardPins.rf433rx); + // Validate pins against reserved GPIOs + auto clampPin = [](int v) { return isValidGpio(v) ? v : -1; }; + boardPins.rgb1 = clampPin(boardPins.rgb1); + boardPins.rgb2 = clampPin(boardPins.rgb2); + for (int i = 0; i < 3; i++) + boardPins.btn[i] = clampPin(boardPins.btn[i]); + boardPins.buzzer = clampPin(boardPins.buzzer); + for (int i = 0; i < 5; i++) + boardPins.touch[i] = clampPin(boardPins.touch[i]); + boardPins.shield = clampPin(boardPins.shield); + for (int i = 0; i < 4; i++) + boardPins.relay[i] = clampPin(boardPins.relay[i]); + for (int i = 0; i < 2; i++) + boardPins.stat[i] = clampPin(boardPins.stat[i]); + boardPins.adc1 = clampPin(boardPins.adc1); + boardPins.oled_dc = clampPin(boardPins.oled_dc); + boardPins.oled_rst = clampPin(boardPins.oled_rst); + boardPins.oled_mosi = clampPin(boardPins.oled_mosi); + boardPins.oled_sck = clampPin(boardPins.oled_sck); + boardPins.oled_cs = clampPin(boardPins.oled_cs); + for (int i = 0; i < 2; i++) + boardPins.ext[i] = clampPin(boardPins.ext[i]); + boardPins.rf433tx = clampPin(boardPins.rf433tx); + boardPins.rf433rx = clampPin(boardPins.rf433rx); - ESP_LOGI(tag, "loaded Pins from %s", path.c_str()); - return true; + ESP_LOGI(tag, "loaded Pins from %s", path.c_str()); + return true; } +void Init_Board_Basic(BOARD_PINS &boardPins) { -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.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); + } - 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..."); + ESP_LOGI(tag, "Board pins initialized..."); } - - - - diff --git a/src/my_buttons.cpp b/src/my_buttons.cpp index 39d945f..151d2bf 100644 --- a/src/my_buttons.cpp +++ b/src/my_buttons.cpp @@ -5,11 +5,10 @@ #include "AppUpgrade.h" static const char* tag = "button"; -OneButton *boardButtons[3]; +OneButton *boardButtons[3] = { nullptr, nullptr, nullptr }; void Init_ButtonEvents(int8_t (&pin)[3]){ - - + // Initialize buttons if pins are valid and not already initialized if (pin[0] >= 0) { if (boardButtons[0] == nullptr) { boardButtons[0] = new OneButton(pin[0], true, true); diff --git a/src/my_buzzer.cpp b/src/my_buzzer.cpp index 930b82f..2d9918e 100644 --- a/src/my_buzzer.cpp +++ b/src/my_buzzer.cpp @@ -17,7 +17,7 @@ static const char* tag = "buzzer"; // Define static constexpr member from RtttlPlayer class constexpr uint16_t RtttlPlayer::LUT4[12]; -RtttlPlayer *player; +RtttlPlayer *player = nullptr; BUZZ_TUNE buzzTune[TUNE_MAX_COUNT]; int8_t buzzPin; int8_t buzzerChannel = -1; // Store the LEDC channel used by the buzzer @@ -105,7 +105,7 @@ void Buzzer_Load_Tunes(const char* tunesPath){ 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_LOGI(tag, "Loaded tune %d: cycles=%d, pause=%d, melody=%.40s...", + ESP_LOGD(tag, "Loaded tune %d: cycles=%d, pause=%d, melody=%.40s...", tuneIndex, buzzTune[tuneIndex].cycles, buzzTune[tuneIndex].pause, buzzTune[tuneIndex].melody.c_str()); tuneIndex++; diff --git a/src/my_device.cpp b/src/my_device.cpp index c2a477e..3c90010 100644 --- a/src/my_device.cpp +++ b/src/my_device.cpp @@ -1,79 +1,94 @@ #include "my_device.h" #include "JsonConstrain.h" -#include "global.h" -#include "system.h" -#include "my_buttons.h" #include "PWM_Output.h" #include "Ramp_Lights.h" #include "esp_log.h" -#include -#include +#include "global.h" +#include "my_buttons.h" +#include "system.h" #include #include -#include -#include -#include +#include +#include +#include #include #include -#include +#include +#include +#include #include #include static const char *tag = "my_device"; SYS_SETTINGS sys_settings; -PWM_Output *pwmOutputs[4]; -RAMP_LIGHT *rampLight1; -RAMP_LIGHT *rampLight2; +PWM_Output *pwmOutputs[4] = { nullptr, nullptr, nullptr, nullptr }; +RAMP_LIGHT *rampLight1 = nullptr; +RAMP_LIGHT *rampLight2 = nullptr; + +String booth_file_path; + +float PowerVinAlpha = 0.5; // Low-pass filter alpha +float prevPowerVin = 0.0; +float PowerVin = 0.0; + + // TODO Restore original setOutput code.. #define RELAY_RES 10 -void Init_PWM_Outputs(int8_t (&pin)[4], PWM_OUT_SETTINGS (&pwmSettings)[4]) -{ - // Initialize all pointers to nullptr first +void Init_PWM_Outputs(int8_t (&pin)[4], PWM_OUT_SETTINGS (&pwmSettings)[4]) { + // Delete any existing objects to avoid leaks on re-init for (int i = 0; i < 4; i++) { - pwmOutputs[i] = nullptr; + if (pwmOutputs[i]) { + delete pwmOutputs[i]; + pwmOutputs[i] = nullptr; + } } - - for (int i = 0; i < 4; i++) - { + + for (int i = 0; i < 4; i++) { int chIndex = findUnusedLedcChannel(); - if (chIndex < 0) - { + 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] = new PWM_Output(pin[i], chIndex, RELAY_RES, pwmSettings[i].freq, + pwmSettings[i].max, false); + if (pwmOutputs[i]) { + pwmOutputs[i]->setOutput(pwmSettings[i].def); + } else { + ESP_LOGE(tag, "Allocation failed for PWM Output%d", i); + } // pwmOutputs[i]->setOutput(5.0); - ESP_LOGI(tag, "PWM Output%d: Pin=%d, Freq=%d, ch=%d", i, pin[i], pwmSettings[i].freq, chIndex); + ESP_LOGI(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], settings[0].min, settings[0].max, settings[0].step); - ESP_LOGD(tag, "RampLight%d: btn=%d, pwmIndex=%d", 1, settings[0].btnIndex, settings[0].pwmOutIndex); +void Init_Ramp_Lights(RAMP_LIGHT_SETTINGS (&settings)[2], OneButton *(&btn)[3], PWM_Output *(&pwm)[4]) { + if (settings[0].enabled) { + if (rampLight1) { delete rampLight1; rampLight1 = nullptr; } + rampLight1 = new RAMP_LIGHT(btn[settings[0].btnIndex], pwm[settings[0].pwmOutIndex], settings[0].min, settings[0].max, settings[0].step); + if (rampLight1) + ESP_LOGD(tag, "RampLight%d: btn=%d, pwmIndex=%d, min=%f, max=%f, step=%f", 1, settings[0].btnIndex, settings[0].pwmOutIndex, settings[0].min, settings[0].max, settings[0].step); + else + ESP_LOGE(tag, "Failed to allocate RampLight1"); } - if (settings[1].enabled) - { - rampLight2 = new RAMP_LIGHT(btn[settings[1].btnIndex], pwm[settings[1].pwmOutIndex], settings[1].min, settings[1].max, settings[1].step); - ESP_LOGD(tag, "RampLight%d: btn=%d, pwmIndex=%d", 2, settings[1].btnIndex, settings[1].pwmOutIndex); + if (settings[1].enabled) { + if (rampLight2) { delete rampLight2; rampLight2 = nullptr; } + rampLight2 = new RAMP_LIGHT(btn[settings[1].btnIndex], pwm[settings[1].pwmOutIndex], settings[1].min, settings[1].max, settings[1].step); + if (rampLight2) + ESP_LOGD(tag, "RampLight%d: btn=%d, pwmIndex=%d, min=%f, max=%f, step=%f", 2, settings[1].btnIndex, settings[1].pwmOutIndex, settings[1].min, settings[1].max, settings[1].step); + else + ESP_LOGE(tag, "Failed to allocate RampLight2"); } } - // 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) -{ +void Get_Board_and_Booth_File_Paths(const char *sysPath, String &boardPath, String &boothPath) { File file = LittleFS.open(sysPath); - if (!file) - { + if (!file) { ESP_LOGE(tag, "Error opening %s...", sysPath); return; } @@ -82,23 +97,21 @@ void Get_Board_and_Booth_File_Paths(const char *sysPath, String &boardPath, Stri DeserializationError error = deserializeJson(doc, file); file.close(); - if (error) - { + 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"); + 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) -{ +void Load_Booth_Settings(SYS_SETTINGS &sys, const String &boothPath) { File file = LittleFS.open(boothPath); - if (!file) - { + if (!file) { ESP_LOGE(tag, "Error opening %s...", boothPath.c_str()); return; } @@ -107,139 +120,150 @@ void Load_Booth_Settings(SYS_SETTINGS &sys, const String &boothPath) DeserializationError error = deserializeJson(doc, file); file.close(); - if (error) - { + if (error) { ESP_LOGE(tag, "%s deserialize error!..", boothPath.c_str()); return; } + sys_settings.profile = jsonConstrainString(tag, doc.as(), "profile", "custom"); + // ********** Mode *********** String modeStr = jsonConstrainString(tag, doc.as(), "mode", "booth"); - if (modeStr == "roamer") - { + if (modeStr == "roamer") { sys_settings.mode = BOOTH_MODE_ROAMER; - } - else if (modeStr == "stik") - { + } else if (modeStr == "stik") { sys_settings.mode = BOOTH_MODE_STIK; - } - else - { + } else { sys_settings.mode = BOOTH_MODE_NONE; } // ********** PWM Out *********** JsonArray pwmJsonArray = doc["pwmout"]; - if (!pwmJsonArray.isNull()) - { + if (!pwmJsonArray.isNull()) { int pwmIndex = 0; - for (JsonObject obj : pwmJsonArray) - { - if (pwmIndex >= sizeof(sys_settings.pwmOutSettings) / sizeof(sys_settings.pwmOutSettings[0])) + for (JsonObject obj : pwmJsonArray) { + if (pwmIndex >= + sizeof(sys_settings.pwmOutSettings) / sizeof(sys_settings.pwmOutSettings[0])) 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].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 - { + } else { ESP_LOGE(tag, "Error!, %s key: pwmout not found..", boothPath); } // ********** Ramp Lights *********** JsonArray rampJsonArray = doc["ramp-lights"]; - if (!rampJsonArray.isNull()) - { + if (!rampJsonArray.isNull()) { int rampIndex = 0; - for (JsonObject obj : rampJsonArray) - { - if (rampIndex >= sizeof(sys_settings.rampLightSettings) / sizeof(sys_settings.rampLightSettings[0])) + for (JsonObject obj : rampJsonArray) { + if (rampIndex >= + sizeof(sys_settings.rampLightSettings) / sizeof(sys_settings.rampLightSettings[0])) 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, 95.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.1, 10.0, 1.5); + 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, 95.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.1, 10.0, 1.5); rampIndex++; } ESP_LOGI(tag, "Loaded Ramp Lights settings..."); - } - else - { + } else { ESP_LOGE(tag, "Error!, %s key: ramp-lights not found..", boothPath); } // ********** Fan *********** JsonObject sensorJson = doc["t-sensor"]; - if (!sensorJson.isNull()) - { + 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", 50.0, 100.0, 80.0); - sys_settings.tSensorSettings.setpoint2 = jsonConstrain(tag, sensorJson, "sp2", 60.0, 110.0, 90.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", 50.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); + sys_settings.tSensorSettings.pwmIndex = + jsonConstrain(tag, sensorJson, "relay", 0, 3, 3); + sys_settings.tSensorSettings.setpoint1 = + jsonConstrain(tag, sensorJson, "sp1", 50.0, 100.0, 80.0); + sys_settings.tSensorSettings.setpoint2 = + jsonConstrain(tag, sensorJson, "sp2", 60.0, 110.0, 90.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", 50.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_LOGI(tag, " SP1: %F, SP2 %F, Hyst: %F", sys_settings.tSensorSettings.setpoint1, sys_settings.tSensorSettings.setpoint2, sys_settings.tSensorSettings.hyst); - } - else - { + ESP_LOGI(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()) - { + if (!stripsJsonArray.isNull()) { int stripIndex = 0; - for (JsonObject obj : stripsJsonArray) - { + 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); + if (sys_settings.ledStripSettings[stripIndex]) { + 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); + } else { + ESP_LOGW(tag, "ledStripSettings[%d] is null, skipping config", stripIndex); + } 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 - { + } else { ESP_LOGE(tag, "Error!, %s key: strips not found.."); } // ********** BLE *********** JsonObject bleJson = doc["ble"]; - if (!bleJson.isNull()) - { + 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 - { + } else { ESP_LOGE(tag, "Error!, %s key: ble not found..", boothPath); } @@ -255,14 +279,11 @@ void Load_Booth_Settings(SYS_SETTINGS &sys, const String &boothPath) */ } - -void Init_ADC(void) -{ +void Init_ADC(void) { // Configure ADC analogReadResolution(12); // 12-bit ADC analogSetAttenuation(ADC_11db); - if (sys_settings.boardPins.adc1 >= 0) - { + if (sys_settings.boardPins.adc1 >= 0) { analogSetPinAttenuation(sys_settings.boardPins.adc1, ADC_11db); } @@ -271,35 +292,26 @@ void Init_ADC(void) 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) - { + 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) - { + } 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 - { + } else { ESP_LOGW(tag, "ADC calibration: Using default reference voltage"); } } - -float readBoardInputVoltage(void) -{ +float readBoardInputVoltage(void) { const int SAMPLES = 64; uint32_t reading = 0; - if (sys_settings.boardPins.adc1 < 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++) - { + for (int i = 0; i < SAMPLES; i++) { reading += analogRead(sys_settings.boardPins.adc1); delayMicroseconds(50); // Small delay between samples } @@ -311,8 +323,15 @@ float readBoardInputVoltage(void) 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); + // Voltage divider: R1 = 10k (series to input), R2 = 470 (to ground). Measurement is across R2. + // Vin = Vout * (R1 + R2) / R2 + const float R1 = 10000.0f; + const float R2 = 470.0f; + const float dividerFactor = (R1 + R2) / R2; - return voltage; + // Convert mV to V and apply divider factor + float vout = voltage_mv / 1000.0f; + float vin = vout * dividerFactor; + + return vin; } \ No newline at end of file diff --git a/src/my_oled.cpp b/src/my_oled.cpp index 1b8de4d..8857b90 100644 --- a/src/my_oled.cpp +++ b/src/my_oled.cpp @@ -1,24 +1,25 @@ #include "my_oled.h" -#include #include +#include -static const char* tag = "oled"; -Adafruit_SSD1306 *oled; +static const char *tag = "oled"; +Adafruit_SSD1306 *oled = nullptr; -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); +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 - } + if (!oled->begin(SSD1306_SWITCHCAPVCC)) { + ESP_LOGE(tag, "SSD1306 allocation failed"); + for (;;) + ; // Don't proceed, loop forever + } - oled_ShowInfo(); + oled_ShowInfo(); } -void oled_ShowInfo(void){ - //if(sysProps.oledEnabled) { +void oled_ShowInfo(void) { + // if(sysProps.oledEnabled) { oled->display(); vTaskDelay(2000); // Pause for 2 seconds @@ -37,89 +38,89 @@ void oled_ShowInfo(void){ // drawing operations and then update the screen all at once by calling // display.display(). These examples demonstrate both approaches... - //testdrawline(*oled); // Draw many lines + // testdrawline(*oled); // Draw many lines - //testdrawrect(*oled); // Draw rectangles (outlines) + // testdrawrect(*oled); // Draw rectangles (outlines) - testdrawline(oled); // Draw many lines + testdrawline(oled); // Draw many lines - testdrawrect(oled); // Draw rectangles (outlines) - //} + testdrawrect(oled); // Draw rectangles (outlines) + //} } -void testdrawline(Adafruit_SSD1306* display) { - //if(sysProps.oledEnabled) { +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; i < display->width(); 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); + for (i = 0; i < display->height(); 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 = 0; i < display->width(); 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); + 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->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); + 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; i < display->height(); 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); + for (i = 0; i < display->width(); 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) { +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); + for (int16_t i = 0; i < display->height() / 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 index 7cbf218..6421e9b 100644 --- a/src/my_tsensor.cpp +++ b/src/my_tsensor.cpp @@ -21,7 +21,7 @@ void Init_TSensor(uint8_t addr, TSENSOR_SETTINGS *tsettings) { // Initialize the temperature sensor once with the provided I2C address if (tSensor == nullptr) { tSensor = new TI_TMP102_Compatible(addr); - ESP_LOGI(tag, "TSensor initialized at I2C addr 0x%02X", addr); + ESP_LOGD(tag, "TSensor initialized at I2C addr 0x%02X", addr); } else { ESP_LOGW(tag, "TSensor already initialized; ignoring re-init request (addr 0x%02X)", addr); } @@ -77,7 +77,7 @@ void Init_TSensor(uint8_t addr, TSENSOR_SETTINGS *tsettings) { // Fan is off - check if we should turn it on if (temperature >= sp1) { fanIsOn = true; - ESP_LOGI(tag, "Fan turning ON - temp %.2f >= setpoint1 %.2f", temperature, sp1); + ESP_LOGD(tag, "Fan turning ON - temp %.2f >= setpoint1 %.2f", temperature, sp1); } else { newDuty = 0.0f; // Stay off } @@ -86,7 +86,7 @@ void Init_TSensor(uint8_t addr, TSENSOR_SETTINGS *tsettings) { if (temperature < (sp1 - hyst)) { fanIsOn = false; newDuty = 0.0f; - ESP_LOGI(tag, "Fan turning OFF - temp %.2f < (setpoint1 - hyst) %.2f", temperature, sp1 - hyst); + ESP_LOGD(tag, "Fan turning OFF - temp %.2f < (setpoint1 - hyst) %.2f", temperature, sp1 - hyst); } } @@ -105,7 +105,7 @@ void Init_TSensor(uint8_t addr, TSENSOR_SETTINGS *tsettings) { float tempRatio = (temperature - sp1) / tempRange; newDuty = fp1 + (tempRatio * powerRange); - ESP_LOGV(tag, "Linear scaling: temp=%.2f, ratio=%.3f, duty=%.2f", temperature, tempRatio, newDuty); + ESP_LOGD(tag, "Linear scaling: temp=%.2f, ratio=%.3f, duty=%.2f", temperature, tempRatio, newDuty); } // Ensure duty is within bounds @@ -115,7 +115,7 @@ void Init_TSensor(uint8_t addr, TSENSOR_SETTINGS *tsettings) { // Apply new duty cycle if changed (with small tolerance to avoid constant updates) if (fabs(currentDuty - newDuty) > 0.1f) { pwmOut->setOutput(newDuty); - ESP_LOGI(tag, "Board T: %.2f F, Fan -> %.2f%% (on=%s)", temperature, newDuty, fanIsOn ? "true" : "false"); + ESP_LOGD(tag, "Board T: %.2f F, Fan -> %.2f%% (on=%s)", temperature, newDuty, fanIsOn ? "true" : "false"); } } diff --git a/src/my_wifi.cpp b/src/my_wifi.cpp index ec00a04..5cbef5c 100644 --- a/src/my_wifi.cpp +++ b/src/my_wifi.cpp @@ -1,30 +1,28 @@ #include "my_wifi.h" -#include -#include -#include +#include "AppUpgrade.h" +#include "common/fileSystem.h" +#include "esp_log.h" +#include "global.h" +#include "jsonconstrain.h" +#include "my_board.h" +#include "my_buzzer.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" -#include "my_board.h" +#include +#include +#include static const char *tag = "WIFI"; volatile bool InternetAvailable; AsyncWebServer webServer(80); AsyncEventSource eventUpgradeProgress("/upgrade-progress"); -// DNSServer *dnsServer; -// #define DNS_PORT 53 String client_ssid; String client_pass; @@ -39,10 +37,10 @@ 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); +String filesDropdownOptions; +String dirDropdownOptions; +String savePath; // needed for storing file when editing a file +String savePathInput; const char *http_username = "admin"; const char *http_password = "12345678"; const char *param_delete_path = "delete-path"; @@ -67,11 +65,9 @@ static const uint8_t MAX_ATTEMPTS = 10; static SemaphoreHandle_t wifiMutex = nullptr; volatile bool wifi_task_running = false; -void Wifi_Init() -{ +void Wifi_Init() { // Initialize LittleFS - if (!LittleFS.begin(true)) - { + if (!LittleFS.begin(true)) { ESP_LOGE(tag, "LittleFS mount failed"); return; } @@ -86,8 +82,7 @@ void Wifi_Init() // Configure and start AP WiFi.softAPConfig(local_IP, gateway, subnet); - if (!WiFi.softAP(ap_ssid, ap_pass)) - { + if (!WiFi.softAP(ap_ssid, ap_pass)) { ESP_LOGE(tag, "AP start failed"); return; } @@ -109,12 +104,10 @@ void Wifi_Init() // Wifi_Scan_for_Networks(); } -void Wifi_Load_Settings(String path) -{ +void Wifi_Load_Settings(String path) { // Load WiFi settings File file = LittleFS.open(path, "r"); - if (!file) - { + if (!file) { ESP_LOGE(tag, "Error opening %s", path.c_str()); return; } @@ -123,49 +116,43 @@ void Wifi_Load_Settings(String path) DeserializationError error = deserializeJson(doc, file); file.close(); - if (error) - { + if (error) { ESP_LOGE(tag, "Failed to deserialize %s", path.c_str()); return; } JsonObject wifiJson = doc.as(); - if (wifiJson.isNull()) - { + if (wifiJson.isNull()) { ESP_LOGE(tag, "%s is empty", path.c_str()); return; } // Load AP settings JsonObject apJson = wifiJson["wifi-ap"]; - if (!apJson.isNull()) - { + 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")); - char macSuffix[13] = {0}; // Just need 2 chars + null terminator + char macSuffix[13] = {0}; // Just need 2 chars + null terminator get_chip_mac(macSuffix, sizeof(macSuffix)); // Only get first 2 chars of MAC - ap_ssid += "-"; // Add a separator - ap_ssid += macSuffix[0]; // Add first character - ap_ssid += macSuffix[1]; // Add second character + ap_ssid += "-"; // Add a separator + ap_ssid += macSuffix[0]; // Add first character + ap_ssid += macSuffix[1]; // Add second character } // Load Client settings JsonObject clientJson = wifiJson["wifi-client"]; - if (!apJson.isNull()) - { + if (!apJson.isNull()) { client_ssid = jsonConstrainString(tag, clientJson, "ssid", "none"); client_pass = jsonConstrainString(tag, clientJson, "pass", "12345678"); } } -bool StartWifiConnectTask(String ssid = "", String pass = "") -{ - if (ssid.isEmpty() || pass.length() < 8) - { +bool StartWifiConnectTask(String ssid = "", String pass = "") { + if (ssid.isEmpty() || pass.length() < 8) { ESP_LOGE(tag, "Invalid SSID or password"); return false; } @@ -174,27 +161,22 @@ bool StartWifiConnectTask(String ssid = "", String pass = "") if (wifiMutex == nullptr) { wifiMutex = xSemaphoreCreateMutex(); } - + // Take mutex with timeout if (xSemaphoreTake(wifiMutex, pdMS_TO_TICKS(1000)) == pdTRUE) { - if (!wifi_task_running) - { + if (!wifi_task_running) { client_ssid = ssid; client_pass = pass; - if (Wifi_Task_Handle == NULL) - { + if (Wifi_Task_Handle == NULL) { ESP_LOGI(tag, "Creating WiFi task"); - xTaskCreatePinnedToCore(Wifi_ConnectTask, "Wifi_Task", 1024 * 6, NULL, 1, &Wifi_Task_Handle, 0); + xTaskCreatePinnedToCore(Wifi_ConnectTask, "Wifi_Task", 1024 * 6, NULL, 1, + &Wifi_Task_Handle, 0); xSemaphoreGive(wifiMutex); return true; - } - else - { + } else { ESP_LOGI(tag, "WiFi task already running"); } - } - else - { + } else { ESP_LOGE(tag, "Task already running"); } xSemaphoreGive(wifiMutex); @@ -205,16 +187,14 @@ bool StartWifiConnectTask(String ssid = "", String pass = "") return false; } -void Wifi_ConnectTask(void *parameter) -{ +void Wifi_ConnectTask(void *parameter) { static const char *tag = "Wifi_Task"; wifi_task_running = true; - + // Register task with watchdog to prevent system hangs esp_task_wdt_add(NULL); - - if (WiFi.status() != WL_CONNECTED || client_ssid != WiFi.SSID()) - { + + if (WiFi.status() != WL_CONNECTED || client_ssid != WiFi.SSID()) { ESP_LOGI(tag, "Connecting to: %s", client_ssid.c_str()); // Disconnect and connect to new network @@ -224,13 +204,11 @@ void Wifi_ConnectTask(void *parameter) // Wait for connection uint8_t attempts = 0; - while (WiFi.status() != WL_CONNECTED && attempts < MAX_ATTEMPTS) - { + while (WiFi.status() != WL_CONNECTED && attempts < MAX_ATTEMPTS) { // Reset watchdog timer to prevent timeouts during connection attempts esp_task_wdt_reset(); - - switch (WiFi.status()) - { + + switch (WiFi.status()) { case WL_NO_SSID_AVAIL: ESP_LOGW(tag, "SSID not found: %s", client_ssid.c_str()); break; @@ -245,20 +223,16 @@ void Wifi_ConnectTask(void *parameter) } // Check if connected - if (WiFi.status() == WL_CONNECTED) - { + if (WiFi.status() == WL_CONNECTED) { ESP_LOGI(tag, "Connected to %s", client_ssid.c_str()); WiFi.setAutoReconnect(true); - if (!Wifi_Save_Credentials("/system/wifi.json")) - { + if (!Wifi_Save_Credentials("/system/wifi.json")) { ESP_LOGW(tag, "Failed to save credentials"); } Wifi_Check_Internet(); - } - else - { + } else { ESP_LOGE(tag, "Failed to connect after %d attempts", MAX_ATTEMPTS); } } @@ -267,20 +241,20 @@ void Wifi_ConnectTask(void *parameter) // Unregister from watchdog before deletion esp_task_wdt_delete(NULL); - + Wifi_Task_Handle = NULL; wifi_task_running = false; vTaskDelete(NULL); } -void Wifi_Check_Internet() -{ +void Wifi_Check_Internet() { // Check for internet connection with multiple fallback servers - const char *hosts[] = {"8.8.8.8", "1.1.1.1", "208.67.222.222"}; // Google DNS, Cloudflare DNS, OpenDNS + const char *hosts[] = {"8.8.8.8", "1.1.1.1", + "208.67.222.222"}; // Google DNS, Cloudflare DNS, OpenDNS const int num_hosts = sizeof(hosts) / sizeof(hosts[0]); - + InternetAvailable = false; - + // Try pinging each host for (int i = 0; i < num_hosts; i++) { if (Ping.ping(hosts[i], 1)) { @@ -291,23 +265,20 @@ void Wifi_Check_Internet() // Small delay between ping attempts vTaskDelay(pdMS_TO_TICKS(100)); } - + if (!InternetAvailable) { ESP_LOGW(tag, "No internet connection after trying multiple DNS servers"); } } -bool Wifi_Save_Credentials(String path) -{ +bool Wifi_Save_Credentials(String path) { // Load existing JSON JsonDocument doc; File readFile = LittleFS.open(path, "r"); - if (readFile) - { + if (readFile) { DeserializationError error = deserializeJson(doc, readFile); readFile.close(); - if (error) - { + if (error) { ESP_LOGE(tag, "Failed to parse existing JSON"); return false; } @@ -320,15 +291,13 @@ bool Wifi_Save_Credentials(String path) // Save updated JSON File writeFile = LittleFS.open(path, "w"); - if (!writeFile) - { + 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) - { + if (serializeJsonPretty(doc, writeFile) == 0) { ESP_LOGE(tag, "Failed to write JSON to file"); writeFile.close(); return false; @@ -340,14 +309,13 @@ bool Wifi_Save_Credentials(String path) /** * Scans for available WiFi networks and stores the results in JSON format - * + * * Updates scanStatus global: 0=none, 1=scanning, 2=complete, -1=error * Sets scanInProgress flag during operation * Populates networkList with JSON formatted scan results */ -void Wifi_Scan_for_Networks() -{ - static const char* tag = "WiFiScan"; +void Wifi_Scan_for_Networks() { + static const char *tag = "WiFiScan"; const uint32_t SCAN_TIMEOUT_MS = 15000; // 15 second timeout for scan // Protect against concurrent scans @@ -355,83 +323,101 @@ void Wifi_Scan_for_Networks() ESP_LOGW(tag, "WiFi scan already in progress"); return; } - + // Use mutex for thread safety if available bool useMutex = (wifiMutex != nullptr); if (useMutex && xSemaphoreTake(wifiMutex, pdMS_TO_TICKS(1000)) != pdTRUE) { ESP_LOGE(tag, "Failed to acquire mutex - WiFi operation in progress"); return; } - + scanInProgress = true; scanStatus = 1; // Scanning ESP_LOGI(tag, "Starting WiFi network scan"); - + // Start scan (async=false, show_hidden=false) WiFi.scanNetworks(false, false); - + // Wait for scan with timeout uint32_t startTime = millis(); - while (WiFi.scanComplete() == WIFI_SCAN_RUNNING) - { + while (WiFi.scanComplete() == WIFI_SCAN_RUNNING) { // Check for timeout if (millis() - startTime > SCAN_TIMEOUT_MS) { ESP_LOGE(tag, "WiFi scan timeout after %u ms", SCAN_TIMEOUT_MS); scanInProgress = false; scanStatus = -1; // Error - if (useMutex) xSemaphoreGive(wifiMutex); + if (useMutex) + xSemaphoreGive(wifiMutex); return; } - - // Reset watchdog if needed - #ifdef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0 + +// Reset watchdog if needed +#ifdef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0 esp_task_wdt_reset(); - #endif - +#endif + vTaskDelay(pdMS_TO_TICKS(100)); // Wait for scan to complete } // Get scan results networkCount = WiFi.scanComplete(); - if (networkCount >= 0) - { + if (networkCount >= 0) { ESP_LOGI(tag, "WiFi scan complete, found %d networks", networkCount); scanStatus = 2; // Complete - + // Create JSON document with appropriate capacity JsonDocument doc; doc.clear(); JsonArray networks = doc["networks"].to(); - for (int i = 0; i < networkCount; i++) - { + for (int i = 0; i < networkCount; i++) { auto network = networks.add(); - + // Basic network info network["ssid"] = WiFi.SSID(i); network["rssi"] = WiFi.RSSI(i); network["channel"] = WiFi.channel(i); - + // Security details wifi_auth_mode_t encType = WiFi.encryptionType(i); network["encryption"] = encType != WIFI_AUTH_OPEN; - + // Add detailed encryption type - const char* encTypeStr = "unknown"; + const char *encTypeStr = "unknown"; switch (encType) { - case WIFI_AUTH_OPEN: encTypeStr = "open"; break; - case WIFI_AUTH_WEP: encTypeStr = "WEP"; break; - case WIFI_AUTH_WPA_PSK: encTypeStr = "WPA_PSK"; break; - case WIFI_AUTH_WPA2_PSK: encTypeStr = "WPA2_PSK"; break; - case WIFI_AUTH_WPA_WPA2_PSK: encTypeStr = "WPA_WPA2_PSK"; break; - case WIFI_AUTH_WPA2_ENTERPRISE: encTypeStr = "WPA2_ENTERPRISE"; break; - case WIFI_AUTH_WPA3_PSK: encTypeStr = "WPA3_PSK"; break; - case WIFI_AUTH_WPA2_WPA3_PSK: encTypeStr = "WPA2_WPA3_PSK"; break; - case WIFI_AUTH_WAPI_PSK: encTypeStr = "WAPI_PSK"; break; - default: encTypeStr = "unknown"; break; + case WIFI_AUTH_OPEN: + encTypeStr = "open"; + break; + case WIFI_AUTH_WEP: + encTypeStr = "WEP"; + break; + case WIFI_AUTH_WPA_PSK: + encTypeStr = "WPA_PSK"; + break; + case WIFI_AUTH_WPA2_PSK: + encTypeStr = "WPA2_PSK"; + break; + case WIFI_AUTH_WPA_WPA2_PSK: + encTypeStr = "WPA_WPA2_PSK"; + break; + case WIFI_AUTH_WPA2_ENTERPRISE: + encTypeStr = "WPA2_ENTERPRISE"; + break; + case WIFI_AUTH_WPA3_PSK: + encTypeStr = "WPA3_PSK"; + break; + case WIFI_AUTH_WPA2_WPA3_PSK: + encTypeStr = "WPA2_WPA3_PSK"; + break; + case WIFI_AUTH_WAPI_PSK: + encTypeStr = "WAPI_PSK"; + break; + default: + encTypeStr = "unknown"; + break; } network["security"] = encTypeStr; - + // Add signal quality 0-100% int rssi = WiFi.RSSI(i); int rssiLimited = rssi < -100 ? -100 : (rssi > -50 ? -50 : rssi); @@ -442,267 +428,295 @@ void Wifi_Scan_for_Networks() // Serialize to the global variable networkList.clear(); serializeJson(doc, networkList); - + // Clean up scan results from memory WiFi.scanDelete(); - } - else - { + } else { ESP_LOGE(tag, "WiFi scan failed with error code: %d", networkCount); scanStatus = -1; // Error } - + scanInProgress = false; - if (useMutex) xSemaphoreGive(wifiMutex); + if (useMutex) + xSemaphoreGive(wifiMutex); } -void Setup_WebServer_Handlers(AsyncWebServer &server) -{ +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"); - }); + 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; - } + 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(); + // Get and store credentials + String ssid = request->getParam("ssid", false, false)->value(); + String pass = request->getParam("pass", false, false)->value(); - // Validate credentials - if (ssid.length() < 1 || pass.length() < 8) { - ESP_LOGE(tag, "Invalid credentials"); - request->send(400, "application/json", "{\"error\":\"Invalid credentials\"}"); - return; - } + // Validate credentials + if (ssid.length() < 1 || pass.length() < 8) { + ESP_LOGE(tag, "Invalid credentials"); + request->send(400, "application/json", "{\"error\":\"Invalid credentials\"}"); + return; + } - // Start connection - StartWifiConnectTask(ssid, pass); - - ESP_LOGI(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(WiFi.status() == WL_CONNECTED ? "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"); }); + // Start connection + StartWifiConnectTask(ssid, pass); + + ESP_LOGI(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(WiFi.status() == WL_CONNECTED ? "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/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; - } + 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()); + // Prevent directory traversal + if (filename.indexOf("..") >= 0) { + ESP_LOGW(tag, "Rejected download with unsafe path: %s", filename.c_str()); + request->send(400, "text/plain", "Invalid file path"); + return; + } + if (!filename.startsWith("/")) filename = "/" + filename; + + if (!LittleFS.exists(filename)) { + ESP_LOGE(tag, "File not found: %s", filename.c_str()); + request->send(404, "text/plain", "File not found!"); + return; + } + ESP_LOGI(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"; + }); + 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(); - } + // 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; - } + // 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; - } + // 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; - } + // Prevent directory traversal + if (filename.indexOf("..") >= 0) { + ESP_LOGW(tag, "Rejected delete with unsafe path: %s", filename.c_str()); + request->send(400, "text/plain", "Invalid file path"); + return; + } - 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; - } + // Ensure path starts with / + if (!filename.startsWith("/")) filename = "/" + filename; + while (filename.indexOf("//") >= 0) filename.replace("//", "/"); - request->redirect("/files"); }); - server.on("/files/edit", HTTP_GET, [](AsyncWebServerRequest *request) - { - static const char* tag = "EditHandler"; + 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; + } - // Authentication check - if (!request->authenticate(http_username, http_password)) { - ESP_LOGW(tag, "Authentication failed"); - return request->requestAuthentication(); - } + request->redirect("/files"); + }); + server.on("/files/edit", HTTP_GET, [](AsyncWebServerRequest *request) { + static const char *tag = "EditHandler"; - // Parameter validation - if (!request->hasParam(param_edit_path)) { - ESP_LOGE(tag, "Missing edit path parameter"); - request->send(400, "text/plain", "Missing file path"); - return; - } + // Authentication check + if (!request->authenticate(http_username, http_password)) { + ESP_LOGW(tag, "Authentication failed"); + return request->requestAuthentication(); + } - // Get and decode filename - String fileName = uriDecode(request->getParam(param_edit_path)->value()); - ESP_LOGI(tag, "Edit request for file: %s", fileName.c_str()); + // Parameter validation + if (!request->hasParam(param_edit_path)) { + ESP_LOGE(tag, "Missing edit path parameter"); + request->send(400, "text/plain", "Missing file path"); + return; + } - // Set save path - savePath = (fileName == "new") ? "/new.txt" : fileName; + // Get and decode filename + String fileName = uriDecode(request->getParam(param_edit_path)->value()); + ESP_LOGI(tag, "Edit request for file: %s", fileName.c_str()); - // Validate path - if (!savePath.startsWith("/")) { - savePath = "/" + savePath; - } + // Set save path + savePath = (fileName == "new") ? "/new.txt" : fileName; - ESP_LOGI(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()); + // Validate path + if (!savePath.startsWith("/")) { + savePath = "/" + savePath; + } - 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); }); + ESP_LOGI(tag, "Save path set to: %s", savePath.c_str()); + + if (!sendHtmlFile("/www/edit.html", request, fileManagerHtmlProcessor)) { + ESP_LOGE(tag, "Failed to send edit page: %s", "/www/edit.html"); + 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; + 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()); + if (savePath.indexOf("..") >= 0) { + ESP_LOGW(tag, "Rejected save with unsafe path: %s", savePath.c_str()); + request->send(400, "text/plain", "Invalid file path"); + return; + } + if (!savePath.startsWith("/")) savePath = "/" + savePath; + while (savePath.indexOf("//") >= 0) savePath.replace("//", "/"); + } + 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); }); + 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(WiFi.status() == WL_CONNECTED ? "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(WiFi.status() == WL_CONNECTED ? "Connected" : "Disconnected") + "\"}"; - request->send(200, "application/json", response); }); - server.on("/leds/settings", HTTP_POST, [](AsyncWebServerRequest *request) - { - String response = "{\"status\":\"" + String(WiFi.status() == WL_CONNECTED ? "Connected" : "Disconnected") + "\"}"; - request->send(200, "application/json", response); }); + server.on("/system/summary", HTTP_GET, [](AsyncWebServerRequest *request) { + String response = "{\"status\":\"" + + String(WiFi.status() == WL_CONNECTED ? "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(WiFi.status() == WL_CONNECTED ? "Connected" : "Disconnected") + + "\"}"; + request->send(200, "application/json", response); + }); + server.on("/leds/settings", HTTP_POST, [](AsyncWebServerRequest *request) { + String response = "{\"status\":\"" + + String(WiFi.status() == WL_CONNECTED ? "Connected" : "Disconnected") + + "\"}"; + request->send(200, "application/json", response); + }); // LightStik related handlers - server.on("/lightstik/settings", HTTP_GET, [](AsyncWebServerRequest *request) - { - String response = "{\"status\":\"" + String(WiFi.status() == WL_CONNECTED ? "Connected" : "Disconnected") + "\"}"; - request->send(200, "application/json", response); }); - server.on("/lightstik/settings", HTTP_POST, [](AsyncWebServerRequest *request) - { - String response = "{\"status\":\"" + String(WiFi.status() == WL_CONNECTED ? "Connected" : "Disconnected") + "\"}"; - request->send(200, "application/json", response); }); - server.on("/lightstik/register", HTTP_POST, [](AsyncWebServerRequest *request) - { - String response = "{\"status\":\"" + String(WiFi.status() == WL_CONNECTED ? "Connected" : "Disconnected") + "\"}"; - request->send(200, "application/json", response); }); + server.on("/lightstik/settings", HTTP_GET, [](AsyncWebServerRequest *request) { + String response = "{\"status\":\"" + + String(WiFi.status() == WL_CONNECTED ? "Connected" : "Disconnected") + + "\"}"; + request->send(200, "application/json", response); + }); + server.on("/lightstik/settings", HTTP_POST, [](AsyncWebServerRequest *request) { + String response = "{\"status\":\"" + + String(WiFi.status() == WL_CONNECTED ? "Connected" : "Disconnected") + + "\"}"; + request->send(200, "application/json", response); + }); + server.on("/lightstik/register", HTTP_POST, [](AsyncWebServerRequest *request) { + String response = "{\"status\":\"" + + String(WiFi.status() == WL_CONNECTED ? "Connected" : "Disconnected") + + "\"}"; + request->send(200, "application/json", response); + }); // Firmware Update Handlers - server.on("/upgrade/check", HTTP_GET, [](AsyncWebServerRequest *request) - { + server.on("/upgrade/check", HTTP_GET, [](AsyncWebServerRequest *request) { // Ensure updateUrl is loaded (function resides in AppUpgrade.cpp) - loadUpdateJson(); - // Pass nullptr bucket to use internally loaded default + subsequently set base via setBaseUrl if needed + if (!loadUpdateJson()) { + ESP_LOGE(tag, "Failed to load update.json for /upgrade/check"); + } + // Pass nullptr bucket to use internally loaded default + subsequently set base via + // setBaseUrl if needed AppUpdater updater(LittleFS, localVersion, nullptr, "update.json", "firmware.bin"); // If a dynamic URL was loaded, override base extern String updateUrl; // declared in AppUpgrade.cpp - if(updateUrl.length()) updater.setBaseUrl(updateUrl); - // checkManifest() does not return a bool; capture its result (type-dependent) instead of using it in a boolean expression + if (updateUrl.length()) + updater.setBaseUrl(updateUrl); + // checkManifest() does not return a bool; capture its result (type-dependent) instead of + // using it in a boolean expression auto manifestResult = updater.checkManifest(); // TODO: inspect manifestResult for success/failure once its API is known otaVersion = updater.otaVersion; @@ -710,120 +724,115 @@ void Setup_WebServer_Handlers(AsyncWebServer &server) JsonDocument doc; doc["currentVersion"] = localVersion.toString(); - doc["latestVersion"] = otaVersion.toString(); + doc["latestVersion"] = otaVersion.toString(); doc["updateAvailable"] = avail; - + String response; serializeJson(doc, response); - request->send(200, "application/json", 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.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.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) - { + if (!request) { ESP_LOGE(tag, "Invalid request"); return; } // Get and validate file path String filePath = request->url(); - if (filePath.isEmpty()) - { + 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("/")) - { + 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"; - } + // Basic sanitization to prevent directory traversal + if (filePath.indexOf("..") >= 0) { + ESP_LOGW(tag, "Rejected unsafe path: %s", filePath.c_str()); + request->send(400, "text/plain", "Invalid file path"); + return; + } - ESP_LOGI(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"); + // Ensure leading slash and collapse duplicate slashes + if (!filePath.startsWith("/")) filePath = "/" + filePath; + while (filePath.indexOf("//") >= 0) filePath.replace("//", "/"); + + // 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); }); // 404 handler - server.onNotFound([](AsyncWebServerRequest *request) - { - ESP_LOGE(tag, "404: %s", request->url().c_str()); - request->send(404, "text/plain", "Not found"); }); + 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) -{ +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 * 512; // 512KB limit - if (!index) - { + if (!index) { // Initial upload chunk - if (!request->hasParam("dir-path", true, false)) - { + 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_LOGI(tag, "Starting upload: %s", path.c_str()); + String path = p->value() + "/" + filename; + // Normalize and reject traversal + if (path.indexOf("..") >= 0) { + ESP_LOGW(tag, "Rejected upload with unsafe path: %s", path.c_str()); + request->send(400, "text/plain", "Invalid upload path"); + return; + } + if (!path.startsWith("/")) path = "/" + path; + while (path.indexOf("//") >= 0) path.replace("//", "/"); - // Validate path - if (!path.startsWith("/")) - { - path = "/" + path; - } + ESP_LOGI(tag, "Starting upload: %s", path.c_str()); - request->_tempFile = LittleFS.open(path, "w"); - if (!request->_tempFile) - { + 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; @@ -831,10 +840,8 @@ void handleFilesUpload_OnBody(AsyncWebServerRequest *request, String filename, s } // Write chunk - if (len && request->_tempFile) - { - if (index + len > MAX_UPLOAD_SIZE) - { + if (len && request->_tempFile) { + if (index + len > MAX_UPLOAD_SIZE) { request->_tempFile.close(); LittleFS.remove(request->_tempFile.name()); ESP_LOGE(tag, "Upload too large"); @@ -842,8 +849,7 @@ void handleFilesUpload_OnBody(AsyncWebServerRequest *request, String filename, s return; } - if (!request->_tempFile.write(data, len)) - { + if (!request->_tempFile.write(data, len)) { ESP_LOGE(tag, "Write failed"); request->_tempFile.close(); request->send(500, "text/plain", "Write failed"); @@ -851,8 +857,7 @@ void handleFilesUpload_OnBody(AsyncWebServerRequest *request, String filename, s } } - if (final) - { + if (final) { request->_tempFile.close(); ESP_LOGI(tag, "Upload complete: %s, %u bytes", filename.c_str(), index + len); request->redirect("/files"); @@ -860,58 +865,46 @@ void handleFilesUpload_OnBody(AsyncWebServerRequest *request, String filename, s } // 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_LOGI(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"); +bool sendHtmlFile(const char *filePath, AsyncWebServerRequest *request, + String (*callback)(const String &)) { + 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 false; } + + String processedData = varReplace(htmlFile, callback); + delete[] htmlFile; // Clean up allocated memory + + ESP_LOGI(tag, "Sent file: %s", filePath); + request->send(200, "text/html", processedData); + return true; } -const char *getFileExtension(const char *filename) -{ +const char *getFileExtension(const char *filename) { // Input validation - if (!filename) - { + 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_LOGI(tag, "No valid extension found in: %s", filename); + if (!lastDot || lastDot == filename || *(lastDot + 1) == '\0') { + ESP_LOGD(tag, "No valid extension found in: %s", filename); return ""; } - ESP_LOGI(tag, "Found extension: %s", lastDot + 1); + ESP_LOGD(tag, "Found extension: %s", lastDot + 1); return lastDot + 1; } -const char *getFileType(const char *ext) -{ +const char *getFileType(const char *ext) { if (!ext) return "application/octet-stream"; - ESP_LOGI(tag, "Getting file type for extension: %s", ext); + ESP_LOGD(tag, "Getting file type for extension: %s", ext); if (strcmp(ext, "png") == 0) return "image/png"; @@ -932,18 +925,16 @@ const char *getFileType(const char *ext) if (strcmp(ext, "json") == 0) return "application/json"; - ESP_LOGW(tag, "Unknown file extension: %s", ext); + ESP_LOGD(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 &)) -{ +String varReplace(const String &input, String (*callback)(const String &)) { static const char *tag = "varReplace"; // Validate inputs - if (input.isEmpty() || !callback) - { + if (input.isEmpty() || !callback) { ESP_LOGW(tag, "Empty input or null callback"); return input; } @@ -956,12 +947,10 @@ String varReplace(const String &input, String (*callback)(const String &)) int startPos = 0; // Process all segments - while (true) - { + while (true) { // Find next variable int start = input.indexOf("{{", startPos); - if (start == -1) - { + if (start == -1) { break; } @@ -970,8 +959,7 @@ String varReplace(const String &input, String (*callback)(const String &)) // Find end of variable int end = input.indexOf("}}", start + 2); - if (end == -1) - { + if (end == -1) { ESP_LOGW(tag, "Unmatched {{ at position %d", start); result += input.substring(start); break; @@ -979,21 +967,15 @@ String varReplace(const String &input, String (*callback)(const String &)) // Extract and validate segment String segment = input.substring(start + 2, end); - if (segment.length() <= maxSegmentLength) - { - try - { - String replacement = callback(segment); + if (segment.length() <= maxSegmentLength) { + String replacement = callback(segment); + if (!replacement.isEmpty()) { result += replacement; - } - catch (const std::exception &e) - { - ESP_LOGE(tag, "Callback error: %s", e.what()); + } else { + ESP_LOGW(tag, "Callback returned empty for segment: %s", segment.c_str()); result += input.substring(start, end + 2); } - } - else - { + } else { ESP_LOGW(tag, "Segment too long: %d chars", segment.length()); result += input.substring(start, end + 2); } @@ -1002,43 +984,33 @@ String varReplace(const String &input, String (*callback)(const String &)) } // Add remaining text - if (startPos < input.length()) - { + if (startPos < input.length()) { result += input.substring(startPos); } return result; } -const char *convertFileSize(const size_t bytes) -{ +const char *convertFileSize(const size_t bytes) { static char fileSizeBuffer[16]; // Pre-allocated buffer for the file size - if (bytes < 1024) - { + if (bytes < 1024) { snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%d B", bytes); - } - else if (bytes < 1024 * 1024) - { + } else if (bytes < 1024 * 1024) { snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%.2f kB", bytes / 1024.0); - } - else if (bytes < 1024 * 1024 * 1024) - { + } 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)); + } else { + snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%.2f GB", + bytes / (1024.0 * 1024.0 * 1024.0)); } return fileSizeBuffer; } -void onWiFiEvent(WiFiEvent_t event) -{ +void onWiFiEvent(WiFiEvent_t event) { // Serial.printf("[WiFi-event] event: %d\n", event); - switch (event) - { + switch (event) { case ARDUINO_EVENT_WIFI_READY: ESP_LOGI(tag, "WiFi interface ready"); break; @@ -1132,51 +1104,41 @@ void onWiFiEvent(WiFiEvent_t event) } } -void Wifi_Start_MDNS(void) -{ +void Wifi_Start_MDNS(void) { ESP_LOGV(tag, "Initializing MDNS: %s", mDnsName.c_str()); - if (!MDNS.begin(mDnsName.c_str())) - { + if (!MDNS.begin(mDnsName.c_str())) { ESP_LOGE(tag, "Error setting up MDNS responder!"); - } - else - { + } else { ESP_LOGV(tag, "You can access device via http://%s.local", mDnsName); } } -bool writeFile(fs::FS &fs, const char *path, const char *message) -{ +bool writeFile(fs::FS &fs, const char *path, const char *message) { // Validate inputs - if (!path || !message) - { + if (!path || !message) { ESP_LOGE(tag, "Invalid parameters: path=%p message=%p", path, message); return false; } // Normalize and validate path String finalPath(path); - if (!finalPath.startsWith("/")) - { + if (!finalPath.startsWith("/")) { finalPath = String("/") + finalPath; } // Prevent directory traversal - if (finalPath.indexOf("..") >= 0) - { + if (finalPath.indexOf("..") >= 0) { ESP_LOGE(tag, "Rejected unsafe path: %s", finalPath.c_str()); return false; } // Collapse duplicate slashes (optional hardening) - while (finalPath.indexOf("//") >= 0) - { + while (finalPath.indexOf("//") >= 0) { finalPath.replace("//", "/"); } // Size checks const size_t MAX_FILE_SIZE = 1024 * 1024; // 1MB cap (aligned with readFile) const size_t totalSize = strlen(message); - if (totalSize > MAX_FILE_SIZE) - { + if (totalSize > MAX_FILE_SIZE) { ESP_LOGE(tag, "Write too large: %u bytes for %s", (unsigned)totalSize, finalPath.c_str()); return false; } @@ -1184,8 +1146,7 @@ bool writeFile(fs::FS &fs, const char *path, const char *message) // Write to a temporary file first for atomicity String tmpPath = finalPath + ".tmp"; File tmp = fs.open(tmpPath.c_str(), "w"); - if (!tmp) - { + if (!tmp) { ESP_LOGE(tag, "Failed to open temp file: %s", tmpPath.c_str()); return false; } @@ -1193,12 +1154,11 @@ bool writeFile(fs::FS &fs, const char *path, const char *message) // Write in a loop to ensure all bytes are written size_t written = 0; const uint8_t *buf = reinterpret_cast(message); - while (written < totalSize) - { + while (written < totalSize) { size_t n = tmp.write(buf + written, totalSize - written); - if (n == 0) - { - ESP_LOGE(tag, "Write failed to temp file: %s at %u/%u bytes", tmpPath.c_str(), (unsigned)written, (unsigned)totalSize); + if (n == 0) { + ESP_LOGE(tag, "Write failed to temp file: %s at %u/%u bytes", tmpPath.c_str(), + (unsigned)written, (unsigned)totalSize); tmp.close(); fs.remove(tmpPath.c_str()); return false; @@ -1211,17 +1171,14 @@ bool writeFile(fs::FS &fs, const char *path, const char *message) tmp.close(); // Replace the target file atomically: remove existing then rename - if (fs.exists(finalPath.c_str())) - { - if (!fs.remove(finalPath.c_str())) - { + if (fs.exists(finalPath.c_str())) { + if (!fs.remove(finalPath.c_str())) { ESP_LOGE(tag, "Failed to remove existing file: %s", finalPath.c_str()); fs.remove(tmpPath.c_str()); return false; } } - if (!fs.rename(tmpPath.c_str(), finalPath.c_str())) - { + if (!fs.rename(tmpPath.c_str(), finalPath.c_str())) { ESP_LOGE(tag, "Failed to rename %s to %s", tmpPath.c_str(), finalPath.c_str()); fs.remove(tmpPath.c_str()); return false; @@ -1231,30 +1188,26 @@ bool writeFile(fs::FS &fs, const char *path, const char *message) return true; } -char *readFile(fs::FS &fs, const char *path) -{ +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) - { + if (!path) { ESP_LOGE(tag, "Invalid path parameter"); return nullptr; } // Open file File file = fs.open(path, "r"); - if (!file || file.isDirectory()) - { + 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) - { + if (fileSize == 0 || fileSize > MAX_FILE_SIZE) { ESP_LOGE(tag, "Invalid file size: %u bytes", fileSize); file.close(); return nullptr; @@ -1262,8 +1215,7 @@ char *readFile(fs::FS &fs, const char *path) // Allocate memory char *fileContent = new (std::nothrow) char[fileSize + 1]; - if (!fileContent) - { + if (!fileContent) { ESP_LOGE(tag, "Memory allocation failed for size: %u", fileSize + 1); file.close(); return nullptr; @@ -1273,8 +1225,7 @@ char *readFile(fs::FS &fs, const char *path) size_t bytesRead = file.readBytes(fileContent, fileSize); file.close(); - if (bytesRead != fileSize) - { + if (bytesRead != fileSize) { ESP_LOGE(tag, "Read failed: expected %u bytes, got %u", fileSize, bytesRead); delete[] fileContent; return nullptr; @@ -1286,14 +1237,12 @@ char *readFile(fs::FS &fs, const char *path) return fileContent; } -String getSoftAPMacAddress() -{ +String getSoftAPMacAddress() { uint8_t mac[6]; WiFi.softAPmacAddress(mac); String macString = ""; - for (int i = 0; i < 6; i++) - { + for (int i = 0; i < 6; i++) { macString += String(mac[i], HEX); if (i < 5) macString += ":"; @@ -1301,47 +1250,107 @@ String getSoftAPMacAddress() return macString; } -String listDirAsHtml(String directoryList[], int count) -{ - String listedFiles; +String listDirAsHtml(String directoryList[], int count) { + // Validate input parameters + if (!directoryList || count <= 0 || count > MAX_DIRECTORIES) { + ESP_LOGW(tag, "Invalid parameters: directoryList=%p, count=%d", directoryList, count); + return String(); + } - for (int i = 0; i < count; i++) - { - // directory html - listedFiles += "Dir: "; - listedFiles += directoryList[i]; - listedFiles += "/-\n"; + // Pre-calculate estimated size to reduce reallocations + // Estimate: ~150 bytes per directory + ~120 bytes per file (conservative) + size_t estimatedSize = count * 300 + 512; // Extra buffer for safety + String result; + result.reserve(estimatedSize); - filesDropdownOptions += "\n"; + // Track statistics for debugging + int totalFiles = 0; + int totalDirs = 0; + int skippedDirs = 0; - dirDropdownOptions += "\n"; + for (int i = 0; i < count; i++) { + // Validate and normalize directory path + if (directoryList[i].isEmpty()) { + ESP_LOGD(tag, "Skipping empty directory path at index %d", i); + continue; + } - File dir = LittleFS.open(directoryList[i]); + String dirPath = directoryList[i]; + + // Security check: prevent directory traversal + if (dirPath.indexOf("..") >= 0) { + ESP_LOGW(tag, "Skipping directory with traversal attempt: %s", dirPath.c_str()); + skippedDirs++; + continue; + } + + // Normalize path efficiently + if (!dirPath.startsWith("/")) { + dirPath = "/" + dirPath; + } + + // Replace multiple slashes with single slash (more efficient than while loop) + dirPath.replace("//", "/"); + if (dirPath.indexOf("//") >= 0) { + // If still contains //, do more thorough cleanup + while (dirPath.indexOf("//") >= 0) { + dirPath.replace("//", "/"); + } + } + + // Generate directory row HTML with better formatting + result += "📁 "; + result += dirPath; + result += "/-\n"; + totalDirs++; + + // Open directory with error handling + File dir = LittleFS.open(dirPath); + if (!dir) { + ESP_LOGW(tag, "Cannot open directory: %s", dirPath.c_str()); + skippedDirs++; + continue; + } + + if (!dir.isDirectory()) { + ESP_LOGW(tag, "Path is not a directory: %s", dirPath.c_str()); + dir.close(); + skippedDirs++; + continue; + } + + // Process files in directory 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"; + while (file) { + if (!file.isDirectory()) { + String fileName = file.name(); + String filePath = file.path(); + size_t fileSize = file.size(); + + // Validate file data + if (fileName.isEmpty()) { + ESP_LOGD(tag, "Skipping file with empty name in %s", dirPath.c_str()); + file = dir.openNextFile(); + continue; + } + // Build file row HTML efficiently + result += "  📄 "; + result += fileName; + result += ""; + result += convertFileSize(fileSize); + result += "\n"; + + totalFiles++; + + // Update global dropdown options (maintaining compatibility) filesDropdownOptions += "\n"; @@ -1349,196 +1358,173 @@ String listDirAsHtml(String directoryList[], int count) file = dir.openNextFile(); } dir.close(); + + // Update directory dropdown options (maintaining compatibility) + dirDropdownOptions += "\n"; } - return listedFiles; + // Log summary for debugging + ESP_LOGD(tag, "Listed %d directories (%d skipped), %d files. HTML size: %d bytes", + totalDirs, skippedDirs, totalFiles, result.length()); + + return result; } /******************** Specific Html Processors ********************/ // file manager html processor -String fileManagerHtmlProcessor(const String &var) -{ - if (var == "ALLOWED_EXTENSIONS_EDIT") - { +String fileManagerHtmlProcessor(const String &var) { + if (var == "ALLOWED_EXTENSIONS_EDIT") { return allowedExtensionsForEdit; } - if (var == "FS_FREE_BYTES") - { + if (var == "FS_FREE_BYTES") { return convertFileSize(LittleFS.totalBytes() - LittleFS.usedBytes()); } - if (var == "FS_USED_BYTES") - { + if (var == "FS_USED_BYTES") { return convertFileSize(LittleFS.usedBytes()); } - if (var == "FS_TOTAL_BYTES") - { + if (var == "FS_TOTAL_BYTES") { return convertFileSize(LittleFS.totalBytes()); } - if (var == "RAM_FREE_BYTES") - { + if (var == "RAM_FREE_BYTES") { return convertFileSize(LittleFS.totalBytes()); } - if (var == "RAM_USED_BYTES") - { + if (var == "RAM_USED_BYTES") { return convertFileSize(LittleFS.totalBytes()); } - if (var == "RAM_TOTAL_BYTES") - { + if (var == "RAM_TOTAL_BYTES") { return convertFileSize(LittleFS.totalBytes()); } - if (var == "LISTED_FILES") - { + if (var == "LISTED_FILES") { filesDropdownOptions = ""; // clear out dirDropdownOptions = ""; // clear out - String directories[MAX_DIRECTORIES]; + String *directories = new (std::nothrow) String[MAX_DIRECTORIES]; + if (!directories) { + ESP_LOGE(tag, "Failed to allocate directories array"); + return String(); + } int dirCount = 0; getAllDirectories(directories, dirCount); - return listDirAsHtml(directories, dirCount); + String out = listDirAsHtml(directories, dirCount); + delete[] directories; + return out; } - if (var == "EDIT-DEL_FILES") - { + if (var == "EDIT-DEL_FILES") { return filesDropdownOptions; } - if (var == "DIR_LIST") - { + if (var == "DIR_LIST") { return dirDropdownOptions; } - if (var == "SAVE_PATH_INPUT") - { + if (var == "SAVE_PATH_INPUT") { return savePath; } - if (var == "FIRM_VER") - { + if (var == "FIRM_VER") { return localVersion.toString(); } return var; } -String HomeHtmlProcessor(const String &var) -{ - if (var == "APP_NAME") - { +String HomeHtmlProcessor(const String &var) { + if (var == "APP_NAME") { // return sysProps.appName; return "N/A"; } - if (var == "OLED") - { + if (var == "OLED") { return "N/A"; } - if (var == "STRIP1") - { + if (var == "STRIP1") { // return (strip1) ? "Yes" : "No"; return "N/A"; } - if (var == "STRIP2") - { + if (var == "STRIP2") { // return (strip2) ? "Yes" : "No"; return "N/A"; } - if (var == "FRONT_LIGHT") - { + if (var == "FRONT_LIGHT") { // return (animProps.frontLight.enabled) ? "Yes" : "No"; return "N/A"; } - if (var == "REAR_LIGHT") - { + if (var == "REAR_LIGHT") { // return (animProps.rearLight.enabled) ? "Yes" : "No"; return "N/A"; } - if (var == "FIRMWARE") - { + if (var == "FIRMWARE") { return localVersion.toString(); } - if (var == "BOOTH_T") - { + if (var == "BOOTH_T") { // return String(sysProps.t_sensor.temperature) + "F"; return "N/A"; } - if (var == "SETPOINT") - { + if (var == "SETPOINT") { // return String(sysProps.t_sensor.Setpoint1) + "F"; return "N/A"; } - if (var == "FLASH_SIZE") - { + if (var == "FLASH_SIZE") { return convertFileSize(ESP.getSketchSize()); } - if (var == "FLASH_FREE") - { + if (var == "FLASH_FREE") { return convertFileSize(ESP.getFreeSketchSpace()); } - if (var == "HEAP_SIZE") - { + if (var == "HEAP_SIZE") { return convertFileSize(ESP.getHeapSize()); } - if (var == "HEAP_FREE") - { + if (var == "HEAP_FREE") { return convertFileSize(ESP.getFreeHeap()); } - if (var == "CPU_FREQ") - { + if (var == "CPU_FREQ") { return String(ESP.getCpuFreqMHz()) + "Mhz"; } - if (var == "IP") - { + if (var == "IP") { return WiFi.localIP().toString(); } - if (var == "MAC") - { + if (var == "MAC") { return chipInfo.macStr; } - if (var == "SSID") - { + if (var == "SSID") { return WiFi.SSID(); } - if (var == "RSSI") - { + if (var == "RSSI") { return String(WiFi.RSSI()); } - if (var == "WIFI_CH") - { + if (var == "WIFI_CH") { return String(WiFi.channel()); } - if (var == "ENCRYP") - { + if (var == "ENCRYP") { return String(WiFi.encryptionType(0)); } - if (var == "AP_SSID") - { + if (var == "AP_SSID") { return WiFi.softAPSSID(); } - if (var == "AP_CLIENTS") - { + if (var == "AP_CLIENTS") { return String(WiFi.softAPgetStationNum()); } - if (var == "BLE") - { + if (var == "BLE") { return (commMode == COMM_WIFI_AP_BLE) ? "Yes" : "No"; } - if (var == "BLE_SSID") - { + if (var == "BLE_SSID") { // return (commMode == COMM_WIFI_AP_BLE) ? BLEDeviceName : ""; return "N/A"; } - if (var == "BLE_CLIENTS") - { + if (var == "BLE_CLIENTS") { // return (BTDeviceConnected) ? "1" : "0"; return "N/A"; } - if (var == "AP_MAC") - { + if (var == "AP_MAC") { return getSoftAPMacAddress(); } @@ -1546,8 +1532,7 @@ String HomeHtmlProcessor(const String &var) return var; } -void handleUpdateProgress(AsyncWebServerRequest *request) -{ +void handleUpdateProgress(AsyncWebServerRequest *request) { static const char *tag = "UpdateProgress"; // if (!request->authenticate(http_username, http_password)) { diff --git a/temporary/AppUpgrade copy.cpp b/temporary/AppUpgrade copy.cpp deleted file mode 100644 index e69de29..0000000 diff --git a/temporary/AppUpgrade orig.cpp b/temporary/AppUpgrade orig.cpp deleted file mode 100644 index 91e6c90..0000000 --- a/temporary/AppUpgrade orig.cpp +++ /dev/null @@ -1,673 +0,0 @@ -#include "AppUpgrade.h" -#include "esp_log.h" -#include -#include -#include -#include "global.h" -#include "JsonConstrain.h" -#include "BLE_UpdateService.h" -#include -#include - -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), manifestName(manifestName), appName(appBin), fileSystem(fs), downloadBuffer(new uint8_t[BUFFER_SIZE]) -{ - baseUrl = bucket ? String(bucket) : String(DEFAULT_MANIFEST_URL); - // Ensure baseUrl ends with a single '/' - if(!baseUrl.endsWith("/")) baseUrl += "/"; - ESP_LOGI(TAG, "AppUpdater initialized (local v%s) baseUrl=%s", localVersion.toString().c_str(), baseUrl.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 = buildUrl(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; - // Only mark update available if remote is strictly newer than local - 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 = buildUrl(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); - - if (contentLength > 0) { - // Single pass with known content length - 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(); - } - } else { - // Unknown content length: read until stream ends - for (;;) { - size_t readLen = stream->readBytes(downloadBuffer.get(), BUFFER_SIZE); - if (readLen == 0) { - break; - } - 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; - // Progress unknown; emit periodic heartbeats at 0% - updateProgress(UpdateStatus::DOWNLOADING, 0, 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 = buildUrl(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 > 0 ? firmwareSize : UPDATE_SIZE_UNKNOWN)) { - 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(); - if (firmwareSize > 0) { - 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"); - } - } else { - // Unknown size: stream until end - for (;;) { - size_t read = stream->readBytes(downloadBuffer.get(), BUFFER_SIZE); - if (read == 0) break; - md5.add(downloadBuffer.get(), read); - if (Update.write(downloadBuffer.get(), read) != read) { - ESP_LOGE(TAG, "Write failed"); - Update.abort(); - http.end(); - return false; - } - updateProgress(UpdateStatus::DOWNLOADING, 0, "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; -} - -String AppUpdater::buildUrl(const char* path) const { - if(!path || !*path) return baseUrl; // just base - String p(path); - // If already absolute URL, pass through - if(p.startsWith("http://") || p.startsWith("https://")) return p; - // Strip leading slashes to avoid double - while(p.startsWith("/")) p.remove(0,1); - // Ensure baseUrl has single trailing slash - String b = baseUrl; - if(!b.endsWith("/")) b += "/"; - return b + p; -} - - -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 folderName = jsonConstrainString(TAG, jObj, "folder", "latest/"); - String baseUrl = jsonConstrainString(TAG, jObj, "baseurl", "https://s3-minio.boothwizard.com/boothifier/"); - 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; - - const char* safeMsg = message ? message : ""; - 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%%", safeMsg, percentage); - msg = buffer; - break; - case AppUpdater::UpdateStatus::VERIFYING: - snprintf(buffer, sizeof(buffer), "%s: Verifying update: %d%%", safeMsg, percentage); - msg = buffer; - break; - case AppUpdater::UpdateStatus::FILE_SKIPPED: - snprintf(buffer, sizeof(buffer), "%s: Skipping file update, already up to date", safeMsg); - msg = buffer; - break; - case AppUpdater::UpdateStatus::FILE_SAVED: - snprintf(buffer, sizeof(buffer), "%s: File Saved", safeMsg); - msg = buffer; - break; - case AppUpdater::UpdateStatus::MD5_FAILED: - snprintf(buffer, sizeof(buffer), "%s: MD5 Verification Failed", safeMsg); - 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", safeMsg); - 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/temporary/AppUpgrade_orig.cpp b/temporary/AppUpgrade_orig.cpp deleted file mode 100644 index 2d72a74..0000000 --- a/temporary/AppUpgrade_orig.cpp +++ /dev/null @@ -1,716 +0,0 @@ -#include "AppUpgrade.h" -#include "esp_log.h" -#include -#include -#include -#include "global.h" -#include "JsonConstrain.h" -#include "BLE_UpdateService.h" -#include -#include -#include - -static const char* TAG = "AppUpdater"; -TaskHandle_t Update_Task_Handle = NULL; -TaskHandle_t versionCheckTask_Handle = NULL; -volatile bool g_UpdateCancelFlag = false; // cancellation flag -String updateUrl = ""; -Version otaVersion; - - -AppUpdater::AppUpdater(fs::FS& fs, Version localVersion, const char* bucket, const char* manifestName, const char* appBin) - : localVersion(localVersion), manifestName(manifestName), appName(appBin), fileSystem(fs), downloadBuffer(new uint8_t[BUFFER_SIZE]) -{ - baseUrl = bucket ? String(bucket) : String(DEFAULT_MANIFEST_URL); - // Ensure baseUrl ends with a single '/' - if(!baseUrl.endsWith("/")) baseUrl += "/"; - ESP_LOGI(TAG, "AppUpdater initialized (local v%s) baseUrl=%s", localVersion.toString().c_str(), baseUrl.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); - } -} - -AppUpdater::ManifestCheckResult AppUpdater::checkManifest() { - String url = buildUrl(manifestName); - ESP_LOGD(TAG, "Fetching manifest from: %s", url.c_str()); - - String payload; - for(int attempt=0; attempt MAX_MANIFEST_SIZE){ - ESP_LOGE(TAG, "Manifest too large (%u bytes)", (unsigned)payload.length()); - return ManifestCheckResult::ERROR_TOO_LARGE; - } - - // 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 ManifestCheckResult::ERROR_PARSE_FAILED; - } - - // Check for files section - jsonFilesArray = jsonManifest["files"]; - if (jsonFilesArray.isNull()) { - ESP_LOGE(TAG, "No files section in manifest"); - return ManifestCheckResult::ERROR_NO_FILES_SECTION; - }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 ManifestCheckResult::ERROR_NO_VERSION; - } - - // 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; - // Only mark update available if remote is strictly newer than local - if (otaVersion <= localVersion) { - ESP_LOGI(TAG, "No updates available: remote=%s, local=%s", - otaVersion.toString().c_str(), localVersion.toString().c_str()); - return ManifestCheckResult::VERSION_CURRENT; - }else{ - updateAvailable = true; - ESP_LOGI(TAG, "Update available: remote=%s, local=%s", - otaVersion.toString().c_str(), localVersion.toString().c_str()); - } - - //ESP_LOGD(TAG, "Manifest content: %s", payload.c_str()); - - return ManifestCheckResult::UPDATE_AVAILABLE; -} - -bool AppUpdater::updateFile(const char* remotePath, const char* localPath, const char* expectedMd5) { - //updateProgress(UpdateStatus::DOWNLOADING, 0, localPath); - - // Construct full URL - String url = buildUrl(remotePath); - ESP_LOGD(TAG, "Downloading: %s -> %s", url.c_str(), localPath); - - // Quick skip: if exists and size & MD5 match - bool skip = false; - if(fileSystem.exists(localPath)){ - String localMd5 = getLocalMD5(localPath); - if(localMd5.equals(expectedMd5)) skip = true; - } - if(skip){ - ESP_LOGI(TAG, "File already up to date: %s", localPath); - updateProgress(UpdateStatus::FILE_SKIPPED, 100, localPath); - return true; - } - - // Start the download - HTTPClient http; - int httpCode = -1; - for(int attempt=0; attempt 0) { - // Single pass with known content length - while (totalRead < contentLength) { - if(g_UpdateCancelFlag){ file.close(); fileSystem.remove(tempPath.c_str()); return false; } - 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 * 80) / contentLength , localPath); - } - yield(); - } - } else { - // Unknown content length: read until stream ends - for (;;) { - if(g_UpdateCancelFlag){ file.close(); fileSystem.remove(tempPath.c_str()); return false; } - size_t readLen = stream->readBytes(downloadBuffer.get(), BUFFER_SIZE); - if (readLen == 0) { - break; - } - 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; - // Progress unknown; emit periodic heartbeats at 0% - // For unknown size, send heartbeats every ~16KB - if((totalRead & 0x3FFF) == 0){ - updateProgress(UpdateStatus::DOWNLOADING, 0, localPath); - } - yield(); - } - } - - file.close(); - md5.calculate(); - String calculatedMd5 = md5.toString(); - - // Verify MD5 hash - updateProgress(UpdateStatus::VERIFYING, 90, localPath); - if (!calculatedMd5.equals(expectedMd5)) { - //ESP_LOGE(TAG, "MD5 mismatch for %s", localPath); - fileSystem.remove(tempPath.c_str()); - return false; - } - - updateProgress(UpdateStatus::VERIFYING, 95, localPath); - - // 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::VERIFYING, 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["path"]; - const char* localPath = remotePath; - // If path begins with "data/" or "/data/" strip only the "data" portion, retaining the leading slash - if (localPath) { - if (strncmp(localPath, "data/", 5) == 0) { - localPath += 4; // points to '/' - } else if (strncmp(localPath, "/data/", 6) == 0) { - localPath += 5; // points to '/' - } - } - 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 = buildUrl(appName); - - // Download the firmware - HTTPClient http; - int httpCode = -1; - for(int attempt=0; attempt 0 ? firmwareSize : UPDATE_SIZE_UNKNOWN)) { - 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(); - if (firmwareSize > 0) { - size_t remaining = firmwareSize; - while (remaining > 0) { - if(g_UpdateCancelFlag){ Update.abort(); http.end(); return false; } - 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"); - } - } else { - // Unknown size: stream until end - for (;;) { - if(g_UpdateCancelFlag){ Update.abort(); http.end(); return false; } - size_t read = stream->readBytes(downloadBuffer.get(), BUFFER_SIZE); - if (read == 0) break; - md5.add(downloadBuffer.get(), read); - if (Update.write(downloadBuffer.get(), read) != read) { - ESP_LOGE(TAG, "Write failed"); - Update.abort(); - http.end(); - return false; - } - updateProgress(UpdateStatus::DOWNLOADING, 0, "firmware"); - } - } - - // Verify MD5 - md5.calculate(); - String calculatedMd5 = md5.toString(); - updateProgress(UpdateStatus::VERIFYING, 95, "firmware"); - 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, 100, "Firmware: Complete"); - return true; -} - -bool AppUpdater::IsUpdateAvailable(){ - return updateAvailable; -} - -String AppUpdater::buildUrl(const char* path) const { - if(!path || !*path) return baseUrl; // just base - String p(path); - // If already absolute URL, pass through - if(p.startsWith("http://") || p.startsWith("https://")) return p; - // Strip leading slashes to avoid double - while(p.startsWith("/")) p.remove(0,1); - // Ensure baseUrl has single trailing slash - String b = baseUrl; - if(!b.endsWith("/")) b += "/"; - return b + p; -} - - -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(), "manifest.json", "firmware.bin"); - updater->setProgressCallback(updateProgress); - - ESP_LOGI(TAG, "Starting update check from: %s", updateUrl.c_str()); - - // Check and perform updates - auto manifestResult = updater->checkManifest(); - - if (manifestResult != AppUpdater::ManifestCheckResult::UPDATE_AVAILABLE) { - // Handle different error cases - std::string errorMsg; - switch (manifestResult) { - case AppUpdater::ManifestCheckResult::ERROR_FETCH_FAILED: - errorMsg = "Failed to fetch manifest"; - break; - case AppUpdater::ManifestCheckResult::ERROR_TOO_LARGE: - errorMsg = "Manifest file too large"; - break; - case AppUpdater::ManifestCheckResult::ERROR_PARSE_FAILED: - errorMsg = "Failed to parse manifest"; - break; - case AppUpdater::ManifestCheckResult::ERROR_NO_FILES_SECTION: - errorMsg = "Manifest missing files section"; - break; - case AppUpdater::ManifestCheckResult::ERROR_NO_VERSION: - errorMsg = "Manifest missing version section"; - break; - case AppUpdater::ManifestCheckResult::VERSION_CURRENT: - errorMsg = "Current version is up to date"; - // This is not actually an error - ESP_LOGI(TAG, "No update needed: %s", errorMsg.c_str()); - throw std::runtime_error(errorMsg); - break; - default: - errorMsg = "Unknown manifest check error"; - } - throw std::runtime_error(errorMsg); - } - - 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()); - } - 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(); - } - AppUpdater updater(LittleFS, localVersion, updateUrl.c_str(), "manifest.json", "firmware.bin"); - - auto manifestResult = updater.checkManifest(); - - if (manifestResult == AppUpdater::ManifestCheckResult::UPDATE_AVAILABLE || - manifestResult == AppUpdater::ManifestCheckResult::VERSION_CURRENT) { - otaVersion = updater.otaVersion; // capture remote - ESP_LOGI(TAG, "Version check: remote=%s", otaVersion.toString().c_str()); - } else { - ESP_LOGE(TAG, "Version check: manifest check failed with code %d", static_cast(manifestResult)); - } - - 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 folderName = jsonConstrainString(TAG, jObj, "folder", "latest/"); - String baseUrl = jsonConstrainString(TAG, jObj, "baseurl", "https://s3-minio.boothwizard.com/boothifier/"); - 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; - - const char* safeMsg = message ? message : ""; - 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%%", safeMsg, percentage); - msg = buffer; - break; - case AppUpdater::UpdateStatus::VERIFYING: - snprintf(buffer, sizeof(buffer), "%s: Verifying update: %d%%", safeMsg, percentage); - msg = buffer; - break; - case AppUpdater::UpdateStatus::FILE_SKIPPED: - snprintf(buffer, sizeof(buffer), "%s: File Skipped, up to date", safeMsg); - msg = buffer; - break; - case AppUpdater::UpdateStatus::FILE_SAVED: - snprintf(buffer, sizeof(buffer), "%s: File Saved", safeMsg); - msg = buffer; - break; - case AppUpdater::UpdateStatus::MD5_FAILED: - snprintf(buffer, sizeof(buffer), "%s: MD5 Verification Failed", safeMsg); - 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", safeMsg); - 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) { - // This is for the web client and not the BLE client - 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); -} - -// (Removed duplicate global checkManifest; AppUpdater::checkManifest used instead) - -/* -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/temporary/Temp/ATALights2.cpp b/temporary/Temp/ATALights2.cpp deleted file mode 100644 index d64a258..0000000 --- a/temporary/Temp/ATALights2.cpp +++ /dev/null @@ -1,178 +0,0 @@ -#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/BLE-FlashStick-Service.h b/temporary/Temp/BLE-FlashStick-Service.h deleted file mode 100644 index 29260e8..0000000 --- a/temporary/Temp/BLE-FlashStick-Service.h +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - -#include -#include diff --git a/temporary/Temp/BLE-FlaskStick-Service.cpp b/temporary/Temp/BLE-FlaskStick-Service.cpp deleted file mode 100644 index 2172ffc..0000000 --- a/temporary/Temp/BLE-FlaskStick-Service.cpp +++ /dev/null @@ -1,157 +0,0 @@ -#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/Temp/BTSerial.cpp b/temporary/Temp/BTSerial.cpp deleted file mode 100644 index 4295c4b..0000000 --- a/temporary/Temp/BTSerial.cpp +++ /dev/null @@ -1,240 +0,0 @@ -#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 deleted file mode 100644 index 1691151..0000000 --- a/temporary/Temp/BTSerial.h +++ /dev/null @@ -1,16 +0,0 @@ -#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/EventBoxTest.html b/temporary/Temp/EventBoxTest.html deleted file mode 100644 index 114f15e..0000000 --- a/temporary/Temp/EventBoxTest.html +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - -Event Container - - - -
-
-
- - - -
-
- - - - diff --git a/temporary/Temp/GenUpdate.py b/temporary/Temp/GenUpdate.py deleted file mode 100644 index 51c40b3..0000000 --- a/temporary/Temp/GenUpdate.py +++ /dev/null @@ -1,164 +0,0 @@ -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/temporary/Temp/MyNeoPixelBus.cpp b/temporary/Temp/MyNeoPixelBus.cpp deleted file mode 100644 index 74a81f6..0000000 --- a/temporary/Temp/MyNeoPixelBus.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#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/UploadToMinio.py b/temporary/Temp/UploadToMinio.py deleted file mode 100644 index 516301d..0000000 --- a/temporary/Temp/UploadToMinio.py +++ /dev/null @@ -1,337 +0,0 @@ -#!/usr/bin/env python3 -"""Upload firmware, manifest, and data assets to a MinIO (S3-compatible) bucket. - -Features preserved from original GCS script: - - Optional backup (copies existing objects under destination prefix to timestamped folder under backups/) - - Upload firmware.bin, update.json, and recursively mirror a data directory - - Cache-Control set to disable caching on clients - -Switches from google.cloud.storage to boto3 (S3 API) for MinIO compatibility. -""" -import os -import sys -import datetime -import json -from pathlib import Path - -try: - import boto3 - from botocore.exceptions import ClientError - from botocore.config import Config -except ImportError: - print("ERROR: boto3 is required. Install with: pip install boto3") - sys.exit(1) - -# ============================================================================= -# CONFIGURATION CONSTANTS (edit as needed or supply via environment variables) -# ============================================================================= - -CREATE_BACKUP = False -UPLOAD_FIRMWARE = False -UPLOAD_MANIFEST = True -UPLOAD_DATA = False - -# Bucket / endpoint configuration -BUCKET_NAME = os.getenv('MINIO_BUCKET', 'boothifier') -DESTINATION_DIR = os.getenv('MINIO_DEST_PREFIX', 'latest') # prefix inside bucket -BACKUPS_DIR = os.getenv('MINIO_BACKUPS_PREFIX', 'backups') - -LOCAL_ROOT_PATH = Path(__file__).parent.resolve() - -# Optional service account style JSON key (generated by MinIO Console). Expected fields: -# {"url":"https://minio.example.com/api/v1/service-account-credentials","accessKey":"...","secretKey":"...","api":"s3v4","path":"auto"} -MINIO_KEY_FILE = LOCAL_ROOT_PATH / 'minio-boothifier-key.json' - -# Defaults before loading file / env -_json_access = None -_json_secret = None -_json_url = None - -def _load_json_key(): - global _json_access, _json_secret, _json_url - try: - if MINIO_KEY_FILE.is_file(): - with open(MINIO_KEY_FILE, 'r', encoding='utf-8') as fh: - data = json.load(fh) - _json_access = data.get('accessKey') or None - _json_secret = data.get('secretKey') or None - _json_url = data.get('url') or None - except Exception as e: - print(f"WARN: Failed to load MinIO key file '{MINIO_KEY_FILE.name}': {e}") - -_load_json_key() - -def _derive_endpoint(url_value: str) -> str: - if not url_value: - return 'https://s3-minio.boothwizard.com' - # Remove known API suffix if present (/api/...) - # e.g. https://s3-minio.boothwizard.com/api/v1/service-account-credentials -> https://s3-minio.boothwizard.com - parts = url_value.split('/api/') - return parts[0] if parts else url_value - -# MinIO credentials with precedence: ENV > JSON file > fallback -MINIO_ENDPOINT = os.getenv('MINIO_ENDPOINT') or _derive_endpoint(_json_url) -MINIO_ACCESS_KEY = os.getenv('MINIO_ACCESS_KEY') or _json_access or 'CHANGE_ME_ACCESS' -MINIO_SECRET_KEY = os.getenv('MINIO_SECRET_KEY') or _json_secret or 'CHANGE_ME_SECRET' -MINIO_REGION = os.getenv('MINIO_REGION', 'us-east-1') # MinIO ignores but boto3 wants some value - - # Addressing / SSL options -MINIO_ADDRESSING = os.getenv('MINIO_ADDRESSING_STYLE', 'path').lower() # 'path' or 'virtual' -MINIO_VERIFY_SSL = os.getenv('MINIO_TLS_VERIFY', '1') not in ('0','false','no') -MINIO_DEBUG = os.getenv('MINIO_DEBUG', '0') in ('1','true','yes') -MINIO_ALLOW_VARIANTS = os.getenv('MINIO_ALLOW_ENDPOINT_VARIANTS', '0') in ('1','true','yes') # normally false with nginx redirect - -LOCAL_FIRMWARE_PATH = str(LOCAL_ROOT_PATH / 'latest' / 'firmware.bin') -LOCAL_MANIFEST_PATH = str(LOCAL_ROOT_PATH / 'latest' / 'update.json') -LOCAL_DATA_DIRECTORY = str(LOCAL_ROOT_PATH / 'latest' / 'data') - -# ============================================================================= -# HELPERS -# ============================================================================= - -def s3_client(): - """Create an S3 client pointed at MinIO endpoint, forcing path-style unless overridden, with short timeouts.""" - addressing = 'path' if MINIO_ADDRESSING not in ('virtual','auto') else 'virtual' - cfg = Config( - s3={'addressing_style': addressing}, - signature_version='s3v4', - connect_timeout=3, - read_timeout=5, - retries={'max_attempts': 2} - ) - if MINIO_DEBUG: - masked_key = (MINIO_ACCESS_KEY[:3] + '...' + MINIO_ACCESS_KEY[-3:]) if MINIO_ACCESS_KEY else 'None' - print(f"[DEBUG] Creating client: endpoint={MINIO_ENDPOINT} addressing={addressing} verifySSL={MINIO_VERIFY_SSL} region={MINIO_REGION} accessKey={masked_key}") - return boto3.client( - 's3', - endpoint_url=MINIO_ENDPOINT, - aws_access_key_id=MINIO_ACCESS_KEY, - aws_secret_access_key=MINIO_SECRET_KEY, - region_name=MINIO_REGION, - verify=MINIO_VERIFY_SSL, - config=cfg, - ) - -def _endpoint_variants(base: str): - """Return endpoint variants only if explicitly allowed; otherwise just the base (nginx handles forwarding).""" - if not MINIO_ALLOW_VARIANTS: - return [base] - # Fallback to previous expanded logic if variants are enabled - try: - variants = [] - if not base: - return variants - base = base.rstrip('/') - proto_sep = '://' - if proto_sep in base: - scheme, rest = base.split(proto_sep,1) - else: - scheme, rest = 'https', base - host_port = rest - if ':' in host_port: - host, port = host_port.split(':',1) - else: - host, port = host_port, '' - variants.append(f"{scheme}://{host_port}") - common_ports = ['9000','443','80'] - for p in common_ports: - if port != p: - variants.append(f"{scheme}://{host}:{p}") - alt_scheme = 'http' if scheme == 'https' else 'https' - variants.append(f"{alt_scheme}://{host_port}") - for p in common_ports: - if port != p: - variants.append(f"{alt_scheme}://{host}:{p}") - seen = set() - uniq = [] - for v in variants: - if v not in seen: - uniq.append(v) - seen.add(v) - return uniq - except Exception: - return [base] - -def create_validated_client(): - """Validate (or create) client using only provided endpoint unless variants enabled.""" - global MINIO_ENDPOINT - primary = MINIO_ENDPOINT - variants = _endpoint_variants(primary) or [primary] - errors = [] - probe_bucket = BUCKET_NAME # we will head the target bucket directly - for candidate in variants: - saved = MINIO_ENDPOINT - MINIO_ENDPOINT = candidate - if MINIO_DEBUG: - print(f"[DEBUG] Probing endpoint candidate: {candidate}") - try: - c = s3_client() - try: - c.head_bucket(Bucket=probe_bucket) - if MINIO_DEBUG: - print(f"[DEBUG] head_bucket succeeded on {candidate} for '{probe_bucket}'.") - return c - except ClientError as e: - msg = str(e) - # Acceptable if bucket not found (we can create later) - if any(code in msg for code in ('404', 'NoSuchBucket', 'NotFound')): - if MINIO_DEBUG: - print(f"[DEBUG] Bucket not found on {candidate} (expected if first deploy). Using this endpoint.") - return c - if 'API Requests must be made to API port' in msg: - errors.append(f"{candidate}: wrong port (console endpoint)") - else: - errors.append(f"{candidate}: {msg}") - MINIO_ENDPOINT = saved - except Exception as ex: - errors.append(f"{candidate}: {ex}") - MINIO_ENDPOINT = saved - continue - print("ERROR: Could not validate any endpoint candidate.") - for e in errors: - print(' - ' + e) - print("Provide correct API endpoint (e.g. https://host:9000) via MINIO_ENDPOINT env var.") - sys.exit(3) - -def list_objects(client, prefix: str): - """Generator yielding object keys under a prefix (non-recursive listing with pagination).""" - kwargs = {'Bucket': BUCKET_NAME, 'Prefix': prefix} - while True: - resp = client.list_objects_v2(**kwargs) - for obj in resp.get('Contents', []): - yield obj['Key'] - if not resp.get('IsTruncated'): - break - kwargs['ContinuationToken'] = resp['NextContinuationToken'] - -def normalize_prefix(p: str) -> str: - p = p.strip('/') - return p - -def join_key(*parts: str) -> str: - parts_clean = [p.strip('/') for p in parts if p is not None and p != ''] - return '/'.join(parts_clean) - -def backup_existing_files(client, destination_prefix: str, backups_prefix: str, backup_folder: str): - if not destination_prefix: - prefix = '' - else: - prefix = destination_prefix + '/' - print(f"Scanning existing objects under '{prefix}' for backup...") - for key in list_objects(client, prefix): - if backups_prefix and key.startswith(backups_prefix + '/'): # Skip prior backups - continue - # relative path within destination - relative = key[len(prefix):] if prefix and key.startswith(prefix) else key - backup_key = join_key(backups_prefix, backup_folder, relative) - print(f"Backup copy: {key} -> {backup_key}") - client.copy_object( - Bucket=BUCKET_NAME, - CopySource={'Bucket': BUCKET_NAME, 'Key': key}, - Key=backup_key, - MetadataDirective='COPY' - ) - -def upload_file(client, local_path: str, key: str, cache_control: str = 'private, max-age=0, no-transform'): - if not os.path.isfile(local_path): - print(f"WARN: File missing, skipping: {local_path}") - return - print(f"Upload: {local_path} -> s3://{BUCKET_NAME}/{key}") - extra_args = { 'CacheControl': cache_control } - client.upload_file(local_path, BUCKET_NAME, key, ExtraArgs=extra_args) - -def upload_directory(client, local_directory: str, destination_prefix: str): - if not os.path.isdir(local_directory): - print(f"WARN: Data directory missing: {local_directory}") - return - for root, _, files in os.walk(local_directory): - for fname in files: - full = os.path.join(root, fname) - rel = os.path.relpath(full, local_directory) - key = join_key(destination_prefix, rel) - upload_file(client, full, key) - -def ensure_bucket(client): - """Ensure bucket exists; provide diagnostics if HeadBucket returns 400/other errors.""" - try: - client.head_bucket(Bucket=BUCKET_NAME) - if MINIO_DEBUG: - print(f"[DEBUG] Bucket '{BUCKET_NAME}' exists.") - return - except ClientError as e: - code = e.response.get('Error', {}).get('Code') - status = e.response.get('ResponseMetadata', {}).get('HTTPStatusCode') - print(f"HeadBucket failed (code={code}, status={status}).") - - # List buckets for diagnostics - try: - resp = client.list_buckets() - bucket_names = [b['Name'] for b in resp.get('Buckets', [])] - print(f"Available buckets: {bucket_names or 'None'}") - except Exception as le: - print(f"WARN: list_buckets failed: {le}") - - if code in ('404', 'NoSuchBucket', 'NotFound'): - print(f"Bucket '{BUCKET_NAME}' not found. Attempting to create...") - try: - client.create_bucket(Bucket=BUCKET_NAME) - print(f"Created bucket '{BUCKET_NAME}'.") - return - except ClientError as ce: - print(f"ERROR: Cannot create bucket: {ce}") - sys.exit(2) - - if status == 400: - print("HINTS: \n - Verify endpoint URL (MINIO_ENDPOINT).\n - Ensure no trailing slash in endpoint.\n - Check that TLS verify matches server cert (set MINIO_TLS_VERIFY=0 to test).\n - Confirm bucket name is correct and DNS compatible.\n - Credentials may lack permission: verify access key policies.") - - # Retry once forcing path style if not already - if MINIO_ADDRESSING != 'path': - print("Retrying with path-style addressing...") - os.environ['MINIO_ADDRESSING_STYLE'] = 'path' - new_client = s3_client() - try: - new_client.head_bucket(Bucket=BUCKET_NAME) - print("Second attempt succeeded with path-style addressing.") - return - except ClientError as e2: - print(f"Second HeadBucket attempt failed: {e2}") - print(f"ERROR: head_bucket ultimately failed: {e}") - sys.exit(2) - -# ============================================================================= -# MAIN -# ============================================================================= - -def main(): - dest_prefix = normalize_prefix(DESTINATION_DIR) - backups_prefix = normalize_prefix(BACKUPS_DIR) if BACKUPS_DIR else '' - client = create_validated_client() - if MINIO_DEBUG: - print("[DEBUG] Starting ensure_bucket phase...") - ensure_bucket(client) - - if CREATE_BACKUP: - ts = datetime.datetime.now().strftime('%Y%m%d_%H%M%S') - backup_folder = f"backup_{ts}" - print(f"Creating backup under '{backups_prefix}/{backup_folder}' from prefix '{dest_prefix}'") - backup_existing_files(client, dest_prefix, backups_prefix, backup_folder) - - # Firmware - if UPLOAD_FIRMWARE: - firmware_key = join_key(dest_prefix, 'firmware.bin') if dest_prefix else 'firmware.bin' - upload_file(client, LOCAL_FIRMWARE_PATH, firmware_key) - - # Manifest - if UPLOAD_MANIFEST: - manifest_key = join_key(dest_prefix, 'update.json') if dest_prefix else 'update.json' - upload_file(client, LOCAL_MANIFEST_PATH, manifest_key) - - # Data directory - if UPLOAD_DATA: - data_prefix = join_key(dest_prefix, 'data') if dest_prefix else 'data' - upload_directory(client, LOCAL_DATA_DIRECTORY, data_prefix) - - print("All uploads complete.") - -if __name__ == '__main__': - main() diff --git a/temporary/Temp/appcontrol old.html b/temporary/Temp/appcontrol old.html deleted file mode 100644 index 3033a2a..0000000 --- a/temporary/Temp/appcontrol old.html +++ /dev/null @@ -1,623 +0,0 @@ -{{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/Temp/ata-boothifier-upgrade(copy).html b/temporary/Temp/ata-boothifier-upgrade(copy).html deleted file mode 100644 index e69de29..0000000 diff --git a/temporary/Temp/ata-boothifier-upgrade.html b/temporary/Temp/ata-boothifier-upgrade.html deleted file mode 100644 index 3327522..0000000 --- a/temporary/Temp/ata-boothifier-upgrade.html +++ /dev/null @@ -1,508 +0,0 @@ - - - - - - ATA Firmware Update - - - - -

ATA Firmware Update

- - -
- - -
- -
- - -
- -
- - -
- -
- -
-
- -
- - -
- - -
- - - - -
- - -
- - -
- - -
- - -
-
- - -
- -
- - - - diff --git a/temporary/Temp/ata-boothifier-upgradeV2.html b/temporary/Temp/ata-boothifier-upgradeV2.html deleted file mode 100644 index d95fd0b..0000000 --- a/temporary/Temp/ata-boothifier-upgradeV2.html +++ /dev/null @@ -1,547 +0,0 @@ - - - - - - ATA Firmware Update - - - - -

ATA Firmware Update

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

App Control - Bluetooth Configuration

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

Countdown Animation

-
- - - - -
-

Selection Screen

-
- -
- - - diff --git a/temporary/Temp/command_processor.cpp b/temporary/Temp/command_processor.cpp deleted file mode 100644 index 8f4747c..0000000 --- a/temporary/Temp/command_processor.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#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 deleted file mode 100644 index 2367231..0000000 --- a/temporary/Temp/command_processor.h +++ /dev/null @@ -1,31 +0,0 @@ -#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/eventTest.htm b/temporary/Temp/eventTest.htm deleted file mode 100644 index b30e777..0000000 --- a/temporary/Temp/eventTest.htm +++ /dev/null @@ -1,145 +0,0 @@ - - - - - - -Event Container - - - -
-
- Event0
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
-
- Mix  - Fwd -
-
- -
-
- -
-
- - diff --git a/temporary/Temp/firmware_html.h b/temporary/Temp/firmware_html.h deleted file mode 100644 index 7a7440f..0000000 --- a/temporary/Temp/firmware_html.h +++ /dev/null @@ -1,215 +0,0 @@ -#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 deleted file mode 100644 index 40a476e..0000000 --- a/temporary/Temp/githubCert.h +++ /dev/null @@ -1,30 +0,0 @@ -#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/gridstyles.css b/temporary/Temp/gridstyles.css deleted file mode 100644 index bf326f6..0000000 --- a/temporary/Temp/gridstyles.css +++ /dev/null @@ -1,6 +0,0 @@ -.container{ - color:blue; - display: grid; - gap: 1rem; - grid-template-columns: 50% 1fr 1fr; -} \ No newline at end of file diff --git a/temporary/Temp/gridtest.html b/temporary/Temp/gridtest.html deleted file mode 100644 index 872e384..0000000 --- a/temporary/Temp/gridtest.html +++ /dev/null @@ -1,12 +0,0 @@ - - - CSS Grid - - -
-
Test text 1
-
Test text 1
-
Test text 1
-
Test text 1
-
- \ No newline at end of file diff --git a/temporary/Temp/hue-select-min.js b/temporary/Temp/hue-select-min.js deleted file mode 100644 index e25f5aa..0000000 --- a/temporary/Temp/hue-select-min.js +++ /dev/null @@ -1 +0,0 @@ -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/Temp/led_animation.cpp b/temporary/Temp/led_animation.cpp deleted file mode 100644 index 6b191af..0000000 --- a/temporary/Temp/led_animation.cpp +++ /dev/null @@ -1,1023 +0,0 @@ -#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 deleted file mode 100644 index 21ed576..0000000 --- a/temporary/Temp/led_animation.h +++ /dev/null @@ -1,205 +0,0 @@ -#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 deleted file mode 100644 index 328c197..0000000 --- a/temporary/Temp/led_strip.cpp +++ /dev/null @@ -1,564 +0,0 @@ -#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 deleted file mode 100644 index 3f363fa..0000000 --- a/temporary/Temp/led_strip.h +++ /dev/null @@ -1,128 +0,0 @@ -#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/lights-old.html b/temporary/Temp/lights-old.html deleted file mode 100644 index 62a71d5..0000000 --- a/temporary/Temp/lights-old.html +++ /dev/null @@ -1,755 +0,0 @@ -{{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/Temp/log.html b/temporary/Temp/log.html deleted file mode 100644 index 8684117..0000000 --- a/temporary/Temp/log.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - System Log - - -

System Log

- - \ No newline at end of file diff --git a/temporary/Temp/luma_master.cpp b/temporary/Temp/luma_master.cpp deleted file mode 100644 index 7ec642a..0000000 --- a/temporary/Temp/luma_master.cpp +++ /dev/null @@ -1,111 +0,0 @@ -#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 deleted file mode 100644 index 5808f26..0000000 --- a/temporary/Temp/luma_master.h +++ /dev/null @@ -1,24 +0,0 @@ -#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 deleted file mode 100644 index 12ec314..0000000 --- a/temporary/Temp/my_wifi.cpp +++ /dev/null @@ -1,1451 +0,0 @@ - -/* - 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 deleted file mode 100644 index 67f0773..0000000 --- a/temporary/Temp/my_wifi.h +++ /dev/null @@ -1,66 +0,0 @@ -#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/neo_colors.h b/temporary/Temp/neo_colors.h deleted file mode 100644 index 0ffaf31..0000000 --- a/temporary/Temp/neo_colors.h +++ /dev/null @@ -1,78 +0,0 @@ -#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/Temp/otaupdate.cpp b/temporary/Temp/otaupdate.cpp deleted file mode 100644 index e18a99c..0000000 --- a/temporary/Temp/otaupdate.cpp +++ /dev/null @@ -1,96 +0,0 @@ -#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 deleted file mode 100644 index 0667dea..0000000 --- a/temporary/Temp/otaupdate.h +++ /dev/null @@ -1,14 +0,0 @@ -#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 deleted file mode 100644 index 1ff5e67..0000000 --- a/temporary/Temp/rf_transceiver.cpp +++ /dev/null @@ -1,44 +0,0 @@ - -#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 deleted file mode 100644 index 091368f..0000000 --- a/temporary/Temp/rf_transceiver.h +++ /dev/null @@ -1,23 +0,0 @@ -#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/Temp/styles.css b/temporary/Temp/styles.css deleted file mode 100644 index 94eac3f..0000000 --- a/temporary/Temp/styles.css +++ /dev/null @@ -1,50 +0,0 @@ -.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/Temp/update.json b/temporary/Temp/update.json deleted file mode 100644 index 007f91a..0000000 --- a/temporary/Temp/update.json +++ /dev/null @@ -1,190 +0,0 @@ -{ - "files": [ - { - "remote": "data/ata-boothifier-upgrade.html", - "local": "/ata-boothifier-upgrade.html", - "md5": "92074ec24fd467545272eb9e837d644c", - "size": 16980 - }, - { - "remote": "data/ata-boothifier-upgradeV2.html", - "local": "/ata-boothifier-upgradeV2.html", - "md5": "484648993370e4b6aff13124bd1c55c1", - "size": 18178 - }, - { - "remote": "data/ata-boothifier-upgradeV3.html", - "local": "/ata-boothifier-upgradeV3.html", - "md5": "79426145db379ac15ccc742245a31ecb", - "size": 17233 - }, - { - "remote": "data/favicon.ico", - "local": "/favicon.ico", - "md5": "ba4c4e3bf5e5db2bbfc56a52f3657d79", - "size": 1150 - }, - { - "remote": "data/flashstik-reg.html", - "local": "/flashstik-reg.html", - "md5": "394fdfe3cd3fb89a8ddb3d6edf2d2da4", - "size": 15651 - }, - { - "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/ble.json", - "local": "/system/ble.json", - "md5": "faebefd5b50046a01d4a8aa27f8504d0", - "size": 307 - }, - { - "remote": "data/system/readme.txt", - "local": "/system/readme.txt", - "md5": "198c92a2cc06b3effce3b5ba313cc6a0", - "size": 86 - }, - { - "remote": "data/system/tunes.json", - "local": "/system/tunes.json", - "md5": "814999e88296bee179cef5d394f3f696", - "size": 1149 - }, - { - "remote": "data/system/update.json", - "local": "/system/update.json", - "md5": "01cc6a1935601085df49308cab646673", - "size": 156 - }, - { - "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 - } - ], - "firmware": { - "md5": "fcec6e659842cc0333880b701a3e2634", - "size": 1401712 - }, - "release_date": "2025-08-20", - "release_time": "08:53:05" -} \ No newline at end of file diff --git a/temporary/Temp/upgrade.html b/temporary/Temp/upgrade.html deleted file mode 100644 index 7afb28a..0000000 --- a/temporary/Temp/upgrade.html +++ /dev/null @@ -1,228 +0,0 @@ - - - - Firmware Update - - - - - - - - - -

Firmware Update

- -
-
- Local Update -
-
- -
-
- -
-
-
- -
-
-
- -
-
- Web Update -
- -
- -
- -
- -
-
- -
-
- Update Progress -
- - - -
-
-
- - - - \ No newline at end of file diff --git a/temporary/Temp/web_ble.html b/temporary/Temp/web_ble.html deleted file mode 100644 index b8309a0..0000000 --- a/temporary/Temp/web_ble.html +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - BLE Interaction - - - -

BLE Interaction

- - - - - - - - diff --git a/temporary/ata-boothifier-upgradeV3_old.html b/temporary/ata-boothifier-upgradeV3_old.html deleted file mode 100644 index db06713..0000000 --- a/temporary/ata-boothifier-upgradeV3_old.html +++ /dev/null @@ -1,533 +0,0 @@ - - - - - - ATA Firmware Update - - - - -

ATA Firmware Update

- - -
- - -
- -
- -
- - -
- -
- - -
- -
- - -
- -
- -
-
- -
- -
- -
- -
- -
- - -
- - -
- - - - -
- - -
-
- -
-

WiFi Connection

-
- - -
- - -
-
-
- -
-
- - - - diff --git a/temporary/bak/cfg/!instructions.txt b/temporary/bak/cfg/!instructions.txt deleted file mode 100644 index 1e97b38..0000000 --- a/temporary/bak/cfg/!instructions.txt +++ /dev/null @@ -1,13 +0,0 @@ - -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 deleted file mode 100644 index 51dbbc4..0000000 --- a/temporary/bak/cfg/anim-list.json +++ /dev/null @@ -1,105 +0,0 @@ -{ - "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 deleted file mode 100644 index e50c0e4..0000000 --- a/temporary/bak/cfg/anim-profiles.json +++ /dev/null @@ -1,1203 +0,0 @@ -{ - "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 deleted file mode 100644 index 3889b0e..0000000 --- a/temporary/bak/cfg/anim-settings.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "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 deleted file mode 100644 index fa7b393..0000000 --- a/temporary/bak/cfg/app-events.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "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 deleted file mode 100644 index 8cd73ca..0000000 --- a/temporary/bak/cfg/ble.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "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 deleted file mode 100644 index fa2cffd..0000000 --- a/temporary/bak/cfg/buzzer.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "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 deleted file mode 100644 index d691b5d..0000000 --- a/temporary/bak/cfg/firmware.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "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 deleted file mode 100644 index 2935910..0000000 --- a/temporary/bak/cfg/led-devices.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "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 deleted file mode 100644 index 724076b..0000000 --- a/temporary/bak/cfg/luma-stiks.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "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 deleted file mode 100644 index 1ecb9fa..0000000 --- a/temporary/bak/cfg/relays.json +++ /dev/null @@ -1,41 +0,0 @@ -{ -"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 deleted file mode 100644 index 429a075..0000000 --- a/temporary/bak/cfg/system.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "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 deleted file mode 100644 index d556439..0000000 --- a/temporary/bak/cfg/touch-pins.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "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 deleted file mode 100644 index 6f9cac0..0000000 --- a/temporary/bak/cfg/trx433.json +++ /dev/null @@ -1,10 +0,0 @@ -{ -"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 deleted file mode 100644 index 550020c..0000000 --- a/temporary/bak/cfg/wifi.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "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 deleted file mode 100644 index af21eb6..0000000 --- a/temporary/bak/www/edit.html +++ /dev/null @@ -1,144 +0,0 @@ -{{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 deleted file mode 100644 index a4edd4a..0000000 --- a/temporary/bak/www/edit2.html +++ /dev/null @@ -1,170 +0,0 @@ -{{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 deleted file mode 100644 index f587666..0000000 --- a/temporary/bak/www/event-box.js +++ /dev/null @@ -1,420 +0,0 @@ -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 deleted file mode 100644 index a092d0c..0000000 --- a/temporary/bak/www/failed.html +++ /dev/null @@ -1,24 +0,0 @@ -{{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 deleted file mode 100644 index b0f7f69..0000000 --- a/temporary/bak/www/files.html +++ /dev/null @@ -1,311 +0,0 @@ -{{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 deleted file mode 100644 index 442f988..0000000 --- a/temporary/bak/www/global-style.css +++ /dev/null @@ -1,74 +0,0 @@ -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 deleted file mode 100644 index 86a0873..0000000 --- a/temporary/bak/www/home.html +++ /dev/null @@ -1,271 +0,0 @@ -{{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 deleted file mode 100644 index bcc8132..0000000 --- a/temporary/bak/www/hue-select.js +++ /dev/null @@ -1,275 +0,0 @@ - -// 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 deleted file mode 100644 index 6593d48..0000000 --- a/temporary/bak/www/label.html +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - -Event Container - - - -
-
- -
-
- - - - \ No newline at end of file diff --git a/temporary/bak/www/lights.html b/temporary/bak/www/lights.html deleted file mode 100644 index 24814d8..0000000 --- a/temporary/bak/www/lights.html +++ /dev/null @@ -1,452 +0,0 @@ -{{NAVBAR}} - - - - - - - - - - - - -
-

Lights Configuration

- -
- Saved Animation Profiles -
-
- - -
-
- - - -
-
-
-
- -
-
- Countdown ( Constant Light ) -
-
- - -
-
- - -
-
-
-
-
- -
-
-
- - -
-
-
-
- -
- - - - diff --git a/temporary/bak/www/navbar.html b/temporary/bak/www/navbar.html deleted file mode 100644 index 3c23772..0000000 --- a/temporary/bak/www/navbar.html +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - -
- -
- - \ No newline at end of file diff --git a/temporary/bak/www/ok.html b/temporary/bak/www/ok.html deleted file mode 100644 index a5263dd..0000000 --- a/temporary/bak/www/ok.html +++ /dev/null @@ -1,24 +0,0 @@ -{{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 deleted file mode 100644 index 2cfaa4e..0000000 --- a/temporary/bak/www/setup.html +++ /dev/null @@ -1,355 +0,0 @@ -{{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 deleted file mode 100644 index 58f9781..0000000 --- a/temporary/bak/www/wifi.html +++ /dev/null @@ -1,127 +0,0 @@ - - - - - WiFi Credentials - - - -

Enter WiFi Credentials

-
- - - - - - Image not found -
- - - - diff --git a/temporary/my_buzzer.cpp b/temporary/my_buzzer.cpp deleted file mode 100644 index e686da2..0000000 --- a/temporary/my_buzzer.cpp +++ /dev/null @@ -1,249 +0,0 @@ -#include "my_buzzer.h" -#include -#include - -#include -#include -#include - -#include "JsonConstrain.h" -#include "global.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; -int8_t buzzerChannel = -1; // Store the LEDC channel used by the buzzer - -// File-scope state for tune management (minimal overhead) -static volatile int prev_tune = -1; -static volatile bool buzzer_busy = false; - -// Optimized tone functions - minimal overhead, no logging -void buzzerTone(uint8_t pin, unsigned int frequency, unsigned long duration) { - if (buzzerChannel >= 0 && frequency > 0) { - ledcWriteTone(buzzerChannel, frequency); - } -} - -void buzzerNoTone(uint8_t pin) { - if (buzzerChannel >= 0) { - ledcWrite(buzzerChannel, 0); - } -} - -void Init_Buzzer(int8_t pin, const char* configFile, int8_t channel) -{ - buzzPin = pin; - if(buzzPin >= 0){ - pinMode(buzzPin, OUTPUT); - - // If channel is not provided, find an unused one - if (channel < 0) { - buzzerChannel = findUnusedLedcChannel(); - if (buzzerChannel < 0) { - ESP_LOGE(tag, "No available LEDC channel for buzzer"); - return; - } - } else { - // Use the provided channel and mark it as used - extern bool markLedcChannelUsed(int ch); // Function from global.cpp - if (markLedcChannelUsed(channel)) { - buzzerChannel = channel; - } else { - ESP_LOGE(tag, "Requested channel %d is already in use, finding alternative", channel); - buzzerChannel = findUnusedLedcChannel(); - if (buzzerChannel < 0) { - ESP_LOGE(tag, "No available LEDC channel for buzzer"); - return; - } - } - } - - // Set up the channel for the buzzer with proper audio frequency range - ledcSetup(buzzerChannel, 2000, 10); // 2000 Hz base, 10-bit resolution for better frequency range - ledcAttachPin(buzzPin, buzzerChannel); - - // Test the channel is working - ESP_LOGI(tag, "Testing buzzer channel %d...", buzzerChannel); - ledcWriteTone(buzzerChannel, 1000); // Test tone - delay(100); - ledcWrite(buzzerChannel, 0); // Stop test tone - - // Set custom tone functions for anyrtttl - anyrtttl::setToneFunction(buzzerTone); - anyrtttl::setNoToneFunction(buzzerNoTone); - - ESP_LOGI(tag, "Buzzer hardware initialized on pin %d using LEDC channel %d", buzzPin, buzzerChannel); - } - - Buzzer_Load_Tunes(configFile); // Load Tunes - ESP_LOGI(tag, "Buzzer initialized on pin %d, channel %d", buzzPin, buzzerChannel); -} - -int8_t Buzzer_Get_Channel() { - return buzzerChannel; -} - -void Buzzer_Play_Tune(TUNE_TYPE tune, bool async, bool hasPriority) -{ - // Fast path checks - minimal overhead - if (buzzPin < 0 || buzzerChannel < 0) { - ESP_LOGW(tag, "Buzzer not initialized - pin:%d, channel:%d", buzzPin, buzzerChannel); - return; - } - if (tune < 0 || tune >= TUNE_MAX_COUNT) { - ESP_LOGW(tag, "Invalid tune index: %d (max: %d)", (int)tune, TUNE_MAX_COUNT); - return; - } - - // Direct reference to avoid String copying - const String& melody = buzzTune[tune].melody; - if (melody.isEmpty()) { - ESP_LOGW(tag, "Empty melody for tune %d", (int)tune); - return; - } - - ESP_LOGI(tag, "Playing tune %d: %s (async=%d, priority=%d)", (int)tune, melody.c_str(), async, hasPriority); - - // Simple atomic check for thread safety without mutex overhead - if (buzzer_busy && !hasPriority) { - ESP_LOGD(tag, "Buzzer busy, skipping tune %d", (int)tune); - return; - } - - // Async mode: minimal state management - if (async) { - bool playing = anyrtttl::nonblocking::isPlaying(); - if (hasPriority && playing) { - ESP_LOGD(tag, "Stopping current tune for priority tune %d", (int)tune); - anyrtttl::nonblocking::stop(); - playing = false; - } - if (!playing || prev_tune != tune) { - ESP_LOGI(tag, "Starting async tune %d", (int)tune); - anyrtttl::nonblocking::begin(buzzPin, melody.c_str()); - prev_tune = tune; - } - anyrtttl::nonblocking::play(); - return; - } - - // Blocking mode: minimal cycles with yield for multitasking - ESP_LOGI(tag, "Playing blocking tune %d, cycles=%d", (int)tune, buzzTune[tune].cycles); - buzzer_busy = true; - const int cycles = buzzTune[tune].cycles; - const int pause_ms = buzzTune[tune].pause; - - for (int c = 0; c < cycles; ++c) { - anyrtttl::blocking::play(buzzPin, melody.c_str()); - if (pause_ms > 0 && c + 1 < cycles) { - delay(pause_ms); - } - yield(); // Allow other tasks to run - } - - prev_tune = tune; - buzzer_busy = false; - ESP_LOGI(tag, "Finished playing tune %d", (int)tune); -} - -// Optimized beep function - minimal overhead -void Buzzer_Beep(int mSecs, int freq) -{ - if (buzzPin < 0 || buzzerChannel < 0) return; - - ledcWriteTone(buzzerChannel, freq); - delay(mSecs); - ledcWrite(buzzerChannel, 0); -} - -// Test function to verify buzzer functionality -void Buzzer_Test() { - if (buzzPin < 0 || buzzerChannel < 0) { - ESP_LOGE(tag, "Cannot test buzzer - not initialized"); - return; - } - - ESP_LOGI(tag, "Testing buzzer..."); - - // Test direct LEDC control - ESP_LOGI(tag, "Test 1: Direct LEDC tones"); - for (int freq = 500; freq <= 2000; freq += 500) { - ESP_LOGI(tag, "Playing %d Hz", freq); - ledcWriteTone(buzzerChannel, freq); - delay(200); - ledcWrite(buzzerChannel, 0); - delay(100); - } - - // Test custom tone functions - ESP_LOGI(tag, "Test 2: Custom tone functions"); - buzzerTone(buzzPin, 1000, 500); - delay(500); - buzzerNoTone(buzzPin); - - // Test anyrtttl with a simple melody - ESP_LOGI(tag, "Test 3: anyrtttl blocking play"); - anyrtttl::blocking::play(buzzPin, DEFAULT_MELODY); - - ESP_LOGI(tag, "Buzzer test complete"); -} - -// Optimized tune loading - minimal memory allocation -void Buzzer_Load_Tunes(const char* tunesPath){ - ESP_LOGI(tag, "Loading tunes from: %s", tunesPath); - File file = LittleFS.open(tunesPath); - if (!file) { - ESP_LOGW(tag, "Could not open %s, using default tune", tunesPath); - // Set default tune only at index 0 - buzzTune[0].cycles = 1; - buzzTune[0].pause = 0; - buzzTune[0].melody = DEFAULT_MELODY; - ESP_LOGI(tag, "Loaded default tune at index 0: %s", DEFAULT_MELODY); - return; - } - - // Use smaller JSON document for memory efficiency - JsonDocument doc; - DeserializationError error = deserializeJson(doc, file); - file.close(); - - if(error){ - ESP_LOGE(tag, "JSON parse error: %s", error.c_str()); - // Set default tune on error - buzzTune[0].cycles = 1; - buzzTune[0].pause = 0; - buzzTune[0].melody = DEFAULT_MELODY; - ESP_LOGI(tag, "Loaded default tune due to JSON error: %s", DEFAULT_MELODY); - 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_LOGI(tag, "Loaded tune %d: cycles=%d, pause=%d, melody=%.40s...", - tuneIndex, buzzTune[tuneIndex].cycles, buzzTune[tuneIndex].pause, - buzzTune[tuneIndex].melody.c_str()); - tuneIndex++; - } - ESP_LOGI(tag, "Successfully loaded %d tunes", tuneIndex); - } else { - ESP_LOGW(tag, "No 'tunes' array found in JSON"); - // Set default tune if no tunes array found - buzzTune[0].cycles = 1; - buzzTune[0].pause = 0; - buzzTune[0].melody = DEFAULT_MELODY; - ESP_LOGI(tag, "Loaded default tune: %s", DEFAULT_MELODY); - } -} \ No newline at end of file diff --git a/test/edit.html b/test/edit.html new file mode 100644 index 0000000..7f478e8 --- /dev/null +++ b/test/edit.html @@ -0,0 +1,265 @@ + + + + Edit file + + + + + + + + + + +
+

Edit File

+ +
+
Editor
+ +
+ +
+ + + + +
+ + +
+ +
+
+
+
+ + + + diff --git a/test/files.html b/test/files.html new file mode 100644 index 0000000..55fcee0 --- /dev/null +++ b/test/files.html @@ -0,0 +1,350 @@ + + + + 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/webSock/AppUpgrade.cpp b/webSock/AppUpgrade.cpp deleted file mode 100644 index d3a2857..0000000 --- a/webSock/AppUpgrade.cpp +++ /dev/null @@ -1,522 +0,0 @@ -#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 deleted file mode 100644 index c23d9c0..0000000 --- a/webSock/AppUpgrade.h +++ /dev/null @@ -1,156 +0,0 @@ -#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 deleted file mode 100644 index 45e53e4..0000000 --- a/webSock/my_wifi.cpp +++ /dev/null @@ -1,1164 +0,0 @@ -#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*6, 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 deleted file mode 100644 index 603d416..0000000 --- a/webSock/my_wifi.h +++ /dev/null @@ -1,38 +0,0 @@ -#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 deleted file mode 100644 index c0b55ea..0000000 --- a/webSock/upgrade.html +++ /dev/null @@ -1,193 +0,0 @@ - - - - - - Firmware Upgrade - - - - -
-

Firmware Upgrade

- -
- - Disconnected -
- -
-

Current Version: -

-

Latest Version: -

-
- -
- - -
- -
-
- - - - \ No newline at end of file diff --git a/z_old/HSVTable.cpp b/z_old/HSVTable.cpp deleted file mode 100644 index a5643e7..0000000 --- a/z_old/HSVTable.cpp +++ /dev/null @@ -1,175 +0,0 @@ -#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 deleted file mode 100644 index f88957c..0000000 --- a/z_old/HSVTable.h +++ /dev/null @@ -1,23 +0,0 @@ -#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 deleted file mode 100644 index dd54478..0000000 --- a/z_old/LEDStrip.cpp +++ /dev/null @@ -1,473 +0,0 @@ -#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 deleted file mode 100644 index 6c18f0a..0000000 --- a/z_old/LEDStrip.h +++ /dev/null @@ -1,117 +0,0 @@ -#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 deleted file mode 100644 index 3dc53d0..0000000 --- a/z_old/color_tools.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#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 deleted file mode 100644 index 176ef2c..0000000 --- a/z_old/color_tools.h +++ /dev/null @@ -1,38 +0,0 @@ -#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 deleted file mode 100644 index d36929d..0000000 --- a/z_old/fileSystem.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#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 deleted file mode 100644 index 1dbb701..0000000 --- a/z_old/fileSystem.h +++ /dev/null @@ -1,23 +0,0 @@ -#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 deleted file mode 100644 index 8eeeb64..0000000 --- a/z_old/my_board.cpp +++ /dev/null @@ -1,249 +0,0 @@ -#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 deleted file mode 100644 index 27a93fb..0000000 --- a/z_old/my_board.h +++ /dev/null @@ -1,162 +0,0 @@ -#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 deleted file mode 100644 index d1465b3..0000000 --- a/z_old/my_buttons.cpp +++ /dev/null @@ -1,92 +0,0 @@ -#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 deleted file mode 100644 index 467eb12..0000000 --- a/z_old/my_buttons.h +++ /dev/null @@ -1,34 +0,0 @@ -#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 deleted file mode 100644 index 2bd8dfc..0000000 --- a/z_old/my_buzzer.cpp +++ /dev/null @@ -1,123 +0,0 @@ -#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 deleted file mode 100644 index 41e4cbd..0000000 --- a/z_old/neo_colors.h +++ /dev/null @@ -1,138 +0,0 @@ -#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 deleted file mode 100644 index fd3a27f..0000000 --- a/z_old/old/anim-list.json +++ /dev/null @@ -1,105 +0,0 @@ -{ - "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 deleted file mode 100644 index 87730d5..0000000 --- a/z_old/old/anim-profile-common.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "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 deleted file mode 100644 index 96209ec..0000000 --- a/z_old/old/anim-profile1.json +++ /dev/null @@ -1,149 +0,0 @@ -{ -"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 deleted file mode 100644 index 530f239..0000000 --- a/z_old/old/anim-profile2.json +++ /dev/null @@ -1,149 +0,0 @@ -{ -"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 deleted file mode 100644 index 195e8c5..0000000 --- a/z_old/old/anim-profile3.json +++ /dev/null @@ -1,149 +0,0 @@ -{ -"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 deleted file mode 100644 index 949c7ad..0000000 --- a/z_old/old/anim-profile4.json +++ /dev/null @@ -1,149 +0,0 @@ -{ -"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 deleted file mode 100644 index 38980f7..0000000 --- a/z_old/old/anim-profile5.json +++ /dev/null @@ -1,149 +0,0 @@ -{ -"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 deleted file mode 100644 index 5fa5a46..0000000 --- a/z_old/old/anim-profile6.json +++ /dev/null @@ -1,149 +0,0 @@ -{ -"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 deleted file mode 100644 index f9205df..0000000 --- a/z_old/old/anim-profile7.json +++ /dev/null @@ -1,149 +0,0 @@ -{ -"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 deleted file mode 100644 index eb06cd8..0000000 --- a/z_old/old/anim-profile8.json +++ /dev/null @@ -1,149 +0,0 @@ -{ -"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 deleted file mode 100644 index e50c0e4..0000000 --- a/z_old/old/anim-profiles copy.json +++ /dev/null @@ -1,1203 +0,0 @@ -{ - "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 deleted file mode 100644 index 3889b0e..0000000 --- a/z_old/old/anim-settings.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "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 deleted file mode 100644 index fa7b393..0000000 --- a/z_old/old/app-events.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "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 deleted file mode 100644 index 8cd73ca..0000000 --- a/z_old/old/ble.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "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 deleted file mode 100644 index 2077f39..0000000 --- a/z_old/old/board.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "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 deleted file mode 100644 index 7f5fd74..0000000 --- a/z_old/old/led-devices.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "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 deleted file mode 100644 index 97a7052..0000000 --- a/z_old/old/ramp-lights.json +++ /dev/null @@ -1,23 +0,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 - } - ] -} \ No newline at end of file diff --git a/z_old/old/relays.json b/z_old/old/relays.json deleted file mode 100644 index 7e11224..0000000 --- a/z_old/old/relays.json +++ /dev/null @@ -1,41 +0,0 @@ -{ -"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 deleted file mode 100644 index 8cc156a..0000000 --- a/z_old/old/system.json +++ /dev/null @@ -1,146 +0,0 @@ -{ - "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 deleted file mode 100644 index d556439..0000000 --- a/z_old/old/touch-pins.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "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 deleted file mode 100644 index 56bcacd..0000000 --- a/z_old/wifi_temp.cpp +++ /dev/null @@ -1,1450 +0,0 @@ - -/* - 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 - JsonDocument doc; - 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); - JsonDocument profJson; - 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")){ - JsonDocument animData; - 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")){ - JsonDocument js; - 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")){ - JsonDocument js; - 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; - } - - 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.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); - } -} - -