From a64b648f7a828ade7e9df912d0c9a51823c4cd8e Mon Sep 17 00:00:00 2001 From: cjkas Date: Tue, 10 Mar 2026 20:23:48 +0100 Subject: [PATCH 01/16] init --- .gitignore | 1 + .vscode/c_cpp_properties.json | 513 ++++++++++++++++++ .vscode/extensions.json | 10 + .vscode/launch.json | 44 ++ data-src/apple-icon.png | Bin 0 -> 6625 bytes data-src/appversion | 1 + data-src/favicon.png | Bin 0 -> 1389 bytes data-src/icon.png | Bin 0 -> 13572 bytes {data => data-src}/icon.svg | 0 {data => data-src}/icons.css | 0 {data => data-src}/index.html | 0 {data => data-src}/index.js | 0 {data => data-src}/login.html | 0 {data => data-src}/main.css | 0 {data => data-src}/widgets.css | 0 data/icon.svg.gz | Bin 0 -> 9066 bytes data/icons.css.gz | Bin 0 -> 4197 bytes data/index.html.gz | Bin 0 -> 9447 bytes data/index.js.gz | Bin 0 -> 37312 bytes data/login.html.gz | Bin 0 -> 831 bytes data/main.css.gz | Bin 0 -> 3221 bytes data/widgets.css.gz | Bin 0 -> 1951 bytes huge_app.csv | 6 + include/README | 37 ++ lib/README | 46 ++ min_spiffs.csv | 7 + minify.py | 290 ++++++++++ platformio.ini | 37 ++ ConfigFile.cpp => src/ConfigFile.cpp | 0 ConfigFile.h => src/ConfigFile.h | 0 ConfigSettings.cpp => src/ConfigSettings.cpp | 0 ConfigSettings.h => src/ConfigSettings.h | 0 GitOTA.cpp => src/GitOTA.cpp | 0 GitOTA.h => src/GitOTA.h | 0 MQTT.cpp => src/MQTT.cpp | 0 MQTT.h => src/MQTT.h | 0 Network.cpp => src/Network.cpp | 0 Network.h => src/Network.h | 0 SSDP.cpp => src/SSDP.cpp | 0 SSDP.h => src/SSDP.h | 0 Sockets.cpp => src/Sockets.cpp | 0 Sockets.h => src/Sockets.h | 0 Somfy.cpp => src/Somfy.cpp | 0 Somfy.h => src/Somfy.h | 0 .../SomfyController.ino | 0 .../SomfyController.ino.esp32.bin | Bin .../SomfyController.ino.esp32s3.bin | Bin .../SomfyController.littlefs.bin | Bin Utils.cpp => src/Utils.cpp | 0 Utils.h => src/Utils.h | 0 WResp.cpp => src/WResp.cpp | 0 WResp.h => src/WResp.h | 0 Web.cpp => src/Web.cpp | 0 Web.h => src/Web.h | 0 esp32.svd => src/esp32.svd | 0 test/README | 11 + 56 files changed, 1003 insertions(+) create mode 100644 .vscode/c_cpp_properties.json create mode 100644 .vscode/extensions.json create mode 100644 .vscode/launch.json create mode 100644 data-src/apple-icon.png create mode 100644 data-src/appversion create mode 100644 data-src/favicon.png create mode 100644 data-src/icon.png rename {data => data-src}/icon.svg (100%) rename {data => data-src}/icons.css (100%) rename {data => data-src}/index.html (100%) rename {data => data-src}/index.js (100%) rename {data => data-src}/login.html (100%) rename {data => data-src}/main.css (100%) rename {data => data-src}/widgets.css (100%) create mode 100644 data/icon.svg.gz create mode 100644 data/icons.css.gz create mode 100644 data/index.html.gz create mode 100644 data/index.js.gz create mode 100644 data/login.html.gz create mode 100644 data/main.css.gz create mode 100644 data/widgets.css.gz create mode 100644 huge_app.csv create mode 100644 include/README create mode 100644 lib/README create mode 100644 min_spiffs.csv create mode 100644 minify.py create mode 100644 platformio.ini rename ConfigFile.cpp => src/ConfigFile.cpp (100%) rename ConfigFile.h => src/ConfigFile.h (100%) rename ConfigSettings.cpp => src/ConfigSettings.cpp (100%) rename ConfigSettings.h => src/ConfigSettings.h (100%) rename GitOTA.cpp => src/GitOTA.cpp (100%) rename GitOTA.h => src/GitOTA.h (100%) rename MQTT.cpp => src/MQTT.cpp (100%) rename MQTT.h => src/MQTT.h (100%) rename Network.cpp => src/Network.cpp (100%) rename Network.h => src/Network.h (100%) rename SSDP.cpp => src/SSDP.cpp (100%) rename SSDP.h => src/SSDP.h (100%) rename Sockets.cpp => src/Sockets.cpp (100%) rename Sockets.h => src/Sockets.h (100%) rename Somfy.cpp => src/Somfy.cpp (100%) rename Somfy.h => src/Somfy.h (100%) rename SomfyController.ino => src/SomfyController.ino (100%) rename SomfyController.ino.esp32.bin => src/SomfyController.ino.esp32.bin (100%) rename SomfyController.ino.esp32s3.bin => src/SomfyController.ino.esp32s3.bin (100%) rename SomfyController.littlefs.bin => src/SomfyController.littlefs.bin (100%) rename Utils.cpp => src/Utils.cpp (100%) rename Utils.h => src/Utils.h (100%) rename WResp.cpp => src/WResp.cpp (100%) rename WResp.h => src/WResp.h (100%) rename Web.cpp => src/Web.cpp (100%) rename Web.h => src/Web.h (100%) rename esp32.svd => src/esp32.svd (100%) create mode 100644 test/README diff --git a/.gitignore b/.gitignore index be96788..81dbea1 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ SomfyController.ino.XIAO_ESP32S3.bin SomfyController.ino.esp32c3.bin SomfyController.ino.esp32s2.bin .vscode/settings.json +.pio \ No newline at end of file diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..e4e5e8d --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,513 @@ +// +// !!! WARNING !!! AUTO-GENERATED FILE! +// PLEASE DO NOT MODIFY IT AND USE "platformio.ini": +// https://docs.platformio.org/page/projectconf/section_env_build.html#build-flags +// +{ + "configurations": [ + { + "name": "PlatformIO", + "includePath": [ + "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/include", + "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/WebServer/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/AsyncUDP/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/ESPmDNS/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Update/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/HTTPClient/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Preferences/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/LittleFS/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/FS/src", + "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/PubSubClient/src", + "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/SmartRC-CC1101-Driver-Lib", + "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/WebSockets/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/WiFiClientSecure/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/SPI/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Ethernet/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/WiFi/src", + "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/ArduinoJson/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/newlib/platform_include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/freertos/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/freertos/include/esp_additions/freertos", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/freertos/port/riscv/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/freertos/include/esp_additions", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_hw_support/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_hw_support/include/soc", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_hw_support/include/soc/esp32c3", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_hw_support/port/esp32c3", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_hw_support/port/esp32c3/private_include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/heap/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/log/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/lwip/include/apps", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/lwip/include/apps/sntp", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/lwip/lwip/src/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/lwip/port/esp32/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/lwip/port/esp32/include/arch", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/soc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/soc/esp32c3", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/soc/esp32c3/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/hal/esp32c3/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/hal/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/hal/platform_port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_rom/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_rom/include/esp32c3", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_rom/esp32c3", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_common/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_system/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_system/port/soc", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_system/port/include/riscv", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_system/port/public_compat", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/riscv/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/driver/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/driver/esp32c3/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_pm/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_ringbuf/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/efuse/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/efuse/esp32c3/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/vfs/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_wifi/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_event/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_netif/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_eth/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/tcpip_adapter/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_phy/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_phy/esp32c3/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_ipc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/app_trace/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_timer/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/mbedtls/port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/mbedtls/mbedtls/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/mbedtls/esp_crt_bundle/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/app_update/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/spi_flash/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bootloader_support/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/nvs_flash/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/pthread/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_gdbstub/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_gdbstub/riscv", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_gdbstub/esp32c3", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espcoredump/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espcoredump/include/port/riscv", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/wpa_supplicant/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/wpa_supplicant/port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/wpa_supplicant/esp_supplicant/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/ieee802154/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/console", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/asio/asio/asio/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/asio/port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/common/osi/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/include/esp32c3/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/common/api/include/api", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/common/btc/profile/esp/blufi/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/common/btc/profile/esp/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/host/bluedroid/api/include/api", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_common/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_common/tinycrypt/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_core", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_core/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_core/storage", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/btc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_models/common/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_models/client/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_models/server/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/api/core/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/api/models/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/api", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/cbor/port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/unity/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/unity/unity/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/cmock/CMock/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/coap/port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/coap/libcoap/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/nghttp/port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/nghttp/nghttp2/lib/includes", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-tls", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-tls/esp-tls-crypto", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_adc_cal/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_hid/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/tcp_transport/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_http_client/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_http_server/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_https_ota/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_https_server/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_lcd/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_lcd/interface", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/protobuf-c/protobuf-c", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/protocomm/include/common", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/protocomm/include/security", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/protocomm/include/transports", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/mdns/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_local_ctrl/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/sdmmc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_serial_slave_link/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_websocket_client/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/expat/expat/expat/lib", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/expat/port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/wear_levelling/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/fatfs/diskio", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/fatfs/vfs", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/fatfs/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/freemodbus/freemodbus/common/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/idf_test/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/idf_test/include/esp32c3", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/jsmn/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/json/cJSON", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/libsodium/libsodium/src/libsodium/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/libsodium/port_include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/mqtt/esp-mqtt/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/openssl/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/spiffs/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/wifi_provisioning/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/rmaker_common/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_diagnostics/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/rtc_store/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_insights/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/json_parser/upstream/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/json_parser/upstream", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/json_generator/upstream", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_schedule/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp_secure_cert_mgr/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_rainmaker/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/gpio_button/button/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/qrcode/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/ws2812_led", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_littlefs/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/tool", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/typedef", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/image", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/math", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/nn", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/layer", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/detect", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/model_zoo", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp32-camera/driver/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp32-camera/conversions/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/dotprod/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/support/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/support/mem/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/windows/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/windows/hann/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/windows/blackman/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/windows/blackman_harris/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/windows/blackman_nuttall/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/windows/nuttall/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/windows/flat_top/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/iir/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/fir/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/math/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/math/add/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/math/sub/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/math/mul/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/math/addc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/math/mulc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/math/sqrt/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/matrix/mul/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/matrix/add/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/matrix/addc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/matrix/mulc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/matrix/sub/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/matrix/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/fft/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/dct/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/conv/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/common/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/matrix/mul/test/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/kalman/ekf/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/kalman/ekf_imu13states/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/fb_gfx/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/qio_qspi/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/cores/esp32", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/variants/esp32c3", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/ArduinoOTA/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/BLE/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/BluetoothSerial/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/DNSServer/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/EEPROM/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/ESP32/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/FFat/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/HTTPUpdate/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/HTTPUpdateServer/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/I2S/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Insights/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/NetBIOS/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/RainMaker/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/SD/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/SD_MMC/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/SPIFFS/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/SimpleBLE/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Ticker/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/USB/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/WiFiProv/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Wire/src", + "" + ], + "browse": { + "limitSymbolsToIncludedHeaders": true, + "path": [ + "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/include", + "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/WebServer/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/AsyncUDP/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/ESPmDNS/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Update/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/HTTPClient/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Preferences/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/LittleFS/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/FS/src", + "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/PubSubClient/src", + "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/SmartRC-CC1101-Driver-Lib", + "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/WebSockets/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/WiFiClientSecure/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/SPI/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Ethernet/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/WiFi/src", + "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/ArduinoJson/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/newlib/platform_include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/freertos/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/freertos/include/esp_additions/freertos", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/freertos/port/riscv/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/freertos/include/esp_additions", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_hw_support/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_hw_support/include/soc", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_hw_support/include/soc/esp32c3", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_hw_support/port/esp32c3", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_hw_support/port/esp32c3/private_include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/heap/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/log/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/lwip/include/apps", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/lwip/include/apps/sntp", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/lwip/lwip/src/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/lwip/port/esp32/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/lwip/port/esp32/include/arch", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/soc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/soc/esp32c3", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/soc/esp32c3/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/hal/esp32c3/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/hal/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/hal/platform_port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_rom/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_rom/include/esp32c3", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_rom/esp32c3", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_common/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_system/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_system/port/soc", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_system/port/include/riscv", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_system/port/public_compat", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/riscv/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/driver/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/driver/esp32c3/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_pm/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_ringbuf/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/efuse/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/efuse/esp32c3/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/vfs/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_wifi/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_event/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_netif/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_eth/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/tcpip_adapter/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_phy/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_phy/esp32c3/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_ipc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/app_trace/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_timer/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/mbedtls/port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/mbedtls/mbedtls/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/mbedtls/esp_crt_bundle/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/app_update/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/spi_flash/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bootloader_support/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/nvs_flash/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/pthread/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_gdbstub/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_gdbstub/riscv", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_gdbstub/esp32c3", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espcoredump/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espcoredump/include/port/riscv", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/wpa_supplicant/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/wpa_supplicant/port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/wpa_supplicant/esp_supplicant/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/ieee802154/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/console", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/asio/asio/asio/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/asio/port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/common/osi/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/include/esp32c3/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/common/api/include/api", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/common/btc/profile/esp/blufi/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/common/btc/profile/esp/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/host/bluedroid/api/include/api", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_common/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_common/tinycrypt/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_core", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_core/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_core/storage", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/btc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_models/common/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_models/client/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_models/server/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/api/core/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/api/models/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/api", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/cbor/port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/unity/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/unity/unity/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/cmock/CMock/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/coap/port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/coap/libcoap/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/nghttp/port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/nghttp/nghttp2/lib/includes", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-tls", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-tls/esp-tls-crypto", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_adc_cal/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_hid/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/tcp_transport/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_http_client/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_http_server/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_https_ota/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_https_server/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_lcd/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_lcd/interface", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/protobuf-c/protobuf-c", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/protocomm/include/common", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/protocomm/include/security", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/protocomm/include/transports", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/mdns/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_local_ctrl/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/sdmmc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_serial_slave_link/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_websocket_client/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/expat/expat/expat/lib", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/expat/port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/wear_levelling/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/fatfs/diskio", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/fatfs/vfs", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/fatfs/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/freemodbus/freemodbus/common/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/idf_test/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/idf_test/include/esp32c3", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/jsmn/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/json/cJSON", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/libsodium/libsodium/src/libsodium/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/libsodium/port_include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/mqtt/esp-mqtt/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/openssl/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/spiffs/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/wifi_provisioning/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/rmaker_common/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_diagnostics/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/rtc_store/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_insights/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/json_parser/upstream/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/json_parser/upstream", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/json_generator/upstream", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_schedule/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp_secure_cert_mgr/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_rainmaker/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/gpio_button/button/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/qrcode/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/ws2812_led", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_littlefs/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/tool", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/typedef", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/image", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/math", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/nn", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/layer", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/detect", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/model_zoo", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp32-camera/driver/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp32-camera/conversions/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/dotprod/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/support/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/support/mem/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/windows/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/windows/hann/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/windows/blackman/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/windows/blackman_harris/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/windows/blackman_nuttall/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/windows/nuttall/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/windows/flat_top/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/iir/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/fir/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/math/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/math/add/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/math/sub/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/math/mul/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/math/addc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/math/mulc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/math/sqrt/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/matrix/mul/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/matrix/add/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/matrix/addc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/matrix/mulc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/matrix/sub/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/matrix/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/fft/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/dct/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/conv/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/common/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/matrix/mul/test/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/kalman/ekf/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/kalman/ekf_imu13states/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/fb_gfx/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/qio_qspi/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/cores/esp32", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/variants/esp32c3", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/ArduinoOTA/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/BLE/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/BluetoothSerial/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/DNSServer/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/EEPROM/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/ESP32/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/FFat/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/HTTPUpdate/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/HTTPUpdateServer/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/I2S/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Insights/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/NetBIOS/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/RainMaker/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/SD/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/SD_MMC/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/SPIFFS/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/SimpleBLE/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Ticker/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/USB/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/WiFiProv/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Wire/src", + "" + ] + }, + "defines": [ + "PLATFORMIO=60119", + "ARDUINO_ESP32C3_DEV", + "HAVE_CONFIG_H", + "MBEDTLS_CONFIG_FILE=\"mbedtls/esp_config.h\"", + "UNITY_INCLUDE_CONFIG_H", + "WITH_POSIX", + "_GNU_SOURCE", + "IDF_VER=\"v4.4.7-dirty\"", + "ESP_PLATFORM", + "_POSIX_READER_WRITER_LOCKS", + "ARDUINO_ARCH_ESP32", + "ESP32", + "F_CPU=160000000L", + "ARDUINO=10812", + "ARDUINO_VARIANT=\"esp32c3\"", + "ARDUINO_BOARD=\"Espressif ESP32-C3-DevKitM-1\"", + "ARDUINO_PARTITION_min_spiffs", + "" + ], + "cStandard": "gnu99", + "cppStandard": "gnu++11", + "compilerPath": "C:/Users/oem/.platformio/packages/toolchain-riscv32-esp/bin/riscv32-esp-elf-gcc.exe", + "compilerArgs": [ + "-march=rv32imc", + "" + ] + } + ], + "version": 4 +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..080e70d --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..6f795a7 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,44 @@ +// AUTOMATICALLY GENERATED FILE. PLEASE DO NOT MODIFY IT MANUALLY +// +// PlatformIO Debugging Solution +// +// Documentation: https://docs.platformio.org/en/latest/plus/debugging.html +// Configuration: https://docs.platformio.org/en/latest/projectconf/sections/env/options/debug/index.html + +{ + "version": "0.2.0", + "configurations": [ + { + "type": "platformio-debug", + "request": "launch", + "name": "PIO Debug", + "executable": "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/build/esp32dev/firmware.elf", + "projectEnvName": "esp32devdbg", + "toolchainBinDir": "C:/Users/oem/.platformio/packages/toolchain-riscv32-esp/bin", + "internalConsoleOptions": "openOnSessionStart", + "preLaunchTask": { + "type": "PlatformIO", + "task": "Pre-Debug" + } + }, + { + "type": "platformio-debug", + "request": "launch", + "name": "PIO Debug (skip Pre-Debug)", + "executable": "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/build/esp32dev/firmware.elf", + "projectEnvName": "esp32devdbg", + "toolchainBinDir": "C:/Users/oem/.platformio/packages/toolchain-riscv32-esp/bin", + "internalConsoleOptions": "openOnSessionStart" + }, + { + "type": "platformio-debug", + "request": "launch", + "name": "PIO Debug (without uploading)", + "executable": "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/build/esp32dev/firmware.elf", + "projectEnvName": "esp32devdbg", + "toolchainBinDir": "C:/Users/oem/.platformio/packages/toolchain-riscv32-esp/bin", + "internalConsoleOptions": "openOnSessionStart", + "loadMode": "manual" + } + ] +} diff --git a/data-src/apple-icon.png b/data-src/apple-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..78fc318ef2fe46b798181b124911a197b8da84b2 GIT binary patch literal 6625 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 literal 0 HcmV?d00001 diff --git a/data-src/appversion b/data-src/appversion new file mode 100644 index 0000000..48a6b50 --- /dev/null +++ b/data-src/appversion @@ -0,0 +1 @@ +2.4.7 \ No newline at end of file diff --git a/data-src/favicon.png b/data-src/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..80a5dd37238a04a2057f764bc9f93f6d09c734e8 GIT binary patch literal 1389 zcmV-z1(N!SP)2 z5JNzPmlNp8-} zne(6jnft%zDiPssn^LL}7zgA4mqp~q?3}*q*)ujAXaX(+9;MVn*$G!40nmU0cmV}? zI#2Y4{S63E2e^UXvJ(n;ypKQ(1V#eibapBrMW#OY!g5_?%HY070JI`Q4Zs9oW*;S9 zN9IzZvO1uYTIO-9E#n^aXqSt-A`aDchCeUZ0{BHF1=qZWUN>sTs{UqJ$=#{QiVy3Z zEeC9?v+p_`Pzup1!$3omp|em1da-MtJ0tFPqf+`y2Js zQ~ff2nCFJ~>ZVF40*zNp$8cE+m!$~hY5DF%tJ?Z`QV)N?%~zYE&Xlpu;G^lX4_`ac>&{Kz;HmeSV*!rru%Q&Ia9*65d)8 zG&3gV$lK)&m`*HD9p{s|Q~W*Bl5PRy_tR9@IYeh&=vXtP$Sr604RlsK)6Z=BETQ*Q zHmUy_jCwfcmKUDPHNRg-D}Z32=O5W_0lf24*vx*^FOIo-kb(U(*J16FkeUBPKu({t z2?jLN#`(l$!Jw#1zW!i|Y&WnG7(#}%UfG=h@Bx1TaUgIn04xPQ0{Q_vdb>%l+j0>(3>@xleAfD^E$`ND z#K0J(RAVF($&1J1-Rgng@Ap`iMKYOe>+A;X_Gd592Bf3WsK2JBrU?jNyN?2<1EYY+ zz*7JV7cL|e3aOf!8aaCODEayM#N%-ykqE7=t)$awR<2yBDk>`E$dMxy6cpg`cv!P$ zjk0Z<ir3^8iD8r+VttDK%%# z9F z9cmpqbjS$=0=l%cRORO88t7ed$#FqJh1(L8CMs5g=*y}00000NkvXXu0mjfTp*C0 literal 0 HcmV?d00001 diff --git a/data-src/icon.png b/data-src/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..cbc072e7448d30ce1cd1165992540df5a8666522 GIT binary patch literal 13572 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 adcIh>6uIlhga2THo<4piS0-Z;^uGXrL0K&T literal 0 HcmV?d00001 diff --git a/data/icon.svg b/data-src/icon.svg similarity index 100% rename from data/icon.svg rename to data-src/icon.svg diff --git a/data/icons.css b/data-src/icons.css similarity index 100% rename from data/icons.css rename to data-src/icons.css diff --git a/data/index.html b/data-src/index.html similarity index 100% rename from data/index.html rename to data-src/index.html diff --git a/data/index.js b/data-src/index.js similarity index 100% rename from data/index.js rename to data-src/index.js diff --git a/data/login.html b/data-src/login.html similarity index 100% rename from data/login.html rename to data-src/login.html diff --git a/data/main.css b/data-src/main.css similarity index 100% rename from data/main.css rename to data-src/main.css diff --git a/data/widgets.css b/data-src/widgets.css similarity index 100% rename from data/widgets.css rename to data-src/widgets.css diff --git a/data/icon.svg.gz b/data/icon.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..a5c99cd25e15efc0955d23bc45c76bc6442925a3 GIT binary patch literal 9066 zcmV-wBbD4AiwFpQZm?+r|7l}yZZ30nX8`3~TW=mma{Vg<=3zI$e4qP%ku5K>0fM}2 z9)e9C^2Sh<$Sg$(6fIl+^*N`ix@U%xEZNu%62yyzhV{**FLgPmy1Kvn!%wg8miN~W z?{44RKfmy2cd=aGU%h#8d;jYB#lQXepHA&!dG~mE|KjrQ&HeTBi~BbhfB62pckf>< zKikofBTudaNt1vrnzH z(*GW>e7L@P#Hf6@!+^YbdwF&H_~Ds5_x|n6+q=7Gf9K>s`QtQx_TRpHe0cMh>-nqx zd6H3l7S7T27KiY~<-429hlk4#&+akkLpoojL&ZjfQXNKXOhrArl6~E#BA#RRIroI& z;wa~uolAJ)1OAHV99;ET2IOzhrS4)YzpYEuy3wh0&N-yswqE6Psx`P0q*viwdvh&! z=~WHq(rXFnxAw~YVz1J{Xh*D$w{GTTZ)PxwCJsc%7?$b}=T`g7hkQOk|Bc zeoe1lzP!T3KIs*H4nEgk>>S$EYskN`b76Kaqwzh;9E{F|dhRKdSX2?+IrxxU8=cGD z6<4KmZ!aHjefj48@g&!1(ZlP@yKhDE{_^4W^8WE4`%%7o5TX6o zk5@NG+3nBQ&q6xa*!tU_RQB@n_3hnJgSnv=PpWzw#fODmU3Q=+aZjPc_bBuV&xwnu~ z=DVUR4I=y&p>cQ@TzB-TcidHT=`B%IdyS>yt`b5+i#8EjLnEk*!>=xc8qzm-JqaP2k``-gVUu`#9T5iK(&o2X zIk50!K7>_f9KKVhGGq}owoPWF6tt$XE!_w78+YNBSlM6<8k!}V(sHa}C&E?5U{a6p zfnGhXA@m3jMO36!yrofw4g{(VyPJIw8RiulNf|mEpo3JG0$=b%C^tawTCk-edFXP4*Ka*!LQVbbmb9b8S(bQ|K`Zbc4i7$(IRjJxC~XMR#oT4OQ}t>qQ088!#;_yj zWn@yS&WUE%;@eI#M#$zHNQst#j=R7gbMU=&v6ap#3UnWsi(xE$sCAES<3${EQ6P^= zq#2ZknWKjEl0)V#5z52XtEMb8y;3Vxrb88CT@pPF%x9C}g&Zil8NdYJd>Nw{(f^3= z!$Q+4^98FFn<$}o(V^5Lo`9&87D`)XxO_){17%#at*MR_4-?@N1rwKK^4SdZ8_ytORx>G|ToG7W=pEF50U+ck zt&Er!v7i__yZ~kJlkk3vsy8NEIyn6(l*MO})dWu`jucQ5(uFTD2IH>!r>3$0w1&BPJ&*7~vA- zbx^m`L zoSYHvEdCXFkNOk72ez9fQosNbBL!w8MusUw+rV2iI<0fUB@)Nn#JeCk(IV8d?j@sv zY+1-j=hzo`4sjGAihC3vlEm=DQ;A5_WEqMh>K8+EeJ2{7(>b!z}!lwnFE7owCXPDgZtW8yauSJmzkMx)3VGFx6$uh~PnufYa2064sl zqTYO{N&SCnMNLQCr05}#suMI+5#Y^CJUl?7*)8Pmm>w`vK*OOe%kUPPI&{ZSNebT? zIB+MxSB{(HX6cMyL7?gMkkr5q?_e4jT1yNN^f4j<8p>+lFn@Jy1(qn<2HLGEiGg)Q zL{ns5%JdIkhYs`gfFDk#7%>UhS3LjrNCY~M%k;bBW1?8F@U6{c_59BL*QrUJ=b1O|<8XVNH7uu|=&u@8ftyumGCwv!TdBUQIa-t0b%->kn+$!QokZV(5)wQn z``*ru06@EL=vX`|Zm>y*vL~JNl1JaYV3^%ZjLr-A)=&t`C~YU&+~Ur~%e+GsHBurb zb(!PsH8xr#x68+h*VOV=9V#GN)G56GfV>nCz9aZ=k2Gxny&{d^9 z@4)m_fu{N)21rCOA2$gN8XzRLB?t9sPK!4F1kYxBGruHyw5ko!@IEI{E z4mo?l`0TsrGolZiJ0^jsDht&}oB}}tp!MsF-~wDQXQVR$sP!PhWOveF(hiJLSFDh_ z6G9GeS`>UwMwHR)7$=ho>)z8-cHDk9%$GzMBvf(eW3M;MHP{fdG2gmA=r5p%t;1~|AV z%8o!41v)hZZy_;oMv^rH5|PxFGBvVC?Vv4x(gPF3qd~3)KHO; zlK2Ab3zsB@2e-}F!b-Ee#D)$RVAMSbRBly6KJb0Bq?(sJ0ES3h5a7NSkAp``Rp`tM zz9ul<%00xDdkGjDOvPm)88JJqqB!GNh^Ci^~Y3xcnFqID)<1cOv?9s7D~$(!QB}%_r=@F_ z4VK%CscPDFbDh~cT_TQ-ph=U-zBwb}c&%CpFi=+Y^6+(Ppgl*;Fb7zzCai(7f-(Y_ zrlDo0DCDh98w_;>4pf^C#Zp*IEf4!ORhD*&tkz&o z2lK5Tt%6p_yf8-AeR#&U^Y&tTq_%3R)?BeXd6`Ej=)*tg+cueyDkFyKViy@E8|%aj zsGxPkGMzXWxn#f0A*(M?3IJyljF*|$hT2uN>?r~n>~!{2P$#^Wt*(+UXc!7fPm@6K zvuI~kC!bvxHczBpZB?XdkQ%2%vmi;Z!5GS9Ykm( zvBCRg`sy2v=AEl<*7Bam(hoZ*OC?-kDCr==xE8u)&Fv}j!Q6DEQ6N%E+3BHINz@Cb zfrcs&Qt*TuqbmE%xR#K0wMiv6vX}@i$t(;PcpD*sW(L>*QAWpP>Z+ll`%M*zWeAv; z>AZ)35-X}q%I=grVR{Y%)j+ipbV+^lw2~%Eh({|K5n{6wiSZht2zCx4Yu$TYx|W_v ze?j}Z=9hr4$^4yQ#!@s)@gXmm(OdQp!zpdrIbZPAx{S7|Ap$@gMT|FNIZ3d&x@(~0 zW-$%KK{+V6q%Hrb?4Ord(aG>k~^MQuv!Hfp9w0g=l* z5JQH57210^h}7%Jh@wN(Z|7vP(Jq&$MEF1|``M?{mJcA}$vQXh5dHvHFC1kE#11vH z2Luo`Kr&}4NW8-{X#Aj>w#9s}&4qr_lIn2ZzP7!@#>x$ht651fDO(X3;8pNSQNU?3 zaW$-xSEyi z?P**qCI3xnTnVi{rE%iyzewZovhb9~4cX^tT*~<=jq}>U1C8?^(YX9EjVqt0aY0k* z8;t`3^#8-jz}mZ3ReGraoK`nS`80kMo$0>hgGk>17@b+OUS{zEn3BwzL>wtQh-Gq$utz`} zxtS`e{!Bb;OybJbo$Vw7=XBK**wg8nqXWB@`iLMu?%_7nG>@e2!09eH)v-A6{U%bX zNP55;KHx1c?1t3|y(+uOm`cUih+(oGAcTQ<3}i{8tMCPW9$J|+0=GUEVZBUNm#R)e zL@u_i6LOeeSXHN!g2miK!ou0m_mB;e#bl}%w(yWpaM;OE#@-9UNTyzqZ-ZWqg|p6 z@C}yYfkBCTok+}Osk*`dStgZELW{Rb>h(ytN-!ToPD4Q-hRRa>yq5$jpcSmR?zJ`w zA2Z4fz>vJbn! zjw<{H7tNY3#r!L<#&8)30Sp~krAmG}Ac7JTxFx60vOrH)xeHm^IylN=XhfBUl@0p- z(uX5qx%qKA#3d^YnBg6RoTH*G)}AP!iCXuvp)D2Bm*;aQ#ht|~GmRp3GEsfJWUCZS z`PekgMIgGIW~3Y$t7W1p;e()x?I2<)RnY~1GzM@@j75sv0K3H7`WBQMSg_i&1^tH9 zB$l{q2%A>iUUEa=-4jdv8828Vfq5M90?;MOm#ZKcmgmOlrj@04buq)2O;Sp*qm==| zMYZZuszRs0!m`ARR+<9gEnc8A`QvzDYYl_?YC}YK#F+M?Enduy?Yetqj<9QyCm2Mw z+?Pz_J{vYiZ)U9(3mmf6@J``L*4idUfrm@#J2ba=vYFg8p+6y+I++LH!5|zgeN4cq zEPHFE$OL+1X5rzRmx+XmbO9jl=Ix~zCBfR84nv>-KkTfYYcd#@`R6vhFkd~o? z>%i{nT7><f92`a(Cp8k3ZiiOj}276i3492{K+N>G=kS|Yrtw3zW& zy;AA&DRnM8cV@jY;^mcT-|&}Hu5n65}TeOKo+zTmZ(jn!7R4XAg)HgoC0nzfq~?w(Nc1y`Y|4RloAyN!aV z{W8fcy@MnS$y?FzxDD~ggT7@fN!V%+#g?mBqbj|yN>SCf#n`r5gQtOKs+RiO zKa#=|PZPNjOKr&6&LpmBf|d+}xr8PD@0gcU7xZp)Cfe z?f(6Okx?pbiR^6wwvBs{AE-M4xKU0lOAqW&0=%#}CKbs0ML|q?-Q_j*lg!}{$4QRmFvl`! z?J&oPN``_eqsxAZGsnMtc8WRf$2GwmjE`+=g4(!$Uuxq1x}N47!?0uz&CI{RriiF5 z{UUX|!MR?R4d@$Km-ZxM>TH?up>#W8@#Dr(%VOF0%1egKmvnU3b zaPuGAzQL5u_6JJ&MdOd+lXk}MbsOPPG5n?(`D-*IJkg9W(B`HYL$sNHpr0>RAumqc zE8uduDoKM`M_X@Dr#LGYmg&NeJU4PtXzVnty3>?-*3c&rtjk#19jRyCwVGKBzhw%t zmVsjE<59Lxq=63yrw*iO1%%RY-S?HF6-8)%NE;k<|50EHL4sdV){e$1YqI5ZRZL43 z1PqA6w#+TEqkjs&CRyE{)W|q_yg{-!hHEx$bB~)$u+1o)2u^n^F|5S{KGijeIhOSh z*3fKA6UX9|T5yk%M61G_QP=f$C{Ia#uD44n??_h=Fhf1k6;ZhJ*#AT`}D4l_S?h3mmK>>y|1s#~Rd=T1y4rji=oifJBV19Mk2b7^+8E5Hc6R)cSp^hazo-e z%E6Yg@;JZ283>9PypV#i#IqMDQBrylSP!=jOOj&~z;DCO2qyKJcBBZZY3Uj8iy}E) zyjK5=I4KZBP~KPw^IA&cyX7@g(N{?_X`N0~RI_p;XyaBM!~hNkTiQZBI+qur1FOuR&>SqmtD_VfMNpb|=|{ zN_54Ibw$DrH{El~(t3(;FqP|qSBGK;U24#jR#tq-lxwk?4`(|oB^?tEi*D1cBc_g$ z$m!N~;pFJnPrmTTk>0dUFtM*!W36=9MsKUwX1->a7-23vOz9BC{$5XNMcep)oEe zJpqB4WZ51HZK%pAdX%-*Sy{QSy_4cf+enzZxJ#+7v^FYlvl7SCt}v37QtepSsF->u z`q|Dy8ymy+M+80Hvy^x|)u;C?>1j}}V0DM%)LvgcKHUEFdvS2J$tF_%PyeJd&sEL| zQDR$&C0pbXYp?8mC0*E7HvIXg1=!}y8nL)W7kLN_to@%COdKc8&J}88EvF0&km=(x zvqN-fUi>6o6@U+BlXz%cfu+HDGchi&DYAD{n#AJ`1SvtUF|m~g%D|Cr5}%>O)kzYl z;iGbpO66faoNN3FrQ>3lUbVf*ZkKvL6R!5;PBQOz_>na-Y01zMo|wcl+_Ib-q|FKI zY|Z-xPX5fzx#Eg!I944WCU|r-+N$GI=cwd$4hl1`fe&^`v6eHBtq^?ZdF}@+9#2CL zd3ec`*oABWwm}kb%M(SSlkNAFM3mE)OIhsS`S;CI{aM!(xSy5}b=gkKpXE#id43a& z2j5F0whwB18BVN`t2Hv$U2`|t&kOV`V*DM?Lh&_&mX~a-J#iHU@HJvCq#CZV@)YpZ z{@nz2Cz`Ahh6z5C=HKH^w~n`-^p;{jmF0B2w=C8Gj{W?iwPhz6cQXtOp$`%M7Jd;O ziLzmrFb|zeO;X2n6_xpJ+?$yFX)^DLM`Oh%8*G_vtlUTptkBRwj}TTkCUQ3)rZSGd zWzTuQO{|s-iE$^&F8q=(50R%k%!bO?22#Mxh1SNslYCL&aW#wmy9sWVoLLjvMK$Cp z^kv*;&(FWdQ$MS7q8CZI%ssGAyc_sEEH7+(XJyYN(EpjoMy*-aYpQ(uR0@+gvWx=@ zVL+@2w_|B?A-PlJ|+d8Amn%=663n;CI)Eem}5{M+d-9Ba#+qn6(bkGWv#F2*7LxK zZsp;C{7JkgA6@L;EXG7sA294pOcZYIC#%KR6PK1(o-+dMLS4;;Tf|w>89U}6GbB@? zK!E5HJ5xB$UKS;jnz%{5lk#jb4sw#EJzNv(vm!Eel6bmZPg+vDcxSz=2dO=_7R{w=#2 zoE_!C3UW(fv36k;dwP~KQ1!gjZB%A+VezR559N_lfzS$M)pV`NM5(fQx<@%FDYXz>w+YI&WNE`T&B)+Yiye3 z_)|LLBZx^*O>iGTgekELISw#r>6I+fGzcVCR)0Nn9>OFtCzue4>&TVsxUv8O)-(U+v4TYoEWw?5RH;gW zJaW^Lo(934wUKd{W%NlfP~^!93-lEu!a2@##bqZ+tZ9)fGVE6~a6HtD0f_E~lUA4# z(tnAHhh*GMw>W>b2is0$_dB8$SSUgx-3gZqBFP}r!qOU2vDq3O(P|^=;oYL1Hw82R zbZz6f+Q4!-k@u}K6Xfi6BYhhm^xG7i#vGhF69@#+jTK3>W}R3j-GZ!Vafinyv)N`0 zQ*DA>m)CI$X;H-MDV_9IRp?Q?j>Zr?%uYBqJu?1RML`V z^eTlV%K3T>*#MB8X1Qc$qNPe2wgQ@raJ-2csaw^JE~(*~$ub`7$=ZZdt8!)=&!o{y z&caS;Pe`z|8&-X|?4DV-C)^4j+7h>S@a!spyiAviIEY|wt!nFRRAACqECt<#XD8IP z{W%Wo4!R{vDsni8oU7(}CZh}pB4==U1_3#s2~I90 z+Fg?4bEp|pR9UUe^Q4@(%u;!b4|>IAAp8o*#Jzcav8%e5jr%cP^JjgO*{CVn9&j`p z7X2~IHsSth&F!4E1bj3~+9aLX28<)YP}3ZpRbm=2Z~fQq%Tb0*wbS%iFT~)JxE2{% zBv^UyV&%4+`S(G)!`Eo{H?Q>nqUC!kD(7Ly0UHlL++9Dret&)c=EaLI9AnG~IZvLx zyS=}@x_tZWKYw_9+{^y!&F%fO*SC+?55M|ATP|jFVhJv9cP-#zs*rJMbR51)vE=pL z-R;|V*MHSxsDtdT=)dZuW@!{!b+KIi^!$RoV2w-0pC6uI@H`&R<&TTyfxiL@@Ej;S c`tU*igzL+_Y;*PQ{j2Z)8~Ty6AU$3H0I5V*Pyhe` literal 0 HcmV?d00001 diff --git a/data/icons.css.gz b/data/icons.css.gz new file mode 100644 index 0000000000000000000000000000000000000000..ec95255b5c43a9789ba5986a9dc3d1a6b4d7f465 GIT binary patch literal 4197 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 literal 0 HcmV?d00001 diff --git a/data/index.html.gz b/data/index.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..3f59ce3920a43c1ba6450895a468beb012f42152 GIT binary patch literal 9447 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 literal 0 HcmV?d00001 diff --git a/data/index.js.gz b/data/index.js.gz new file mode 100644 index 0000000000000000000000000000000000000000..859a849eaeae84fdd63c0c446e0e80a5f0011277 GIT binary patch literal 37312 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 literal 0 HcmV?d00001 diff --git a/data/login.html.gz b/data/login.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..111518d23a80ed9e7288cc214b6744f59effeeee GIT binary patch literal 831 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 THIS FILE +| +|- platformio.ini +|--src + |- main.c + +Example contents of `src/main.c` using Foo and Bar: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +The PlatformIO Library Dependency Finder will find automatically dependent +libraries by scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/min_spiffs.csv b/min_spiffs.csv new file mode 100644 index 0000000..0990a3b --- /dev/null +++ b/min_spiffs.csv @@ -0,0 +1,7 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x1E0000, +app1, app, ota_1, 0x1F0000,0x1E0000, +spiffs, data, spiffs, 0x3D0000,0x20000, +coredump, data, coredump,0x3F0000,0x10000, diff --git a/minify.py b/minify.py new file mode 100644 index 0000000..ed3dfe2 --- /dev/null +++ b/minify.py @@ -0,0 +1,290 @@ +""" +PlatformIO pre-build script: minify & gzip web assets. + +Copies files from data-src/ → data/ + • HTML – whitespace & comment removal, then gzip + • CSS – whitespace & comment removal, then gzip + • JS – whitespace & comment removal, then gzip + • PNG – copied as-is (optipng if available) + • Other – copied as-is + +Usage in platformio.ini +----------------------- + extra_scripts = pre:minify.py + +Directory layout +---------------- + project/ + ├── data-src/ ← original, human-readable assets + │ ├── index.html + │ ├── css/ + │ ├── js/ + │ └── img/ + ├── data/ ← auto-generated (gitignore this!) + ├── minify.py + └── platformio.ini + +The script runs automatically before LittleFS / SPIFFS image is built. +It also runs once at the start of every build so the data/ dir is always fresh. + +Dependencies: none (pure Python ≥ 3.7). +Optional: optipng (for PNG optimisation) +""" + +Import("env") # PlatformIO macro – gives us the build environment + +import gzip +import os +import re +import shutil +import subprocess +import sys + +# ────────────────────────────────────────────── +# Config +# ────────────────────────────────────────────── +SRC_DIR_NAME = "data-src" +DST_DIR_NAME = "data" + +# Extensions that will be minified + gzipped +MINIFY_AND_GZIP = {".html", ".htm", ".css", ".js", ".json", ".svg", ".xml"} + +# Extensions that optipng can optimise +PNG_EXTENSIONS = {".png"} + +# Everything else is copied verbatim +# ────────────────────────────────────────────── + + +def _project_dir(): + return env.subst("$PROJECT_DIR") + + +def _src_dir(): + return os.path.join(_project_dir(), SRC_DIR_NAME) + + +def _dst_dir(): + return os.path.join(_project_dir(), DST_DIR_NAME) + + +# ────────────────────────────────────────────── +# Minifiers (pure Python, no npm needed) +# ────────────────────────────────────────────── +def minify_html(text: str) -> str: + """Simple but effective HTML minifier.""" + # Remove HTML comments (but keep IE conditional comments) + text = re.sub(r"", "", text, flags=re.DOTALL) + # Collapse whitespace between tags + text = re.sub(r">\s+<", "> <", text) + # Collapse runs of whitespace into a single space + text = re.sub(r"\s{2,}", " ", text) + # Remove whitespace around = in attributes + text = re.sub(r'\s*=\s*', '=', text) + # Strip leading/trailing whitespace per line, rejoin + lines = [line.strip() for line in text.splitlines() if line.strip()] + return " ".join(lines) + + +def minify_css(text: str) -> str: + """Remove comments, collapse whitespace in CSS.""" + # Remove comments + text = re.sub(r"/\*.*?\*/", "", text, flags=re.DOTALL) + # Remove whitespace around : ; { } , + text = re.sub(r"\s*([:{};,])\s*", r"\1", text) + # Collapse remaining whitespace + text = re.sub(r"\s{2,}", " ", text) + # Strip leading/trailing + return text.strip() + + +def minify_js(text: str) -> str: + """ + Light JS minifier – removes comments and collapses whitespace. + For heavy minification install terser and the script will use it + automatically (see _try_terser below). + """ + # Remove single-line comments (careful with URLs – :// ) + text = re.sub(r"(? str: + """Compact JSON by removing unnecessary whitespace.""" + import json + try: + data = json.loads(text) + return json.dumps(data, separators=(",", ":"), ensure_ascii=False) + except json.JSONDecodeError: + return text + + +def minify_svg(text: str) -> str: + """Minimal SVG minifier – comments + whitespace.""" + text = re.sub(r"", "", text, flags=re.DOTALL) + text = re.sub(r">\s+<", "><", text) + text = re.sub(r"\s{2,}", " ", text) + return text.strip() + + +MINIFIERS = { + ".html": minify_html, + ".htm": minify_html, + ".css": minify_css, + ".js": minify_js, + ".json": minify_json, + ".svg": minify_svg, + ".xml": minify_svg, # same approach works for generic XML +} + + +# ────────────────────────────────────────────── +# Optional external tools (used when available) +# ────────────────────────────────────────────── +def _has_command(cmd: str) -> bool: + return shutil.which(cmd) is not None + + +def _try_terser(src_path: str, dst_path: str) -> bool: + """Use terser for JS if installed (npm i -g terser).""" + if not _has_command("terser"): + return False + try: + subprocess.run( + ["terser", src_path, "-o", dst_path, "--compress", "--mangle"], + check=True, capture_output=True, + ) + return True + except subprocess.CalledProcessError: + return False + + +def _try_optipng(path: str) -> None: + """Optimise PNG in-place if optipng is available.""" + if _has_command("optipng"): + try: + subprocess.run( + ["optipng", "-quiet", "-o2", path], + check=True, capture_output=True, + ) + except subprocess.CalledProcessError: + pass + + +# ────────────────────────────────────────────── +# Core logic +# ────────────────────────────────────────────── +def process_file(src_path: str, dst_path: str) -> dict: + """ + Process a single file: minify, gzip or copy. + Returns a small stats dict. + """ + ext = os.path.splitext(src_path)[1].lower() + original_size = os.path.getsize(src_path) + stats = {"src": src_path, "original": original_size, "final": 0, "action": "copy"} + + os.makedirs(os.path.dirname(dst_path), exist_ok=True) + + # ── Text assets: minify + gzip ────────── + if ext in MINIFY_AND_GZIP: + # Try terser for JS first + if ext == ".js" and _try_terser(src_path, dst_path + ".tmp"): + with open(dst_path + ".tmp", "rb") as f: + minified = f.read() + os.remove(dst_path + ".tmp") + stats["action"] = "terser+gzip" + else: + with open(src_path, "r", encoding="utf-8", errors="ignore") as f: + content = f.read() + + minifier = MINIFIERS.get(ext) + if minifier: + content = minifier(content) + stats["action"] = "minify+gzip" + else: + stats["action"] = "gzip" + + minified = content.encode("utf-8") + + # Write gzipped version + gz_path = dst_path + ".gz" + with gzip.open(gz_path, "wb", compresslevel=9) as gz: + gz.write(minified) + + stats["final"] = os.path.getsize(gz_path) + return stats + + # ── PNG: copy + optional optipng ──────── + if ext in PNG_EXTENSIONS: + shutil.copy2(src_path, dst_path) + _try_optipng(dst_path) + stats["final"] = os.path.getsize(dst_path) + stats["action"] = "optipng" if _has_command("optipng") else "copy" + return stats + + # ── Everything else: plain copy ───────── + shutil.copy2(src_path, dst_path) + stats["final"] = os.path.getsize(dst_path) + return stats + + +def minify_all(): + src_dir = _src_dir() + dst_dir = _dst_dir() + + if not os.path.isdir(src_dir): + print(f"[minify] WARNING: '{SRC_DIR_NAME}/' not found – skipping.") + return + + print(f"[minify] {SRC_DIR_NAME}/ → {DST_DIR_NAME}/") + + # Clean destination + if os.path.exists(dst_dir): + shutil.rmtree(dst_dir) + os.makedirs(dst_dir, exist_ok=True) + + total_original = 0 + total_final = 0 + file_count = 0 + + for root, dirs, files in os.walk(src_dir): + for fname in sorted(files): + # Skip hidden files and editor temp files + if fname.startswith(".") or fname.endswith("~"): + continue + + src_path = os.path.join(root, fname) + rel_path = os.path.relpath(src_path, src_dir) + dst_path = os.path.join(dst_dir, rel_path) + + stats = process_file(src_path, dst_path) + + pct = (1 - stats["final"] / stats["original"]) * 100 if stats["original"] > 0 else 0 + print( + f" {rel_path:<40s} " + f"{stats['original']:>8,d} → {stats['final']:>8,d} B " + f"({pct:+.0f}%) [{stats['action']}]" + ) + + total_original += stats["original"] + total_final += stats["final"] + file_count += 1 + + print(f"[minify] {file_count} files processed") + print( + f"[minify] Total: {total_original:,d} → {total_final:,d} bytes " + f"(saved {total_original - total_final:,d} bytes, " + f"{(1 - total_final / total_original) * 100:.0f}%)" + ) + + +# ────────────────────────────────────────────── +# PlatformIO hooks +# ────────────────────────────────────────────── +# Run at the start of every build +minify_all() \ No newline at end of file diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..4a85885 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,37 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[platformio] +default_envs = esp32dev + +[env:esp32dev] +platform = espressif32 +board = esp32-c3-devkitm-1 +framework = arduino +lib_deps = + bblanchon/ArduinoJson@^7.2.2 + links2004/WebSockets@^2.7.3 + lsatan/SmartRC-CC1101-Driver-Lib@^2.5.7 + knolleary/PubSubClient@^2.8 +extra_scripts = pre:minify.py +board_build.partitions = min_spiffs.csv + +[env:esp32devdbg] +build_type = debug +platform = espressif32 +board = esp32dev +framework = arduino +lib_deps = + bblanchon/ArduinoJson@^7.2.2 + links2004/WebSockets@^2.7.3 + lsatan/SmartRC-CC1101-Driver-Lib@^2.5.7 + knolleary/PubSubClient@^2.8 +extra_scripts = pre:minify.py +board_build.partitions = min_spiffs.csv \ No newline at end of file diff --git a/ConfigFile.cpp b/src/ConfigFile.cpp similarity index 100% rename from ConfigFile.cpp rename to src/ConfigFile.cpp diff --git a/ConfigFile.h b/src/ConfigFile.h similarity index 100% rename from ConfigFile.h rename to src/ConfigFile.h diff --git a/ConfigSettings.cpp b/src/ConfigSettings.cpp similarity index 100% rename from ConfigSettings.cpp rename to src/ConfigSettings.cpp diff --git a/ConfigSettings.h b/src/ConfigSettings.h similarity index 100% rename from ConfigSettings.h rename to src/ConfigSettings.h diff --git a/GitOTA.cpp b/src/GitOTA.cpp similarity index 100% rename from GitOTA.cpp rename to src/GitOTA.cpp diff --git a/GitOTA.h b/src/GitOTA.h similarity index 100% rename from GitOTA.h rename to src/GitOTA.h diff --git a/MQTT.cpp b/src/MQTT.cpp similarity index 100% rename from MQTT.cpp rename to src/MQTT.cpp diff --git a/MQTT.h b/src/MQTT.h similarity index 100% rename from MQTT.h rename to src/MQTT.h diff --git a/Network.cpp b/src/Network.cpp similarity index 100% rename from Network.cpp rename to src/Network.cpp diff --git a/Network.h b/src/Network.h similarity index 100% rename from Network.h rename to src/Network.h diff --git a/SSDP.cpp b/src/SSDP.cpp similarity index 100% rename from SSDP.cpp rename to src/SSDP.cpp diff --git a/SSDP.h b/src/SSDP.h similarity index 100% rename from SSDP.h rename to src/SSDP.h diff --git a/Sockets.cpp b/src/Sockets.cpp similarity index 100% rename from Sockets.cpp rename to src/Sockets.cpp diff --git a/Sockets.h b/src/Sockets.h similarity index 100% rename from Sockets.h rename to src/Sockets.h diff --git a/Somfy.cpp b/src/Somfy.cpp similarity index 100% rename from Somfy.cpp rename to src/Somfy.cpp diff --git a/Somfy.h b/src/Somfy.h similarity index 100% rename from Somfy.h rename to src/Somfy.h diff --git a/SomfyController.ino b/src/SomfyController.ino similarity index 100% rename from SomfyController.ino rename to src/SomfyController.ino diff --git a/SomfyController.ino.esp32.bin b/src/SomfyController.ino.esp32.bin similarity index 100% rename from SomfyController.ino.esp32.bin rename to src/SomfyController.ino.esp32.bin diff --git a/SomfyController.ino.esp32s3.bin b/src/SomfyController.ino.esp32s3.bin similarity index 100% rename from SomfyController.ino.esp32s3.bin rename to src/SomfyController.ino.esp32s3.bin diff --git a/SomfyController.littlefs.bin b/src/SomfyController.littlefs.bin similarity index 100% rename from SomfyController.littlefs.bin rename to src/SomfyController.littlefs.bin diff --git a/Utils.cpp b/src/Utils.cpp similarity index 100% rename from Utils.cpp rename to src/Utils.cpp diff --git a/Utils.h b/src/Utils.h similarity index 100% rename from Utils.h rename to src/Utils.h diff --git a/WResp.cpp b/src/WResp.cpp similarity index 100% rename from WResp.cpp rename to src/WResp.cpp diff --git a/WResp.h b/src/WResp.h similarity index 100% rename from WResp.h rename to src/WResp.h diff --git a/Web.cpp b/src/Web.cpp similarity index 100% rename from Web.cpp rename to src/Web.cpp diff --git a/Web.h b/src/Web.h similarity index 100% rename from Web.h rename to src/Web.h diff --git a/esp32.svd b/src/esp32.svd similarity index 100% rename from esp32.svd rename to src/esp32.svd diff --git a/test/README b/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html From 96d66c1b456a8fd57854f3261c82fdbe61994a79 Mon Sep 17 00:00:00 2001 From: cjkas Date: Thu, 12 Mar 2026 13:59:17 +0100 Subject: [PATCH 02/16] add gzip support --- .gitignore | 3 ++- platformio.ini | 4 +++- src/Web.cpp | 13 ++++++++----- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 81dbea1..e4cdf53 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ SomfyController.ino.XIAO_ESP32S3.bin SomfyController.ino.esp32c3.bin SomfyController.ino.esp32s2.bin .vscode/settings.json -.pio \ No newline at end of file +.pio +data \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 4a85885..6bf731f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,6 +22,7 @@ lib_deps = knolleary/PubSubClient@^2.8 extra_scripts = pre:minify.py board_build.partitions = min_spiffs.csv +board_build.filesystem = littlefs [env:esp32devdbg] build_type = debug @@ -34,4 +35,5 @@ lib_deps = lsatan/SmartRC-CC1101-Driver-Lib@^2.5.7 knolleary/PubSubClient@^2.8 extra_scripts = pre:minify.py -board_build.partitions = min_spiffs.csv \ No newline at end of file +board_build.partitions = min_spiffs.csv +board_build.filesystem = littlefs \ No newline at end of file diff --git a/src/Web.cpp b/src/Web.cpp index 425d1b2..b4f481d 100644 --- a/src/Web.cpp +++ b/src/Web.cpp @@ -216,14 +216,17 @@ void Web::handleStreamFile(WebServer &server, const char *filename, const char * webServer.sendCORSHeaders(server); if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } esp_task_wdt_reset(); - // Load the index html page from the data directory. - Serial.print("Loading file "); - Serial.println(filename); - File file = LittleFS.open(filename, "r"); + // Try gzip variant first; streamFile() auto-adds Content-Encoding: gzip for .gz files + String gzFilename = String(filename) + ".gz"; + File file = LittleFS.open(gzFilename.c_str(), "r"); if (!file) { - Serial.print("Error opening"); + file = LittleFS.open(filename, "r"); + } + if (!file) { + Serial.print("Error opening "); Serial.println(filename); server.send(500, _encoding_text, "Error opening file"); + return; } esp_task_wdt_delete(NULL); server.streamFile(file, encoding); From a54651a96149ef410ec265687489136a6d0b004c Mon Sep 17 00:00:00 2001 From: cjkas Date: Thu, 12 Mar 2026 14:17:34 +0100 Subject: [PATCH 03/16] update version --- data-src/appversion | 2 +- data-src/index.html | 8 ++++---- data-src/index.js | 2 +- minify.py | 42 +++++++++++++++++++++--------------------- src/ConfigSettings.h | 2 +- 5 files changed, 28 insertions(+), 28 deletions(-) diff --git a/data-src/appversion b/data-src/appversion index 48a6b50..752a79e 100644 --- a/data-src/appversion +++ b/data-src/appversion @@ -1 +1 @@ -2.4.7 \ No newline at end of file +2.4.8 \ No newline at end of file diff --git a/data-src/index.html b/data-src/index.html index fda0670..20ef11d 100644 --- a/data-src/index.html +++ b/data-src/index.html @@ -8,9 +8,9 @@ - - - + + + @@ -114,7 +114,7 @@ rel="apple-touch-startup-image"> - +
diff --git a/data-src/index.js b/data-src/index.js index 2a70854..89f1aea 100644 --- a/data-src/index.js +++ b/data-src/index.js @@ -1270,7 +1270,7 @@ var security = new Security(); class General { initialized = false; - appVersion = 'v2.4.7'; + appVersion = 'v2.4.8'; reloadApp = false; init() { if (this.initialized) return; diff --git a/minify.py b/minify.py index ed3dfe2..a5c581c 100644 --- a/minify.py +++ b/minify.py @@ -98,20 +98,20 @@ def minify_css(text: str) -> str: return text.strip() -def minify_js(text: str) -> str: - """ - Light JS minifier – removes comments and collapses whitespace. - For heavy minification install terser and the script will use it - automatically (see _try_terser below). - """ - # Remove single-line comments (careful with URLs – :// ) - text = re.sub(r"(? str: +# """ +# Light JS minifier – removes comments and collapses whitespace. +# For heavy minification install terser and the script will use it +# automatically (see _try_terser below). +# """ +# # Remove single-line comments (careful with URLs – :// ) +# text = re.sub(r"(? str: @@ -133,13 +133,13 @@ def minify_svg(text: str) -> str: MINIFIERS = { - ".html": minify_html, - ".htm": minify_html, - ".css": minify_css, - ".js": minify_js, - ".json": minify_json, - ".svg": minify_svg, - ".xml": minify_svg, # same approach works for generic XML + # ".html": minify_html, + # ".htm": minify_html, + # ".css": minify_css, + # ".js": minify_js, + # ".json": minify_json, + # ".svg": minify_svg, + # ".xml": minify_svg, # same approach works for generic XML } diff --git a/src/ConfigSettings.h b/src/ConfigSettings.h index 350db96..50314db 100644 --- a/src/ConfigSettings.h +++ b/src/ConfigSettings.h @@ -3,7 +3,7 @@ #ifndef configsettings_h #define configsettings_h #include "WResp.h" -#define FW_VERSION "v2.4.7" +#define FW_VERSION "v2.4.8" enum class conn_types_t : byte { unset = 0x00, wifi = 0x01, From 9113e58ec4cd25a893ff66afaa402f48ce3e59b9 Mon Sep 17 00:00:00 2001 From: cjkas Date: Fri, 13 Mar 2026 12:40:37 +0100 Subject: [PATCH 04/16] ignore --- .gitignore | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index e4cdf53..da20569 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ debug.cfg SomfyController.ino.XIAO_ESP32S3.bin SomfyController.ino.esp32c3.bin SomfyController.ino.esp32s2.bin -.vscode/settings.json -.pio -data \ No newline at end of file +.vscode/ +.pio/ +data/ +build/ \ No newline at end of file From 2509030c49e49000cf09394fa399172d59e7081f Mon Sep 17 00:00:00 2001 From: cjkas Date: Fri, 13 Mar 2026 18:40:34 +0100 Subject: [PATCH 05/16] backup with RAM --- README.md | 5 ++++ minify.py | 8 +++---- platformio.ini | 23 ++++++++++++++++-- src/ConfigFile.cpp | 25 ++++++++++++++++--- src/ConfigFile.h | 2 ++ src/Somfy.cpp | 14 ++++++++--- src/Somfy.h | 1 + src/SomfyController.ino | 53 ++++++++++++++++++++++++++++++++++++++++- src/Web.cpp | 9 +++---- 9 files changed, 121 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 53aedd2..d857adf 100644 --- a/README.md +++ b/README.md @@ -82,3 +82,8 @@ Configuration of the Transceiver is done with the ELECHOUSE_CC1101 library which +pio pkg exec -p tool-esptoolpy -- esptool.py --port COM9 read_flash 0x3F0000 0x10000 coredump.bin + + + +esp-coredump info_corefile --core coredump.bin --core-format=raw --gdb C:\Users\oem\.platformio\packages\toolchain-xtensa-esp32\bin\xtensa-esp32-elf-gdb.exe .pio\build\esp32dev\firmware.elf > coredump_report.txt \ No newline at end of file diff --git a/minify.py b/minify.py index a5c581c..bff2956 100644 --- a/minify.py +++ b/minify.py @@ -133,12 +133,12 @@ def minify_svg(text: str) -> str: MINIFIERS = { - # ".html": minify_html, - # ".htm": minify_html, - # ".css": minify_css, + ".html": minify_html, + ".htm": minify_html, + ".css": minify_css, # ".js": minify_js, # ".json": minify_json, - # ".svg": minify_svg, + ".svg": minify_svg, # ".xml": minify_svg, # same approach works for generic XML } diff --git a/platformio.ini b/platformio.ini index 6bf731f..52fcb65 100644 --- a/platformio.ini +++ b/platformio.ini @@ -11,9 +11,11 @@ [platformio] default_envs = esp32dev + + [env:esp32dev] platform = espressif32 -board = esp32-c3-devkitm-1 +board = esp32dev framework = arduino lib_deps = bblanchon/ArduinoJson@^7.2.2 @@ -23,7 +25,11 @@ lib_deps = extra_scripts = pre:minify.py board_build.partitions = min_spiffs.csv board_build.filesystem = littlefs - +build_flags = + -DCONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=1 + -DCONFIG_ESP_COREDUMP_DATA_FORMAT_ELF=1 + -DCONFIG_ESP_COREDUMP_CHECKSUM_CRC32=1 + [env:esp32devdbg] build_type = debug platform = espressif32 @@ -36,4 +42,17 @@ lib_deps = knolleary/PubSubClient@^2.8 extra_scripts = pre:minify.py board_build.partitions = min_spiffs.csv +board_build.filesystem = littlefs + +[env:esp32c3dev] +platform = espressif32 +board = esp32-c3-devkitm-1 +framework = arduino +lib_deps = + bblanchon/ArduinoJson@^7.2.2 + links2004/WebSockets@^2.7.3 + lsatan/SmartRC-CC1101-Driver-Lib@^2.5.7 + knolleary/PubSubClient@^2.8 +extra_scripts = pre:minify.py +board_build.partitions = min_spiffs.csv board_build.filesystem = littlefs \ No newline at end of file diff --git a/src/ConfigFile.cpp b/src/ConfigFile.cpp index e515bed..43fcbb7 100644 --- a/src/ConfigFile.cpp +++ b/src/ConfigFile.cpp @@ -22,10 +22,15 @@ bool ConfigFile::begin(const char* filename, bool readOnly) { this->_opened = true; return true; } +bool ConfigFile::beginRAM(String *buf) { + _ramBuf = buf; + _opened = true; + return true; +} void ConfigFile::end() { if(this->isOpen()) { - if(!this->readOnly) this->file.flush(); - this->file.close(); + if(_ramBuf) { _ramBuf = nullptr; } + else { if(!this->readOnly) this->file.flush(); this->file.close(); } } this->_opened = false; } @@ -187,10 +192,16 @@ bool ConfigFile::readVarString(char *buff, size_t len) { bool ConfigFile::writeString(const char *val, size_t len, const char tok) { if(!this->isOpen()) return false; int slen = strlen(val); + if(_ramBuf) { + if(slen > 0) _ramBuf->concat(val); + while(slen < (int)len - 1) { _ramBuf->concat(' '); slen++; } + if(tok != CFG_TOK_NONE) _ramBuf->concat(tok); + return true; + } if(slen > 0) if(this->file.write((uint8_t *)val, slen) != slen) return false; // Now we need to pad the end of the string so that it is of a fixed length. - while(slen < len - 1) { + while(slen < (int)len - 1) { this->file.write(' '); slen++; } @@ -202,6 +213,13 @@ bool ConfigFile::writeString(const char *val, size_t len, const char tok) { bool ConfigFile::writeVarString(const char *val, const char tok) { if(!this->isOpen()) return false; int slen = strlen(val); + if(_ramBuf) { + _ramBuf->concat((char)CFG_TOK_QUOTE); + if(slen > 0) _ramBuf->concat(val); + _ramBuf->concat((char)CFG_TOK_QUOTE); + if(tok != CFG_TOK_NONE) _ramBuf->concat(tok); + return true; + } this->writeChar(CFG_TOK_QUOTE); if(slen > 0) if(this->file.write((uint8_t *)val, slen) != slen) return false; this->writeChar(CFG_TOK_QUOTE); @@ -210,6 +228,7 @@ bool ConfigFile::writeVarString(const char *val, const char tok) { } bool ConfigFile::writeChar(const char val) { if(!this->isOpen()) return false; + if(_ramBuf) { _ramBuf->concat(val); return true; } if(this->file.write(static_cast(val)) == 1) return true; return false; } diff --git a/src/ConfigFile.h b/src/ConfigFile.h index 5ad9281..011e7a9 100644 --- a/src/ConfigFile.h +++ b/src/ConfigFile.h @@ -31,12 +31,14 @@ struct config_header_t { class ConfigFile { protected: File file; + String *_ramBuf = nullptr; bool readOnly = false; bool begin(const char *filename, bool readOnly = false); uint32_t startRecPos = 0; bool _opened = false; public: config_header_t header; + bool beginRAM(String *buf); void end(); bool isOpen(); bool seekRecordByIndex(uint16_t ndx); diff --git a/src/Somfy.cpp b/src/Somfy.cpp index bffebd2..0b5eff1 100644 --- a/src/Somfy.cpp +++ b/src/Somfy.cpp @@ -633,8 +633,10 @@ void SomfyShadeController::commit() { void SomfyShadeController::writeBackup() { if(git.lockFS) return; esp_task_wdt_reset(); // Make sure we don't reset inadvertently. + this->backupData = ""; + this->backupData.reserve(16384); ShadeConfigFile file; - file.begin("/controller.backup", false); + file.beginRAM(&this->backupData); file.backup(this); file.end(); } @@ -2888,6 +2890,7 @@ void SomfyShade::moveToMyPosition() { } void SomfyShade::sendCommand(somfy_commands cmd) { this->sendCommand(cmd, this->repeats); } void SomfyShade::sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize) { + Serial.print("Send command start\n"); // This sendCommand function will always be called externally. sendCommand at the remote level // is expected to be called internally when the motor needs commanded. if(this->bitLength == 0) this->bitLength = somfy.transceiver.config.type; @@ -2930,9 +2933,13 @@ void SomfyShade::sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSiz else if(cmd == somfy_commands::My) { if(this->isToggle() || this->shadeType == shade_types::drycontact) SomfyRemote::sendCommand(cmd, repeat); - else if(this->shadeType == shade_types::drycontact2) return; + else if(this->shadeType == shade_types::drycontact2){ + Serial.print("Send command start 1\n"); + return; + } else if(this->isIdle()) { - this->moveToMyPosition(); + this->moveToMyPosition(); + Serial.print("Send command end 2\n"); return; } else { @@ -2951,6 +2958,7 @@ void SomfyShade::sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSiz else { SomfyRemote::sendCommand(cmd, repeat, stepSize); } + Serial.print("Send command end\n"); } void SomfyGroup::sendCommand(somfy_commands cmd) { this->sendCommand(cmd, this->repeats); } void SomfyGroup::sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize) { diff --git a/src/Somfy.h b/src/Somfy.h index bc5c99b..7f99703 100644 --- a/src/Somfy.h +++ b/src/Somfy.h @@ -578,6 +578,7 @@ class SomfyShadeController { void processWaitingFrame(); void commit(); void writeBackup(); + String backupData; bool loadShadesFile(const char *filename); #ifdef USE_NVS bool loadLegacy(); diff --git a/src/SomfyController.ino b/src/SomfyController.ino index 464d179..a76c7e2 100644 --- a/src/SomfyController.ino +++ b/src/SomfyController.ino @@ -9,6 +9,7 @@ #include "Somfy.h" #include "MQTT.h" #include "GitOTA.h" +#include "esp_core_dump.h" ConfigSettings settings; Web webServer; @@ -20,11 +21,61 @@ MQTTClass mqtt; GitUpdater git; uint32_t oldheap = 0; -void setup() { + +void inline checkCoreDumpPartition() { + esp_core_dump_init(); + esp_core_dump_summary_t *summary = + static_cast(malloc(sizeof(esp_core_dump_summary_t))); + if (summary) { + esp_err_t err = esp_core_dump_get_summary(summary); + if (err == ESP_OK) { + log_i("Getting core dump summary ok."); + + } else { + log_e("Getting core dump summary not ok. Error: %d", (int)err); + log_e("Probably no coredump present yet."); + log_e("esp_core_dump_image_check() = %d", esp_core_dump_image_check()); + } + free(summary); + } +} + +void listDir(const char *dirname, uint8_t levels) { + Serial.printf("Listing: %s\n", dirname); + File root = LittleFS.open(dirname); + if (!root || !root.isDirectory()) { + Serial.println("Failed to open directory"); + return; + } + File file = root.openNextFile(); + while (file) { + if (file.isDirectory()) { + Serial.printf(" DIR : %s\n", file.name()); + if (levels) listDir(file.path(), levels - 1); + } else { + Serial.printf(" FILE: %-30s %d bytes\n", file.name(), file.size()); + } + file = root.openNextFile(); + } +} + +void setup() { Serial.begin(115200); Serial.println(); Serial.println("Startup/Boot...."); Serial.println("Mounting File System..."); + checkCoreDumpPartition(); + + if (LittleFS.begin()) { + Serial.printf("\nTotal: %d bytes\n", LittleFS.totalBytes()); + Serial.printf("Used: %d bytes\n", LittleFS.usedBytes()); + Serial.printf("Free: %d bytes\n", LittleFS.totalBytes() - LittleFS.usedBytes()); + Serial.println(); + listDir("/", 3); +} else { + Serial.println("LittleFS mount failed!"); +} + if(LittleFS.begin()) Serial.println("File system mounted successfully"); else Serial.println("Error mounting file system"); settings.begin(); diff --git a/src/Web.cpp b/src/Web.cpp index b4f481d..832b153 100644 --- a/src/Web.cpp +++ b/src/Web.cpp @@ -858,14 +858,11 @@ void Web::handleBackup(WebServer &server, bool attach) { } Serial.println("Saving current shade information"); somfy.writeBackup(); - File file = LittleFS.open("/controller.backup", "r"); - if (!file) { - Serial.println("Error opening shades.cfg"); - server.send(500, _encoding_text, "shades.cfg"); + if(somfy.backupData.length() == 0) { + server.send(500, _encoding_text, "backup failed"); return; } - server.streamFile(file, _encoding_text); - file.close(); + server.send(200, _encoding_text, somfy.backupData); } void Web::handleSetPositions(WebServer &server) { webServer.sendCORSHeaders(server); From 88fdfa577ac9775d815ec6fe4dce73131e7c8d3e Mon Sep 17 00:00:00 2001 From: cjkas Date: Tue, 17 Mar 2026 14:27:57 +0100 Subject: [PATCH 06/16] improve logging --- platformio.ini | 20 +++++++++------ src/SomfyController.ino | 54 +++-------------------------------------- 2 files changed, 17 insertions(+), 57 deletions(-) diff --git a/platformio.ini b/platformio.ini index 52fcb65..da43e41 100644 --- a/platformio.ini +++ b/platformio.ini @@ -11,8 +11,6 @@ [platformio] default_envs = esp32dev - - [env:esp32dev] platform = espressif32 board = esp32dev @@ -25,11 +23,19 @@ lib_deps = extra_scripts = pre:minify.py board_build.partitions = min_spiffs.csv board_build.filesystem = littlefs -build_flags = +build_flags = -DCONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=1 - -DCONFIG_ESP_COREDUMP_DATA_FORMAT_ELF=1 - -DCONFIG_ESP_COREDUMP_CHECKSUM_CRC32=1 - + -DCONFIG_ESP_COREDUMP_ENABLE_TO_UART=1 + -DCONFIG_ESP_COREDUMP_DATA_FORMAT_ELF=1 + -DCONFIG_ESP_COREDUMP_CHECKSUM_CRC32=1 + -DCONFIG_ESP_TASK_WDT_PANIC=1 + -DCONFIG_ESP_COREDUMP_DECODE_INFO=1 +monitor_speed = 115200 +monitor_filters = + time + esp32_exception_decoder + log2file + [env:esp32devdbg] build_type = debug platform = espressif32 @@ -55,4 +61,4 @@ lib_deps = knolleary/PubSubClient@^2.8 extra_scripts = pre:minify.py board_build.partitions = min_spiffs.csv -board_build.filesystem = littlefs \ No newline at end of file +board_build.filesystem = littlefs diff --git a/src/SomfyController.ino b/src/SomfyController.ino index a76c7e2..9fcb56b 100644 --- a/src/SomfyController.ino +++ b/src/SomfyController.ino @@ -1,3 +1,5 @@ +#define LOG_LOCAL_LEVEL ESP_LOG_INFO +#include "esp_log.h" #include #include #include @@ -22,59 +24,11 @@ GitUpdater git; uint32_t oldheap = 0; -void inline checkCoreDumpPartition() { - esp_core_dump_init(); - esp_core_dump_summary_t *summary = - static_cast(malloc(sizeof(esp_core_dump_summary_t))); - if (summary) { - esp_err_t err = esp_core_dump_get_summary(summary); - if (err == ESP_OK) { - log_i("Getting core dump summary ok."); - - } else { - log_e("Getting core dump summary not ok. Error: %d", (int)err); - log_e("Probably no coredump present yet."); - log_e("esp_core_dump_image_check() = %d", esp_core_dump_image_check()); - } - free(summary); - } -} - -void listDir(const char *dirname, uint8_t levels) { - Serial.printf("Listing: %s\n", dirname); - File root = LittleFS.open(dirname); - if (!root || !root.isDirectory()) { - Serial.println("Failed to open directory"); - return; - } - File file = root.openNextFile(); - while (file) { - if (file.isDirectory()) { - Serial.printf(" DIR : %s\n", file.name()); - if (levels) listDir(file.path(), levels - 1); - } else { - Serial.printf(" FILE: %-30s %d bytes\n", file.name(), file.size()); - } - file = root.openNextFile(); - } -} - void setup() { Serial.begin(115200); Serial.println(); - Serial.println("Startup/Boot...."); - Serial.println("Mounting File System..."); - checkCoreDumpPartition(); - - if (LittleFS.begin()) { - Serial.printf("\nTotal: %d bytes\n", LittleFS.totalBytes()); - Serial.printf("Used: %d bytes\n", LittleFS.usedBytes()); - Serial.printf("Free: %d bytes\n", LittleFS.totalBytes() - LittleFS.usedBytes()); - Serial.println(); - listDir("/", 3); -} else { - Serial.println("LittleFS mount failed!"); -} + log_i("Startup/Boot...."); + log_i("Mounting File System..."); if(LittleFS.begin()) Serial.println("File system mounted successfully"); else Serial.println("Error mounting file system"); From c94819e928103ce77258e92a177157787736baaa Mon Sep 17 00:00:00 2001 From: cjkas Date: Wed, 18 Mar 2026 08:27:03 +0100 Subject: [PATCH 07/16] fix crash --- README.md | 4 +++- data-src/index.html | 2 ++ data-src/index.js | 2 ++ src/Network.cpp | 1 + src/Sockets.cpp | 4 ++-- src/Somfy.cpp | 19 +++++++++++++++---- src/Somfy.h | 8 ++++---- src/SomfyController.ino | 2 +- src/WResp.cpp | 7 ++++++- src/WResp.h | 2 ++ src/Web.cpp | 2 ++ 11 files changed, 40 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index d857adf..4555054 100644 --- a/README.md +++ b/README.md @@ -86,4 +86,6 @@ pio pkg exec -p tool-esptoolpy -- esptool.py --port COM9 read_flash 0x3F0000 0x1 -esp-coredump info_corefile --core coredump.bin --core-format=raw --gdb C:\Users\oem\.platformio\packages\toolchain-xtensa-esp32\bin\xtensa-esp32-elf-gdb.exe .pio\build\esp32dev\firmware.elf > coredump_report.txt \ No newline at end of file +esp-coredump info_corefile --core coredump.bin --core-format=raw --gdb C:\Users\oem\.platformio\packages\toolchain-xtensa-esp32\bin\xtensa-esp32-elf-gdb.exe .pio\build\esp32dev\firmware.elf > coredump_report.txt + + C:\Users\oem\.platformio\packages\framework-espidf\export.ps1 \ No newline at end of file diff --git a/data-src/index.html b/data-src/index.html index 20ef11d..616a1c2 100644 --- a/data-src/index.html +++ b/data-src/index.html @@ -246,6 +246,8 @@ Min: + Uptime: +
diff --git a/data-src/index.js b/data-src/index.js index 89f1aea..7a689c6 100644 --- a/data-src/index.js +++ b/data-src/index.js @@ -4385,6 +4385,8 @@ class Firmware { if (sp) sp.innerHTML = mem.max.fmt('#,##0'); sp = document.getElementById('spanMinMemory'); if (sp) sp.innerHTML = mem.min.fmt('#,##0'); + sp = document.getElementById('spanUptime'); + if (sp) sp.innerHTML = mem.uptime / 3600000; } diff --git a/src/Network.cpp b/src/Network.cpp index ad8f2e1..914d4fd 100644 --- a/src/Network.cpp +++ b/src/Network.cpp @@ -691,6 +691,7 @@ void Network::emitHeap(uint8_t num) { json->addElem("free", freeHeap); json->addElem("min", minHeap); json->addElem("total", ESP.getHeapSize()); + json->addElem("uptime", (uint64_t)millis()); json->endObject(); if(num == 255 && bTimeEmit && bValEmit) { sockEmit.endEmit(num); diff --git a/src/Sockets.cpp b/src/Sockets.cpp index 51765aa..f980fa2 100644 --- a/src/Sockets.cpp +++ b/src/Sockets.cpp @@ -77,7 +77,7 @@ void SocketEmitter::startup() { } void SocketEmitter::begin() { sockServer.begin(); - sockServer.enableHeartbeat(20000, 10000, 3); + sockServer.enableHeartbeat(3000, 2000, 2); sockServer.onEvent(this->wsEvent); Serial.println("Socket Server Started..."); //settings.printAvailHeap(); @@ -90,7 +90,7 @@ JsonSockEvent *SocketEmitter::beginEmit(const char *evt) { this->json.beginEvent(&sockServer, evt, g_response, sizeof(g_response)); return &this->json; } -void SocketEmitter::endEmit(uint8_t num) { this->json.endEvent(num); sockServer.loop(); } +void SocketEmitter::endEmit(uint8_t num) { this->json.endEvent(num); esp_task_wdt_reset(); sockServer.loop(); } void SocketEmitter::endEmitRoom(uint8_t room) { if(room < SOCK_MAX_ROOMS) { room_t *r = &this->rooms[room]; diff --git a/src/Somfy.cpp b/src/Somfy.cpp index 0b5eff1..946905d 100644 --- a/src/Somfy.cpp +++ b/src/Somfy.cpp @@ -1447,9 +1447,9 @@ void SomfyRoom::unpublish() { } void SomfyShade::publishState() { if(mqtt.connected()) { - this->publish("position", this->transformPosition(this->currentPos), true); - this->publish("direction", this->direction, true); - this->publish("target", this->transformPosition(this->target), true); + this->publish("position", (uint8_t)50, true); + this->publish("direction", (int8_t)0, true); + this->publish("target", (uint8_t)50, true); this->publish("lastRollingCode", this->lastRollingCode); this->publish("mypos", this->transformPosition(this->myPos), true); this->publish("myTiltPos", this->transformPosition(this->myTiltPos), true); @@ -1796,7 +1796,7 @@ bool SomfyGroup::publish(const char *topic, bool val, bool retain) { float SomfyShade::p_currentPos(float pos) { float old = this->currentPos; this->currentPos = pos; - if(floor(old) != floor(pos)) this->publish("position", this->transformPosition(static_cast(floor(this->currentPos)))); + if(floor(old) != floor(pos)) this->publish("position", (uint8_t)50); return old; } float SomfyShade::p_currentTiltPos(float pos) { @@ -2894,6 +2894,17 @@ void SomfyShade::sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSiz // This sendCommand function will always be called externally. sendCommand at the remote level // is expected to be called internally when the motor needs commanded. if(this->bitLength == 0) this->bitLength = somfy.transceiver.config.type; + // If same direction command sent while already moving, stop the state tracking. + // The real motor stops on its own when it receives the same direction again. + if((cmd == somfy_commands::Up && this->direction == -1) || + (cmd == somfy_commands::Down && this->direction == 1)) { + Serial.println("Same command as dir"); + SomfyRemote::sendCommand(cmd, repeat); + this->p_target(this->currentPos); + this->p_tiltTarget(this->currentTiltPos); + this->setMovement(0); + return; + } if(cmd == somfy_commands::Up) { if(this->tiltType == tilt_types::euromode) { // In euromode we need to long press for 2 seconds on the diff --git a/src/Somfy.h b/src/Somfy.h index 7f99703..3c1aac5 100644 --- a/src/Somfy.h +++ b/src/Somfy.h @@ -30,10 +30,10 @@ enum class radio_proto : byte { // Ordinal byte 0-255 }; enum class somfy_commands : byte { Unknown0 = 0x0, - My = 0x1, - Up = 0x2, - MyUp = 0x3, - Down = 0x4, + My = 0x2,//DOWN + Up = 0x1, + MyUp = 0x4, //up + Down = 0x3, MyDown = 0x5, UpDown = 0x6, MyUpDown = 0x7, diff --git a/src/SomfyController.ino b/src/SomfyController.ino index 9fcb56b..1afa189 100644 --- a/src/SomfyController.ino +++ b/src/SomfyController.ino @@ -42,7 +42,7 @@ void setup() { net.setup(); somfy.begin(); //git.checkForUpdate(); - esp_task_wdt_init(7, true); //enable panic so ESP32 restarts + esp_task_wdt_init(15, true); //enable panic so ESP32 restarts esp_task_wdt_add(NULL); //add current thread to WDT watch } diff --git a/src/WResp.cpp b/src/WResp.cpp index e44e3ba..80545a6 100644 --- a/src/WResp.cpp +++ b/src/WResp.cpp @@ -46,11 +46,15 @@ void JsonResponse::endResponse() { server->sendContent("", 0); } void JsonResponse::send() { + Serial.println("JsonResponse::send start "); + unsigned long ts = millis(); + esp_task_wdt_reset(); if(!this->_headersSent) server->send_P(200, "application/json", this->buff); else server->sendContent(this->buff); //Serial.printf("Sent %d bytes %d\n", strlen(this->buff), this->buffSize); this->buff[0] = 0x00; this->_headersSent = true; + Serial.printf("JsonResponse::send end took %d ms\n", millis() - ts); } void JsonResponse::_safecat(const char *val, bool escape) { size_t len = (escape ? this->calcEscapedLength(val) : strlen(val)) + strlen(this->buff); @@ -130,8 +134,9 @@ void JsonFormatter::addElem(const char *name, uint32_t nval) { sprintf(this->_nu void JsonFormatter::addElem(const char *name, int16_t nval) { sprintf(this->_numbuff, "%d", nval); this->_appendNumber(name); } void JsonFormatter::addElem(const char *name, uint16_t nval) { sprintf(this->_numbuff, "%u", nval); this->_appendNumber(name); } void JsonFormatter::addElem(const char *name, int64_t lval) { sprintf(this->_numbuff, "%lld", (long long)lval); this->_appendNumber(name); } -void JsonFormatter::addElem(const char *name, uint64_t lval) { sprintf(this->_numbuff, "%llu", (unsigned long long)lval); this->_appendNumber(name); } */ +void JsonFormatter::addElem(const char *name, uint64_t lval) { sprintf(this->_numbuff, "%llu", (unsigned long long)lval); this->_appendNumber(name); } + void JsonFormatter::addElem(const char *name, bool bval) { strcpy(this->_numbuff, bval ? "true" : "false"); this->_appendNumber(name); } void JsonFormatter::_safecat(const char *val, bool escape) { diff --git a/src/WResp.h b/src/WResp.h index 4bda5d5..2efa89e 100644 --- a/src/WResp.h +++ b/src/WResp.h @@ -1,5 +1,6 @@ #include #include +#include #include "Somfy.h" #ifndef wresp_h #define wresp_h @@ -51,6 +52,7 @@ class JsonFormatter { void addElem(const char* name, uint32_t lval); void addElem(const char* name, bool bval); void addElem(const char *name, const char *val); + void addElem(const char* name, uint64_t lval); }; class JsonResponse : public JsonFormatter { protected: diff --git a/src/Web.cpp b/src/Web.cpp index 832b153..b5c148e 100644 --- a/src/Web.cpp +++ b/src/Web.cpp @@ -249,6 +249,7 @@ void Web::handleController(WebServer &server) { resp.addElem("maxGroupedShades", (uint8_t)SOMFY_MAX_GROUPED_SHADES); resp.addElem("maxLinkedRemotes", (uint8_t)SOMFY_MAX_LINKED_REMOTES); resp.addElem("startingAddress", (uint32_t)somfy.startingAddress); + resp.beginObject("transceiver"); somfy.transceiver.toJSON(resp); resp.endObject(); @@ -815,6 +816,7 @@ void Web::handleDiscovery(WebServer &server) { resp.addElem("free", ESP.getFreeHeap()); resp.addElem("min", ESP.getMinFreeHeap()); resp.addElem("total", ESP.getHeapSize()); + resp.addElem("uptime", (uint64_t)millis()); resp.endObject(); resp.beginArray("rooms"); somfy.toJSONRooms(resp); From 9ccc2d4ff50c0100e74003d44a925a2697c0b843 Mon Sep 17 00:00:00 2001 From: cjkas Date: Sat, 21 Mar 2026 19:21:03 +0100 Subject: [PATCH 08/16] async server introduction --- minify.py | 8 ++++---- platformio.ini | 7 ++++--- src/SomfyController.ino | 34 ++++++++++++++++++++++++++++++++-- src/Web.cpp | 18 ++++++++++++++++++ 4 files changed, 58 insertions(+), 9 deletions(-) diff --git a/minify.py b/minify.py index bff2956..a5c581c 100644 --- a/minify.py +++ b/minify.py @@ -133,12 +133,12 @@ def minify_svg(text: str) -> str: MINIFIERS = { - ".html": minify_html, - ".htm": minify_html, - ".css": minify_css, + # ".html": minify_html, + # ".htm": minify_html, + # ".css": minify_css, # ".js": minify_js, # ".json": minify_json, - ".svg": minify_svg, + # ".svg": minify_svg, # ".xml": minify_svg, # same approach works for generic XML } diff --git a/platformio.ini b/platformio.ini index da43e41..f823a0d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -20,11 +20,12 @@ lib_deps = links2004/WebSockets@^2.7.3 lsatan/SmartRC-CC1101-Driver-Lib@^2.5.7 knolleary/PubSubClient@^2.8 + esp32async/ESPAsyncWebServer @ ^3.10.3 extra_scripts = pre:minify.py -board_build.partitions = min_spiffs.csv +board_build.partitions = huge_app.csv board_build.filesystem = littlefs build_flags = - -DCONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=1 + -DCONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=1 -DCONFIG_ESP_COREDUMP_ENABLE_TO_UART=1 -DCONFIG_ESP_COREDUMP_DATA_FORMAT_ELF=1 -DCONFIG_ESP_COREDUMP_CHECKSUM_CRC32=1 @@ -47,7 +48,7 @@ lib_deps = lsatan/SmartRC-CC1101-Driver-Lib@^2.5.7 knolleary/PubSubClient@^2.8 extra_scripts = pre:minify.py -board_build.partitions = min_spiffs.csv +board_build.partitions = huge_app.csv board_build.filesystem = littlefs [env:esp32c3dev] diff --git a/src/SomfyController.ino b/src/SomfyController.ino index 1afa189..9e0afe1 100644 --- a/src/SomfyController.ino +++ b/src/SomfyController.ino @@ -24,11 +24,41 @@ GitUpdater git; uint32_t oldheap = 0; +void listDir(const char *dirname, uint8_t levels) { + Serial.printf("Listing: %s\n", dirname); + File root = LittleFS.open(dirname); + if (!root || !root.isDirectory()) { + Serial.println("Failed to open directory"); + return; + } + File file = root.openNextFile(); + while (file) { + if (file.isDirectory()) { + Serial.printf(" DIR : %s\n", file.name()); + if (levels) listDir(file.path(), levels - 1); + } else { + Serial.printf(" FILE: %-30s %d bytes\n", file.name(), file.size()); + } + file = root.openNextFile(); + } +} + void setup() { Serial.begin(115200); Serial.println(); - log_i("Startup/Boot...."); - log_i("Mounting File System..."); + Serial.println("Startup/Boot...."); + Serial.println("Mounting File System..."); + + + if (LittleFS.begin()) { + Serial.printf("\nTotal: %d bytes\n", LittleFS.totalBytes()); + Serial.printf("Used: %d bytes\n", LittleFS.usedBytes()); + Serial.printf("Free: %d bytes\n", LittleFS.totalBytes() - LittleFS.usedBytes()); + Serial.println(); + listDir("/", 3); + } else { + Serial.println("LittleFS mount failed!"); + } if(LittleFS.begin()) Serial.println("File system mounted successfully"); else Serial.println("Error mounting file system"); diff --git a/src/Web.cpp b/src/Web.cpp index b5c148e..9f2492d 100644 --- a/src/Web.cpp +++ b/src/Web.cpp @@ -14,6 +14,8 @@ #include "MQTT.h" #include "GitOTA.h" #include "Network.h" +#include +#include extern ConfigSettings settings; extern SSDPClass SSDP; @@ -40,8 +42,24 @@ static const char _encoding_json[] = "application/json"; WebServer apiServer(8081); WebServer server(80); +AsyncWebServer aserver(81); void Web::startup() { Serial.println("Launching web server..."); + aserver.serveStatic("/", LittleFS, "/").setDefaultFile("index.html"); + + aserver.on("/loginContext", HTTP_GET, [](AsyncWebServerRequest *request) { + AsyncJsonResponse *response = new AsyncJsonResponse(); + JsonObject root = response->getRoot().to(); + root["type"] = static_cast(settings.Security.type); + root["permissions"] = settings.Security.permissions; + root["serverId"] = settings.serverId; + root["version"] = settings.fwVersion.name; + root["model"] = "ESPSomfyRTS"; + root["hostname"] = settings.hostname; + response->setLength(); + request->send(response); + }); + aserver.begin(); } void Web::loop() { server.handleClient(); From 8c51e1a518048f772092466325043a29ef2a3a89 Mon Sep 17 00:00:00 2001 From: cjkas Date: Mon, 23 Mar 2026 08:11:12 +0100 Subject: [PATCH 09/16] up --- .claude/settings.local.json | 7 + .gitignore | 4 +- .vscode/c_cpp_properties.json | 797 +++++++++++++++++----------------- .vscode/launch.json | 6 +- README.md | 3 +- data/apple-icon.png | Bin 6625 -> 0 bytes data/appversion | 1 - data/favicon.png | Bin 1389 -> 0 bytes data/icon.png | Bin 13572 -> 0 bytes data/icon.svg.gz | Bin 9066 -> 0 bytes data/icons.css.gz | Bin 4197 -> 0 bytes data/index.html.gz | Bin 9447 -> 0 bytes data/index.js.gz | Bin 37312 -> 0 bytes data/login.html.gz | Bin 831 -> 0 bytes data/main.css.gz | Bin 3221 -> 0 bytes data/widgets.css.gz | Bin 1951 -> 0 bytes platformio.ini | 2 +- src/Sockets.cpp | 3 + src/Somfy.cpp | 1 + src/SomfyController.ino | 12 + src/WResp.cpp | 29 ++ src/WResp.h | 10 + src/Web.cpp | 789 ++++++++++++++++++++++++++++++++- src/Web.h | 26 +- 24 files changed, 1285 insertions(+), 405 deletions(-) create mode 100644 .claude/settings.local.json delete mode 100644 data/apple-icon.png delete mode 100644 data/appversion delete mode 100644 data/favicon.png delete mode 100644 data/icon.png delete mode 100644 data/icon.svg.gz delete mode 100644 data/icons.css.gz delete mode 100644 data/index.html.gz delete mode 100644 data/index.js.gz delete mode 100644 data/login.html.gz delete mode 100644 data/main.css.gz delete mode 100644 data/widgets.css.gz diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..6bc1b04 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": [ + "Bash(~/.platformio/penv/Scripts/platformio.exe run:*)" + ] + } +} diff --git a/.gitignore b/.gitignore index da20569..66186f9 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,6 @@ SomfyController.ino.esp32s2.bin .vscode/ .pio/ data/ -build/ \ No newline at end of file +build/ +coredump_report.txt +coredump.bin diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index e4e5e8d..218700a 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -17,7 +17,9 @@ "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/HTTPClient/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Preferences/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/LittleFS/src", + "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/ESPAsyncWebServer/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/FS/src", + "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/AsyncTCP/src", "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/PubSubClient/src", "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/SmartRC-CC1101-Driver-Lib", "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/WebSockets/src", @@ -26,199 +28,202 @@ "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Ethernet/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/WiFi/src", "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/ArduinoJson/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/newlib/platform_include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/freertos/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/freertos/include/esp_additions/freertos", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/freertos/port/riscv/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/freertos/include/esp_additions", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_hw_support/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_hw_support/include/soc", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_hw_support/include/soc/esp32c3", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_hw_support/port/esp32c3", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_hw_support/port/esp32c3/private_include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/heap/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/log/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/lwip/include/apps", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/lwip/include/apps/sntp", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/lwip/lwip/src/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/lwip/port/esp32/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/lwip/port/esp32/include/arch", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/soc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/soc/esp32c3", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/soc/esp32c3/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/hal/esp32c3/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/hal/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/hal/platform_port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_rom/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_rom/include/esp32c3", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_rom/esp32c3", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_common/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_system/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_system/port/soc", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_system/port/include/riscv", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_system/port/public_compat", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/riscv/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/driver/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/driver/esp32c3/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_pm/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_ringbuf/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/efuse/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/efuse/esp32c3/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/vfs/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_wifi/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_event/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_netif/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_eth/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/tcpip_adapter/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_phy/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_phy/esp32c3/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_ipc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/app_trace/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_timer/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/mbedtls/port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/mbedtls/mbedtls/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/mbedtls/esp_crt_bundle/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/app_update/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/spi_flash/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bootloader_support/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/nvs_flash/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/pthread/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_gdbstub/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_gdbstub/riscv", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_gdbstub/esp32c3", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espcoredump/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espcoredump/include/port/riscv", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/wpa_supplicant/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/wpa_supplicant/port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/wpa_supplicant/esp_supplicant/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/ieee802154/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/console", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/asio/asio/asio/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/asio/port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/common/osi/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/include/esp32c3/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/common/api/include/api", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/common/btc/profile/esp/blufi/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/common/btc/profile/esp/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/host/bluedroid/api/include/api", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_common/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_common/tinycrypt/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_core", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_core/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_core/storage", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/btc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_models/common/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_models/client/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_models/server/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/api/core/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/api/models/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/api", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/cbor/port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/unity/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/unity/unity/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/cmock/CMock/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/coap/port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/coap/libcoap/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/nghttp/port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/nghttp/nghttp2/lib/includes", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-tls", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-tls/esp-tls-crypto", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_adc_cal/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_hid/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/tcp_transport/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_http_client/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_http_server/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_https_ota/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_https_server/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_lcd/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_lcd/interface", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/protobuf-c/protobuf-c", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/protocomm/include/common", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/protocomm/include/security", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/protocomm/include/transports", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/mdns/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_local_ctrl/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/sdmmc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_serial_slave_link/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_websocket_client/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/expat/expat/expat/lib", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/expat/port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/wear_levelling/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/fatfs/diskio", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/fatfs/vfs", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/fatfs/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/freemodbus/freemodbus/common/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/idf_test/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/idf_test/include/esp32c3", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/jsmn/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/json/cJSON", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/libsodium/libsodium/src/libsodium/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/libsodium/port_include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/mqtt/esp-mqtt/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/openssl/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/spiffs/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/wifi_provisioning/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/rmaker_common/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_diagnostics/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/rtc_store/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_insights/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/json_parser/upstream/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/json_parser/upstream", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/json_generator/upstream", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_schedule/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp_secure_cert_mgr/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_rainmaker/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/gpio_button/button/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/qrcode/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/ws2812_led", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_littlefs/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/tool", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/typedef", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/image", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/math", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/nn", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/layer", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/detect", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/model_zoo", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp32-camera/driver/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp32-camera/conversions/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/dotprod/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/support/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/support/mem/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/windows/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/windows/hann/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/windows/blackman/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/windows/blackman_harris/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/windows/blackman_nuttall/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/windows/nuttall/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/windows/flat_top/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/iir/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/fir/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/math/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/math/add/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/math/sub/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/math/mul/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/math/addc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/math/mulc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/math/sqrt/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/matrix/mul/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/matrix/add/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/matrix/addc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/matrix/mulc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/matrix/sub/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/matrix/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/fft/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/dct/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/conv/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/common/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/matrix/mul/test/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/kalman/ekf/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/kalman/ekf_imu13states/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/fb_gfx/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/qio_qspi/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/newlib/platform_include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include/esp_additions/freertos", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/port/xtensa/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include/esp_additions", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include/soc", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include/soc/esp32", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/port/esp32", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/port/esp32/private_include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/heap/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/log/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/include/apps", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/include/apps/sntp", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/lwip/src/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/port/esp32/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/port/esp32/include/arch", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/esp32", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/esp32/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/esp32/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/platform_port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/include/esp32", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/esp32", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_common/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/port/soc", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/port/public_compat", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/xtensa/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/xtensa/esp32/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/driver/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/driver/esp32/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_pm/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_ringbuf/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/efuse/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/efuse/esp32/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/vfs/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_wifi/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_event/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_netif/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_eth/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/tcpip_adapter/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_phy/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_phy/esp32/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_ipc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/app_trace/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_timer/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/mbedtls/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/esp_crt_bundle/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/app_update/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/spi_flash/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bootloader_support/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nvs_flash/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/pthread/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/xtensa", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/esp32", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espcoredump/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espcoredump/include/port/xtensa", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/esp_supplicant/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ieee802154/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/console", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/asio/asio/asio/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/asio/port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/osi/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/include/esp32/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/api/include/api", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/btc/profile/esp/blufi/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/btc/profile/esp/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/host/bluedroid/api/include/api", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_common/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_common/tinycrypt/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core/storage", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/btc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/common/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/client/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/server/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api/core/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api/models/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/cbor/port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/unity/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/unity/unity/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/cmock/CMock/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/coap/port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/coap/libcoap/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nghttp/port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nghttp/nghttp2/lib/includes", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-tls", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-tls/esp-tls-crypto", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_adc_cal/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hid/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/tcp_transport/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_http_client/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_http_server/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_https_ota/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_https_server/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_lcd/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_lcd/interface", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protobuf-c/protobuf-c", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/common", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/security", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/transports", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mdns/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_local_ctrl/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/sdmmc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_serial_slave_link/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_websocket_client/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/expat/expat/expat/lib", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/expat/port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wear_levelling/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/diskio", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/vfs", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freemodbus/freemodbus/common/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/idf_test/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/idf_test/include/esp32", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/jsmn/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json/cJSON", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/libsodium/libsodium/src/libsodium/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/libsodium/port_include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mqtt/esp-mqtt/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/openssl/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/perfmon/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/spiffs/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ulp/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wifi_provisioning/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/rmaker_common/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_diagnostics/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/rtc_store/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_insights/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_parser/upstream/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_parser/upstream", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_generator/upstream", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_schedule/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp_secure_cert_mgr/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rainmaker/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/gpio_button/button/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/qrcode/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ws2812_led", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_littlefs/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/tool", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/typedef", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/image", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/math", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/nn", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/layer", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/detect", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/model_zoo", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32-camera/driver/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32-camera/conversions/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/dotprod/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/support/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/support/mem/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/hann/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman_harris/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman_nuttall/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/nuttall/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/flat_top/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/iir/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/fir/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/add/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/sub/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/mul/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/addc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/mulc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/sqrt/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/mul/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/add/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/addc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/mulc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/sub/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/fft/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/dct/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/conv/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/common/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/mul/test/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/kalman/ekf/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/kalman/ekf_imu13states/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fb_gfx/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/dio_qspi/include", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/cores/esp32", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/variants/esp32c3", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/variants/esp32", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/ArduinoOTA/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/BLE/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/BluetoothSerial/src", @@ -254,7 +259,9 @@ "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/HTTPClient/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Preferences/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/LittleFS/src", + "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/ESPAsyncWebServer/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/FS/src", + "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/AsyncTCP/src", "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/PubSubClient/src", "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/SmartRC-CC1101-Driver-Lib", "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/WebSockets/src", @@ -263,199 +270,202 @@ "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Ethernet/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/WiFi/src", "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/ArduinoJson/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/newlib/platform_include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/freertos/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/freertos/include/esp_additions/freertos", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/freertos/port/riscv/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/freertos/include/esp_additions", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_hw_support/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_hw_support/include/soc", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_hw_support/include/soc/esp32c3", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_hw_support/port/esp32c3", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_hw_support/port/esp32c3/private_include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/heap/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/log/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/lwip/include/apps", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/lwip/include/apps/sntp", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/lwip/lwip/src/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/lwip/port/esp32/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/lwip/port/esp32/include/arch", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/soc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/soc/esp32c3", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/soc/esp32c3/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/hal/esp32c3/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/hal/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/hal/platform_port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_rom/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_rom/include/esp32c3", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_rom/esp32c3", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_common/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_system/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_system/port/soc", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_system/port/include/riscv", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_system/port/public_compat", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/riscv/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/driver/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/driver/esp32c3/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_pm/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_ringbuf/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/efuse/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/efuse/esp32c3/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/vfs/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_wifi/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_event/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_netif/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_eth/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/tcpip_adapter/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_phy/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_phy/esp32c3/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_ipc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/app_trace/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_timer/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/mbedtls/port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/mbedtls/mbedtls/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/mbedtls/esp_crt_bundle/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/app_update/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/spi_flash/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bootloader_support/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/nvs_flash/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/pthread/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_gdbstub/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_gdbstub/riscv", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_gdbstub/esp32c3", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espcoredump/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espcoredump/include/port/riscv", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/wpa_supplicant/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/wpa_supplicant/port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/wpa_supplicant/esp_supplicant/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/ieee802154/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/console", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/asio/asio/asio/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/asio/port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/common/osi/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/include/esp32c3/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/common/api/include/api", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/common/btc/profile/esp/blufi/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/common/btc/profile/esp/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/host/bluedroid/api/include/api", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_common/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_common/tinycrypt/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_core", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_core/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_core/storage", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/btc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_models/common/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_models/client/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/mesh_models/server/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/api/core/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/api/models/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/bt/esp_ble_mesh/api", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/cbor/port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/unity/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/unity/unity/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/cmock/CMock/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/coap/port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/coap/libcoap/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/nghttp/port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/nghttp/nghttp2/lib/includes", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-tls", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-tls/esp-tls-crypto", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_adc_cal/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_hid/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/tcp_transport/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_http_client/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_http_server/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_https_ota/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_https_server/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_lcd/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_lcd/interface", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/protobuf-c/protobuf-c", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/protocomm/include/common", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/protocomm/include/security", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/protocomm/include/transports", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/mdns/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_local_ctrl/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/sdmmc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_serial_slave_link/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_websocket_client/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/expat/expat/expat/lib", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/expat/port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/wear_levelling/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/fatfs/diskio", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/fatfs/vfs", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/fatfs/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/freemodbus/freemodbus/common/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/idf_test/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/idf_test/include/esp32c3", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/jsmn/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/json/cJSON", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/libsodium/libsodium/src/libsodium/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/libsodium/port_include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/mqtt/esp-mqtt/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/openssl/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/spiffs/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/wifi_provisioning/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/rmaker_common/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_diagnostics/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/rtc_store/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_insights/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/json_parser/upstream/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/json_parser/upstream", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/json_generator/upstream", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_schedule/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp_secure_cert_mgr/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_rainmaker/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/gpio_button/button/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/qrcode/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/ws2812_led", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp_littlefs/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/tool", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/typedef", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/image", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/math", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/nn", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/layer", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/detect", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp-dl/include/model_zoo", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp32-camera/driver/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/esp32-camera/conversions/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/dotprod/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/support/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/support/mem/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/windows/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/windows/hann/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/windows/blackman/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/windows/blackman_harris/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/windows/blackman_nuttall/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/windows/nuttall/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/windows/flat_top/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/iir/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/fir/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/math/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/math/add/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/math/sub/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/math/mul/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/math/addc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/math/mulc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/math/sqrt/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/matrix/mul/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/matrix/add/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/matrix/addc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/matrix/mulc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/matrix/sub/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/matrix/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/fft/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/dct/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/conv/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/common/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/matrix/mul/test/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/kalman/ekf/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/espressif__esp-dsp/modules/kalman/ekf_imu13states/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/include/fb_gfx/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32c3/qio_qspi/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/newlib/platform_include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include/esp_additions/freertos", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/port/xtensa/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include/esp_additions", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include/soc", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include/soc/esp32", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/port/esp32", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/port/esp32/private_include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/heap/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/log/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/include/apps", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/include/apps/sntp", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/lwip/src/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/port/esp32/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/port/esp32/include/arch", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/esp32", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/esp32/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/esp32/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/platform_port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/include/esp32", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/esp32", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_common/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/port/soc", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/port/public_compat", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/xtensa/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/xtensa/esp32/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/driver/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/driver/esp32/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_pm/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_ringbuf/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/efuse/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/efuse/esp32/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/vfs/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_wifi/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_event/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_netif/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_eth/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/tcpip_adapter/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_phy/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_phy/esp32/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_ipc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/app_trace/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_timer/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/mbedtls/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/esp_crt_bundle/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/app_update/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/spi_flash/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bootloader_support/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nvs_flash/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/pthread/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/xtensa", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/esp32", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espcoredump/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espcoredump/include/port/xtensa", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/esp_supplicant/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ieee802154/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/console", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/asio/asio/asio/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/asio/port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/osi/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/include/esp32/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/api/include/api", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/btc/profile/esp/blufi/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/btc/profile/esp/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/host/bluedroid/api/include/api", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_common/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_common/tinycrypt/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core/storage", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/btc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/common/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/client/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/server/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api/core/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api/models/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/cbor/port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/unity/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/unity/unity/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/cmock/CMock/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/coap/port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/coap/libcoap/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nghttp/port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nghttp/nghttp2/lib/includes", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-tls", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-tls/esp-tls-crypto", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_adc_cal/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hid/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/tcp_transport/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_http_client/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_http_server/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_https_ota/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_https_server/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_lcd/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_lcd/interface", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protobuf-c/protobuf-c", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/common", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/security", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/transports", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mdns/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_local_ctrl/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/sdmmc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_serial_slave_link/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_websocket_client/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/expat/expat/expat/lib", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/expat/port/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wear_levelling/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/diskio", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/vfs", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freemodbus/freemodbus/common/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/idf_test/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/idf_test/include/esp32", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/jsmn/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json/cJSON", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/libsodium/libsodium/src/libsodium/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/libsodium/port_include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mqtt/esp-mqtt/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/openssl/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/perfmon/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/spiffs/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ulp/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wifi_provisioning/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/rmaker_common/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_diagnostics/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/rtc_store/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_insights/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_parser/upstream/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_parser/upstream", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_generator/upstream", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_schedule/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp_secure_cert_mgr/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rainmaker/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/gpio_button/button/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/qrcode/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ws2812_led", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_littlefs/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/tool", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/typedef", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/image", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/math", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/nn", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/layer", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/detect", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/model_zoo", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32-camera/driver/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32-camera/conversions/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/dotprod/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/support/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/support/mem/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/hann/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman_harris/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman_nuttall/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/nuttall/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/flat_top/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/iir/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/fir/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/add/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/sub/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/mul/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/addc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/mulc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/sqrt/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/mul/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/add/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/addc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/mulc/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/sub/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/fft/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/dct/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/conv/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/common/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/mul/test/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/kalman/ekf/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/kalman/ekf_imu13states/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fb_gfx/include", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/dio_qspi/include", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/cores/esp32", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/variants/esp32c3", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/variants/esp32", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/ArduinoOTA/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/BLE/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/BluetoothSerial/src", @@ -482,7 +492,12 @@ }, "defines": [ "PLATFORMIO=60119", - "ARDUINO_ESP32C3_DEV", + "ARDUINO_ESP32_DEV", + "CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=1", + "CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF=1", + "CONFIG_ESP_COREDUMP_CHECKSUM_CRC32=1", + "CONFIG_ESP_TASK_WDT_PANIC=1", + "CONFIG_ESP_COREDUMP_DECODE_INFO=1", "HAVE_CONFIG_H", "MBEDTLS_CONFIG_FILE=\"mbedtls/esp_config.h\"", "UNITY_INCLUDE_CONFIG_H", @@ -493,18 +508,18 @@ "_POSIX_READER_WRITER_LOCKS", "ARDUINO_ARCH_ESP32", "ESP32", - "F_CPU=160000000L", + "F_CPU=240000000L", "ARDUINO=10812", - "ARDUINO_VARIANT=\"esp32c3\"", - "ARDUINO_BOARD=\"Espressif ESP32-C3-DevKitM-1\"", - "ARDUINO_PARTITION_min_spiffs", + "ARDUINO_VARIANT=\"esp32\"", + "ARDUINO_BOARD=\"Espressif ESP32 Dev Module\"", + "ARDUINO_PARTITION_huge_app", "" ], "cStandard": "gnu99", "cppStandard": "gnu++11", - "compilerPath": "C:/Users/oem/.platformio/packages/toolchain-riscv32-esp/bin/riscv32-esp-elf-gcc.exe", + "compilerPath": "C:/Users/oem/.platformio/packages/toolchain-xtensa-esp32/bin/xtensa-esp32-elf-gcc.exe", "compilerArgs": [ - "-march=rv32imc", + "-mlongcalls", "" ] } diff --git a/.vscode/launch.json b/.vscode/launch.json index 6f795a7..c6b0639 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -14,7 +14,7 @@ "name": "PIO Debug", "executable": "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/build/esp32dev/firmware.elf", "projectEnvName": "esp32devdbg", - "toolchainBinDir": "C:/Users/oem/.platformio/packages/toolchain-riscv32-esp/bin", + "toolchainBinDir": "C:/Users/oem/.platformio/packages/toolchain-xtensa-esp32/bin", "internalConsoleOptions": "openOnSessionStart", "preLaunchTask": { "type": "PlatformIO", @@ -27,7 +27,7 @@ "name": "PIO Debug (skip Pre-Debug)", "executable": "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/build/esp32dev/firmware.elf", "projectEnvName": "esp32devdbg", - "toolchainBinDir": "C:/Users/oem/.platformio/packages/toolchain-riscv32-esp/bin", + "toolchainBinDir": "C:/Users/oem/.platformio/packages/toolchain-xtensa-esp32/bin", "internalConsoleOptions": "openOnSessionStart" }, { @@ -36,7 +36,7 @@ "name": "PIO Debug (without uploading)", "executable": "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/build/esp32dev/firmware.elf", "projectEnvName": "esp32devdbg", - "toolchainBinDir": "C:/Users/oem/.platformio/packages/toolchain-riscv32-esp/bin", + "toolchainBinDir": "C:/Users/oem/.platformio/packages/toolchain-xtensa-esp32/bin", "internalConsoleOptions": "openOnSessionStart", "loadMode": "manual" } diff --git a/README.md b/README.md index 4555054..e30a754 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,6 @@ Configuration of the Transceiver is done with the ELECHOUSE_CC1101 library which pio pkg exec -p tool-esptoolpy -- esptool.py --port COM9 read_flash 0x3F0000 0x10000 coredump.bin - +C:\Users\oem\.platformio\packages\framework-espidf\export.ps1 esp-coredump info_corefile --core coredump.bin --core-format=raw --gdb C:\Users\oem\.platformio\packages\toolchain-xtensa-esp32\bin\xtensa-esp32-elf-gdb.exe .pio\build\esp32dev\firmware.elf > coredump_report.txt - C:\Users\oem\.platformio\packages\framework-espidf\export.ps1 \ No newline at end of file diff --git a/data/apple-icon.png b/data/apple-icon.png deleted file mode 100644 index 78fc318ef2fe46b798181b124911a197b8da84b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6625 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 diff --git a/data/appversion b/data/appversion deleted file mode 100644 index 48a6b50..0000000 --- a/data/appversion +++ /dev/null @@ -1 +0,0 @@ -2.4.7 \ No newline at end of file diff --git a/data/favicon.png b/data/favicon.png deleted file mode 100644 index 80a5dd37238a04a2057f764bc9f93f6d09c734e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1389 zcmV-z1(N!SP)2 z5JNzPmlNp8-} zne(6jnft%zDiPssn^LL}7zgA4mqp~q?3}*q*)ujAXaX(+9;MVn*$G!40nmU0cmV}? zI#2Y4{S63E2e^UXvJ(n;ypKQ(1V#eibapBrMW#OY!g5_?%HY070JI`Q4Zs9oW*;S9 zN9IzZvO1uYTIO-9E#n^aXqSt-A`aDchCeUZ0{BHF1=qZWUN>sTs{UqJ$=#{QiVy3Z zEeC9?v+p_`Pzup1!$3omp|em1da-MtJ0tFPqf+`y2Js zQ~ff2nCFJ~>ZVF40*zNp$8cE+m!$~hY5DF%tJ?Z`QV)N?%~zYE&Xlpu;G^lX4_`ac>&{Kz;HmeSV*!rru%Q&Ia9*65d)8 zG&3gV$lK)&m`*HD9p{s|Q~W*Bl5PRy_tR9@IYeh&=vXtP$Sr604RlsK)6Z=BETQ*Q zHmUy_jCwfcmKUDPHNRg-D}Z32=O5W_0lf24*vx*^FOIo-kb(U(*J16FkeUBPKu({t z2?jLN#`(l$!Jw#1zW!i|Y&WnG7(#}%UfG=h@Bx1TaUgIn04xPQ0{Q_vdb>%l+j0>(3>@xleAfD^E$`ND z#K0J(RAVF($&1J1-Rgng@Ap`iMKYOe>+A;X_Gd592Bf3WsK2JBrU?jNyN?2<1EYY+ zz*7JV7cL|e3aOf!8aaCODEayM#N%-ykqE7=t)$awR<2yBDk>`E$dMxy6cpg`cv!P$ zjk0Z<ir3^8iD8r+VttDK%%# z9F z9cmpqbjS$=0=l%cRORO88t7ed$#FqJh1(L8CMs5g=*y}00000NkvXXu0mjfTp*C0 diff --git a/data/icon.png b/data/icon.png deleted file mode 100644 index cbc072e7448d30ce1cd1165992540df5a8666522..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13572 zcmbVz1yEH{+wMMxgS3>ibazU}Ap|7^1nHC#q`N_+6%YjJMoPM+K@p@IX`~U5ZiKt| z=H9t8|ID5F|I5If+Iz1zpXYhkK18TJQ^3I_$AlmV=jju<=MV$~Phk-HJ@8=QTxba% zFdU!gxk)Y$^^@bKWWwtwwnX6k6c<=||U`d6GB zf@q+pa?+ZfX}jqjX%8M>-b$&e&Y|ygaw+L8J&9X^$EhFSJ;0!g{C=eQfUqFP_#~_| zdn21U9HAcJO-B<+B^^h=_~bqT!_&}^0kLZS*?-qJo5@a=-*)={_Wup`ZZrBfll}+% z{4G}1)|NPVz{`(-zj2_+c?F%pHZigu1-3!qs97ipBxQdK(a7#tSpQ`94Nzdee7ny>5VTs3=drq4ZjvvfsN!WB)X-vY~8gW z*MrPp&8R0DzgaDBI=gi_Z?0t^1bmM+#|J7wI1;v_^!wI?3%fxA_dXzMahF8LRo~_s zWG&;XGeX9&O)L`ve$~@GqQX~wD-3XH7HPS8m@lpgcVI`q@0hhQW8uIhEu=^E@jDTn z9;%5beU@DH&=J)I78P!-EIQCfP68RURVw~i*|EBD-gEndV+ro6)m`k6<#dDggr16 zdY*I<#5sq)0jO4d$UCv}D0D<(x z#+SH%5+xCs+*TiA4J)}fjM7fn-~6jjKwsL9bra@lzW%Om9|0A>N=(-kd_9Oz2f6RE zMK-H{ArL5)^tDOj9J^39@QnXRufS0vFbsB#HVg&Ait&e;3cm$Ur7uq+DkyB2O@%B6 zYeU*#CVYXD{l34brx-8j)!)z{$gpY1Y(7##NI64D9PNZ+;p^cU+!5aXvM>`%0on<^ z{FI5gLY0}!jpB)ibSeBH=Pnt{hRJ=t5jXzvor>*Cc2ks!d+j%H(^HF*!5%QY&C)vfN}NQ!Q*=K{TW^P_%e>qJYFg7CjKD z#ZwnEm@rP)y6Ltjo2sdm=0B9I$FEFSbG zE2!by=~=jjjw+OzJdF*>JwS8wy+yGcJm8tHhZvCQ60fXq0w2&tA|U@te`#_a$<>5~-xJw0TVI6IP=*Is&pF1ub-%Ih z#K7D9LjQAa4)nnH3p3R4`?#rbcdrY6@Ckm7(LM{U^niMOGw-O_6xh4)LjQFALz@9r!l0XAh(Dt?P=P%iYVk8ceq+i8!fW9@)=+) zAs^QblT`RmFx~47PtrS2J8P?`Lu{^(G~bwI28nvBQ6iZw1x0qRD8~KYI;3}s!Wufu zPH#_O23&op55oTAo;zF(a`nK}$6dNlXY=>LDRH1r#H$)m(?_s}AvQ8;mblD0Vo)=N z3I44J#uJo@51R@Fo+r@w8ylTfm;X51UYwXo5B+t=yt$7gHyChDgq;)ESQ&pKi2Fxu z!+&d0P$8`*#l9{UGLJf8o(Z?I$hM6wu^3v6n6qG zP>czsAuQPe7QNN6CsWJm=szn3Gr{6FW~YndqX#Xt|K3YY3MH-Wpg{KI@~XB}7PsHl zV%>|842Ab7;9{t#4PnAHUU{>z>lD8E45Ea5clxlL_WlId z5=Nof%>g6B1kRs&KSE3QerlSWIyD8-Q{=AlF?%Ox19C~?7T1O`(~hDbAS&5HU*jMD z>^iaKUk#pqLETsQH?9OWr*?H%^mm|{_$%}(1KPhA7y$ixBB!F1N`?P|HYZsvJ>VKK z!EK_45q5z9xG2qPl28OuL29_|v~($t9Y|I& zaM*-m)7DN8MH7s;r|R#$q|NbCD}bwp*l5iKCoh^>b}f3j-^E@{K56=Uzk}?fRWg{{ zOnH9rH}gV247Vh&hIy94X8dh9I>P^Z9rn3+VQH{J@&yvr>L=<591s1vkFRi0_7nUU zQf`7xSc)s0801mbc2In()9sI=xscR*pIl ziTC3!8J(neK9b&*9J<+Ev75dJ$)$OZ570lD7gTW&&z)!g{e!WV93NZB(BNrecd}Q2 z^TJi{E_d)%+l0;rZOO>EfE_QG6)WkYxQ*Z}On?6wlz&cZ_=e`;G}P_@r;&vHzZYb) zaZ;{ARk+k_dF3KNzB%9k!;MHNhEn@5PV* zK3RLih5wLn>sJ1ye00!>Pk+so4wd0oRt@=Q(^Q(S!NHqPmd_OlUeYm=f){KA%-T~q z(wv-A&(Nn|?meRQVxHW4G*dx(oz-J};^LZVU6+h|Nb#fOEv^3OYYY*~(46xf0R$8$H*5_nl_!#|4#792aka5r zQrL@n{Oi6eZvD8N4y!rFY-dfpfhvoP$W`I}RYpv~Tilfmy#gXs1d+)g1 ztcM=)FQjC04css((FNtgQ7qbw=xDk>O*!}ve%`BFv$=GSTuLTfUd{7dtT8&YSo*tB z_WFKC-G{E;3Y}>ud*jaj3Z36isMQ|-?Ve|vaS~QN%Io*MenYx;qF(9$IvJIZmeEgW z)ck#_yS`bSW4q>M4v#)5_ki+P{OQNl4HQcoft@P%!Cns5pc6A`6JDR;%x;oU{^o`5eOY29$qSaHJP@@qpopj3QCmn<`e}WPn1`~v7 z^sX2`T3MtuzB*~-8~Dei^yhXIXP{QU%9!0>dfXp@8jzY&>}6-~o!mEoc02Gv+3U^k zn~s+jt0_d{XTR;NzprXWJ(7BY8H#p31-hty30`&vfTVUo5PaN<=6co^;ITyJo$dfwEt=rcTOaRdhR#Z-z{f$ zk^gY(OO%MTdPc9zlPI`OP}Vfl(_nN7)8E?%R8v6(1@oh;eG8dNU(~zLjx6Ng>WP+V z>B`u~2a99T^J{l>ZSxtnFw*xv>FIsZY%c-);?;1leimRzZ(MM4y(G?_s2g}4HQnjC z{%87po^O9AaXBHhR^s&ywYXjO zz*YZh6z5yNexFspk=|eaBXf1J8J}PFI+i6Lw52n7dpL!4&!wUs18a7rmdEq_Ogy!wa$Ec~*JXf8(&X z=DWz^gfhvenNX{>%%6f)KjlO%liic}J*K^~W@H!DWyvv{*KTgv+ix-Vcr-ljc5N! zrPob%6726Gzfbb9xR;z;zcdcfELj})a-4ACQha`8P@ZlnG1n{J?xiQY{Q@2(Yqp~D z0g1)zM$fTdIz6eHN@x8;fw9!ge5J5}dhdx&n5Ii7fGyX6#PC2)W(!(ZDSMf~j{!PA zHO6!-X}GlBqLz327+$1bbkkQV-g3q63{VUeGN*mrX;G2&qIrreuajR8Y-$v5G>#`Q z-T%A3nnc90m_}H2!eGwL@!;20Dym+T)d^cC()0Ik(yl^oABnUp#k8FPo^%EnN!i6N zRy#8uj9QYUK;Y>luh%qh#TwEG8JWtB2ghp%ar!wHi<7QJsdwDv#s2u|`BVd&V&2Hn zayAZ}-#ZF%z44VTvZF7gusm-)mqWJV&3n8+Xx;tOGGD}J2EIPwsLAA!e>n-?g`*Uh zh1Pgy1P-Il&r{1dag0=*qIsSO2QW%8=)+_nIBDq*LDNMPWv};9J}pAMtoNf3zNdP= z5iN`45yQ2g-}pUJsu15C9bK9T4Aep~P{kdHMbbhr<+WEBqPX4GhZa9_9!-xrZ1_!) zO|>M5S}hoMCrhg)35~eq<|CK5AGMs>;c>VqH$O&4LK+fA-@Lgwb~rAOX9eZf_tUD* zl@XRI;Tkgnqb_;s&b;)3e=rhq7;KYbS*E`HzDNs2(y#?(9aOjwsfppC%sW@s1lLj` zv&PC|sTTSb%`DAVA;WINF>x^lt>kl^l4bjyIYL*x&h_MoLm{b+(a;tqQ7_s|x~Skb zfW(#ma^g*n^FO9=$$pZkCotE-$(O;ubLHEN=%h{07phlHXlc5O=8DwiJ@HgqgrDE< zk_=F$l1Dy5y ze>z?nRS8l!vtDJHuc^kgZ@VYIn#~rkTIUDtxH-lvvo*^a7 z&}?s@HI`}Ro!j)NhrY9Eui1EE{_iVMh5dG_*ty)=*tZ=O-THNuy}1Dazvs=FEwjE+ zhJNt9LNVole^_?Es` zsff6?M|my`#>J+T(L6HDW-q!B_FqsWD56SoR$lNCgKM&P-x$&fOA5L!ew2ZF@hyM+ zHT#^YJ6W%RZ~)aTwco3v5j0}NmG_@1 zA~WN@@Lk)MQtp;%s-RKRv?zxw+P>pFFkG)lI!&%67%I8#>A(t{O*+OmE?mcm`8HWHQ zI`S)lb^QKu7HMV+kY&7wzq_6uhLS}bRy5`O%A%K>wmt28E6%>+us1h)?NG3&@VpnN zG!)PHX(tGF&)R%vF?ui*kJ29?y6x-z!9fev{XgON<~M4sY;?a-;QsM*caNt+o=#5T z36&}{7bnQvxqeYfb>iFC@mVbIy=4Y$$QO=upZiA@)4oDiN0`oH^prevJ+S&`qE)ap zSzffVK^6Ji3B@eF>U^6$;t9Q}`63I|w2Zjkg_ggr{G#hjuidmb!c~9(&eMtYq^0}{x6XN(9&_2@1=EHa_h_o z*{0~VSasECr`u7JUJV{M)6nCoXnfc#rpdiy(I6uwHPX!hD?}R)ht|`t^ulw=7jFR& zIOdz$aSD}`V#)wrutG)XSHqz+bEQwEYvHyzpzkkXn!Rn!M<8KvG^|lW9#lZ-d^-6) zr;wBIb?c>2cI3UN?ac;IS{@E|OJb=Mzf#xwWxxO{-PtilEb702se3PFVk)~7H{DU5 z90R3E;jsO0>|r@GhrmncV%LlOofA6pdz7Z%sQ4w^9pY6N3xQA=d;9W%YBLqZNpRAW zJE_#GPQcdvMKvGs5ED##uT=X^3PK%F;Uz;)-*3?zDSUF^L<@7 z+8U98%c&Sgog800T`{$jfDVXu=O5?L1{{Ls^(elohAk)FBaFY#i%r^o?c1*GA?0D{ za$41x_oD8plk%jiefCu&oS){e4 z>>c>hz0CkbPFjq`6bvC)B=jhBK1q}-P5Le!1kA_ziV&TbUThhCB^!Q?L75s-u4~6b z2Q0D%kkEO(q(3g7qn|}XY3bu$)#nk7=Z_Z7hY}e&Wn1VI936A;6@zP8E80lzIWhG< zc~V^bn~O8bHy{x`nkU{s{pJQ)!#B}4C{Z24yW6L1J@AasPo$`LdBC>&Z2_mija2>T zZ*BTNQ+shCQJno9yhSfDf_Pk0c4)1EC;Ujn`e5dv_~UkxkBUYNdC%j6e+HD7G~YaJ zT#Zs`WmPr2ExQP3Px)IKb*)zZ#C}R{2o#!uh~C6U1Acl5{f-eAtr;KjnAUUNFfOAZ z?g?dNY3Ojh3!>hhrS#F0^3Q!Z@23NJa`6V<6?0p?6;Y>#Ze~5RbfW15;{r zdTpE#R&Xz-tX5!@k4nwza0!!1MQ9Bql&>DjfN8YZ}K%zrL^U&`5(uf88=kVc*PtkpSjCI_@+vq}L<}r%yF_ZE}qMuV1r_hccG)6CKni=}GaDoK$pXQh43)`j}k7A{_MO5}|e?@Ub zJ5$Zr*!hjZ*%JsmEKgk1UY{Bi9dW#uul@GT!_15DIh^ORbu&xv=Vx_F%5JZ(`{o<# z_av+Ggn!v`2rvo;6H(pw97YV;BKetXrybvf%$U11Koh0{$4E06Hd{q&!a8UuQex3j{4=i7QQ$+r~lE5K3=gw_SxC^*HTx-Yb5o+`Aewxq>XR2KvK(?N;$niX~Bl| zkf!?0d7akHc?WM~x%gJ}E_G?1z(kYbl0YYo>A2sB;$Gd$px8F_pOvXT8g%D7hbebB zTC?FvW!R7rLt#yHMKEF$ID?^A^EziklJdRe>rnz2uKT-Hu)WbMG-gpUZJqx))j97U zGpDPVhJ3s_Ja#7b6L)nV-;A7%Lf$jk=YKoXw<4qm>XPUQ*Kj z_TDKq=7x3V#(4-Rhn``z@BNhg54LhiriGgu1_o3}4NE6X?0Am$J+WV*mhM~iv&RR8 zS2^ZTk07tVn8Rlf5CSTbF1Ka)5BDr?!`ppiv>?QvLlZ%K1YSc~;~xf_gYSV$ zf7)?~g1=+__czGkop_tMM9Df|8A;^uG+Gg~5q|zZ%G=gn*vez#;lV4s!+zeB;{_ge zTu7pogk-hfBETMHZ$ggbRWN!mYYy(+CLmies@gckh7i=(D@JeHLkQUj|64$q_@J&a zrg`EcZ690Z&=6fb(j#aPv^lT9u_m~IZJkFObJ*?9kXU$OY}+9-`omx-m{d*^tKAZ! zSHIkt#c#K#`?FX59s^E53(+F*8Bu=WWToTAy+f@S74&+7mxB&N#}8*AZ?A`nuXuEE z0=dHrKjV%)K;X$hGGQ@L%^^(&+>Nd7Xg&HB)EEB5xNZjn4rVCYz+D`P&UdonrPuD{ zaMBICR>YeayujflW0niubtnnPO*4x(&>WhFl;QhfXd9mC;qbxSs4QEso^yENy+BDu zxmoNiUVf>*eu|_E56jl?{bI=%nb2QoUmC;uI|?3U5N#-4;L--H!K2`YKCrdlx3&Sj z-_~+n8)`P{{7S{~_yz$3Gw{-eq|ntUB>toSB7?#F2f-vX87AQOxI^W*s_C}u&!Moq`_)5q2aC&TT_Jd{H#Fo zG$zf2O1#ZNJ}bHb;8EVX-jp>a#k6{Bf>_N?C+q+7zLfUd&bE5wu_&D9)1hw=jVwBH ze`PRy<0)O_F<%RY{plKv#a&n(58O6J_n&N<)<$XPP$i_x>4vbK2`#F3t7Y-%?mp5= zs0d)d=X9MSph+lz`D)4NE&8O3Nh3kNdra5)A@>A4J47I%+%YbljhvhwM3eJJPT*nt zeTW~nIhPlUvLt4_s*5L#UXYdDaVuSW=Aobh-PYM*;_Jdln}xf>`h9cbF9;DVIjDGW zo+D!D_GlRoW8uZkHnecYrV*3{gxTi45M=f5Lm5$fZvW2U0unC|7>1uDLevgH$&$}5 zy)190yR9+d;8qANf}c9<55-ayZd^6w8I){2uhj=Z4?^y~3m|nMtrrA=@jtwy-zQt| z$c|N(j|7?<=+C@j(hfp`570aNwUfUJjZe0la^G*kS$YJiBzuJ!^D(2deCIJi|JI)x zUdS1EU+oIb8>JA(?O7XQiCA-N!B7n+Qjov;Yfu$?Ap{CtxW8X55q;JSx7zDuB%+rK zD6deSYHVcLrY|}h)d<@Eq%r@0?_B)f+Z;(qXH30>aBpo_CInpS_rS}NHF+8gqz6R7 zCQ1mG7RG-9S7QrDcl$G*{b%9}Js%%|y1F_~vT{#vZ|7&FM8~yWqOwi#PeKt95t{Y( zhTnhu=rrM>5fUOvM8^kO*}Ni+f*ieC8?LLvqoaf6cif|MUtJOo zdsW{Nb8aSR8{J)u_`-LGhktZzEEX)!Z#6g^ibJvA$KAGHY1aLah?9#eI>YC$2s!Ud z#LJg271Ik|PnDUD<~^;knP7XYZs73P)|SI;@v-fu-y#mKem?*#dh+c zzE3UL4_sSw$CdndtPzu`-oZ&v|CZWkhgXn1iVwQ@+q9>pn5xym$;=$KJz2W#OieCm zTX?qp2)pW;{+qf52BQbJ!rq+6)*=xL1Unp^3qu`n8+28pF zzbiq#I@<^?!^ZXn&GA?P2$x zsN0`mt4u$6j|Hy+rDj6rXC^}#56&h*=o5{|kx*g&dK!IW`8`aN8#opAO( zzDAF|L7&qdjARF4v6DX&xnQLa3`O)!RUEl~SEs(mnwE5)=*q{>KT@Q@Ea|e2?Ew<}w_)Xem0oGcppf+_ z!+4?kw*#Mnlt((XHVH}(pT=deV7l)u>>hd@oUg@GQQ3bp}ua zp1(s#3_3$`siW;?>!t$;viv6>iZ~;BB(UVbs`8$DM+xsDZv5Rlk5O zU0)mlJER~fiFc!pAZ!~}FYt_NZHU}=XC8wZcSZd8@goOZ(e?7!+IGH?NWb2mvnnTw zQtT5ZW;-}}{)J*yfs|<{B9Zc27;g7qx|rcIYh)03Zq^x$F;}QgZ!=TF8YtB_-s*QX zmsYIYEG;MGm$ZOk>A!fByTshi0^wVT!#Y$$%4{ZzUkzuyS@DpUzvqAZ&*Mr2%V}$r zvH5sZ>HMIZS~^dD#HP0E2RCN=GqfmqlD$S;zGpg;h2&E3_e064IdX^Rr`dp-Qd#Zfzv|%)@@0qW!H~=b8 zHQSS%X(BH9zs$HnIbskpnanr3Mf1$IoFxvQo;i8zL&6A}LJn9&k9sVuc1nzwcVw4W z%zdZ5X|ELv5(e3?vz=ZRY<*TBtEh;1b92_VvR&Tx10YAm>nW}hqZV>PCS_!~Q41ag z#kOEno!#t9&@P}Zi1h01alm&xC`Db{-*LBwPkhlf>yD<>Y4+r$@><1D?6i+L8&%>q z1?K#!v!jr_+Tq8h5X5%do^%t58F}?x;%x41EWJXGN#+w788|5M_jj+f%Z+$QvBM)H zJAYN0Rr+1Ii(lf`m($bH!G|Qy9@g2;M#O z0A`m=ZO>CpRaq!KQ&ep_q+z7|R`cgK|3#Hi^7spchkv%W*rs-Kb-KGP1cTZ^P}p4uo14XN05?6fU=tLaTqH6?4`kMN zcII=KuIjRTBjvtB+IHB>K3AYhT?X1T!kUSJVgE-Ih2iM0Dhn(C6Z>DK|NdV}%C2agH6z8keP=>hU5;9S^JBE6LO2XT*-hRG8Jy#w(@b!3M$5ff2 z%lEIK{3_21OuvP4bqbmNz%I|waDsnGOWUcMDAEW^6|nA{Z}QmCHukGk+qMEkWl(GL zq74%XEWn~Pb`o^Z<6SFw&3HCj7kG| z)?u#U_pj#~8uOA(0PkD?MM;Pn>mu5X@>_2%U&r)n>gkcIrU*pbVJ0Fgd|(d_6{_q4 z96SV`c6oVuF-q3#?ATKATVgB7%Qxnk36NiGWCplL_?*pqkoYXfN0B?p%ZDm84kk3Q zz>);*^s-$Uo-UtkPmvI01YMxC@3ov|C^b8+{7BG)oGWZ0jDCWM_=(FN`2 zHqZY788H(!{ZPPW93ABzw&OKOm}r@Co6#Q^@7`?LFvlrFk8Hro=Sy#K9dE9^+A_zI z0jrnjR^N+}I8Dk`2K|U7Xb0%yinE^LnM}c=YfYA;4h{?#do-f&{*rSnf8L;r)qArf29zI9^_*2td6gBFdEm4bF z^GobE;;bBxGdzH}0KM=x+?-;?3bF-Z^ge#o-3kJ;3{3odQNkXFMZ(1bI{gLEbcGzc zRdQcJA6x(ckN$VZ`@oo;(qd=)K)|GlI{EH9zp{N#r>zuYs3bi9&VA)#j);t8W5|)5 zyH-^5g9})X1_LmZyE@w|@L2Rqd}(GjuV-x7;+;^SlKKP80qz(cg2xra6^=*d#YF=t z!*9A?I#IMOiA3h+x%zg6V3BxU9&bf2zD2{82Lyu-r3l(Z0^|`s9mtfTX8QQk!Ar#V zoF|5qx6>j>3y|?bz^$L+lPCL4WOs14RgmeQ1Dbv;o>hZ|iRs6#NV&n+PS8wLB-adg zhrO2k_gD=BxiOgjbJDk4LscRq8VDZG{seaIUl*w)fl5b$?CgCO#?_VsCOo8b#6dwW zyZn57iv0>%mDXcS-LsF&r*bX&J|Q4bZ;VYA2G5?|2dKJtRzC~`(#3$#qALc_AJjmt z{pD-xpmzN`m%QUAywV+`ps1)A(QVM^8Ua|#DzW0QBJLPXnh_e*5QZ()P8sDYCa`@i zEs@WyRAWv&^4RElN8$uzOZVP=O3??Ad&)p`O8|M#WeRLR@?ku9q!9hkiT53qpe-o~ z@>T?MhGVeb;ZJ6uR^Bg`tEfNVFl>ys6Fk4a;I%=SmzQ^5OQcqztdb^N`TdiHoE(CY znYk%gBr)5Bt`M;_UZlaL(gk|{cUjY4=c|fF_gyxkv_yZpbTyg~W&9E?Mbo5Qq;0-v~z2L~4y zHS(1^+TWmj?&*2b{U(kf+ht?0#-szD*vad4XrA}^GwRy6#d-(oS$USMkyraCQ^sPa z>Ed2h!+wWCK*nf%;xhb0An3ZO)>a1x@&69W!5Krp${YiXcuW(;FZw%11wB zE!C;)WV~7Kdgrt`oSnsiBE5$Hf&X@dlUAwf?#5|&Oury8Ngp|&pkqY=wE?2zM2FBfW^h(nvrheKY&VeAcHUe)jyV(?*(gEe*C$r?!DJ` zE6L2rNTjG5$M7}F!W7WxJMldL)|e@4#AFWN!|}Ner~)3WlY&PIQOwcw- zVaj2VT*$t}_iDG^D4__bEk{61K?6g1iz;vWEZ)V$Xx{_3+W>~UC^xV+Br7u+fvEwP zN<3+0d0Ej{Eso(Q5UHAy7pouXtf2L^wS#<>)CSc|Nkvyz*UByN%gtOaB3@C^#vT`s zy#>QcReuqWzYhUIxAk~(>eZ-$?&;F{VxzhUQYv#mM^C>7&`fkt3`mO@Hl(X-&2zPi zR-ia1w_Gj!<$=6%$@5zNjscFYQ%GRsr!vA%v7NyP3soF#4WSuw%n%QYV+df+=%Bwerkv;;hX-MfVf z3=yC6@)$JB49Li%LY0{at*oq80AB-kOq+U!1oKtg$B(QcB4j{M58KqprNL$j1Ax|=8JYYF37NkbfcV-aj7iHIb}#l@)s?f0ry zFOkm;Fld$=920znTuf^T(;J2t9|bh~7{FpmERVeW{2bY^`@UdHqJ5_UFP?}5hF+`= zy>S53rsw(ZFhi}~WI;P}xiwtsAe#qBH9>IcLV#cQ#})v%eW+HL1G3z9u0dV%hDQb+ znPj5>FeLOorJijX2*65DEWHfWiGF~A$Mhs_b#it$2o7b`)mpCu adcIh>6uIlhga2THo<4piS0-Z;^uGXrL0K&T diff --git a/data/icon.svg.gz b/data/icon.svg.gz deleted file mode 100644 index a5c99cd25e15efc0955d23bc45c76bc6442925a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9066 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 diff --git a/data/icons.css.gz b/data/icons.css.gz deleted file mode 100644 index ec95255b5c43a9789ba5986a9dc3d1a6b4d7f465..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4197 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 diff --git a/data/index.html.gz b/data/index.html.gz deleted file mode 100644 index 3f59ce3920a43c1ba6450895a468beb012f42152..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9447 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 diff --git a/data/index.js.gz b/data/index.js.gz deleted file mode 100644 index 859a849eaeae84fdd63c0c446e0e80a5f0011277..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37312 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 diff --git a/data/login.html.gz b/data/login.html.gz deleted file mode 100644 index 111518d23a80ed9e7288cc214b6744f59effeeee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 831 zcmV-F1Hk+riwFpQZm?+r|7>q(X>KlPbZu+^y;jX`)G!c!72^{~WdDj(v~0=&$^{UW z+8#K1?b*cCu_N0_yU)gfN8%M2+gWGJF6CF89FOOJzM09_&tF!LUVnIX_3`4I)V@(? zWVJhh2|OC`jA+(CNqY&mt+AdGZnOvOOL{AXuS)^Fi~tWuig~T16fIZLoBG0I=@xmY5-JTTiBGeVNy@Hb1(aHF?lwbQR25PI^p5YPmxZ^ z^d}n7oeI4BJ0%iB{Vf$iV88E@)lS=n_2GRINkPtWD_g&>@)PS>qEg~4M+i~w8{>vKqv+zW=pMch(4p2SPbT64>|II zp<#CksLRFaESq2ACt;m8k$q!q6YXwn6YWyMc+gtXsY-W5bZtbm=f86)NCz!ax1CRF zl(>)?6}1!s@RpjY#fSTmBJ1Ul{2Z2$u2pQi)JBgH4OTZ2lqf1|x|SX(P_EBtB>8i$ zy*;!bHDVcxwKDt$OJVd4Y=ybiBdpNlx3Imi;2em@(4+o@uN3g$6>=3qRuZ#=DOaE? z929guplgk&#&#RR3@RsL7OZUy@7z6GuY-~|f&33`&HtmV#ecMQ@;kPsh=(w{*cuc; z)E)=MUVn!6rDx{>PXzf^(8jgJ|V;r}}ia!SQ_kDEjz=p=zwNIDt;|x8XpOJG& z| zA}iN29m5G9aXP-oNq~zk*OnV)EDm51R~2%d9m&$V^WLQSoz_m+Vv!b=ue}~7Z%Ryh zqNHc4!|P?YZbDla&6VUg`#Uf1yC{!gCDFY&!+<^rdpI6k$boHKy3B&^;d)p21r`V)nZw>GHx-03$*i8 z6dj;IF~|cg(KZ`dGDJCvui!81LqDm%(8En4DN6Pow0&rd1QwUG@$6Lo^^bpMO@|9HkY+E-4ZhRQoT(@;`>Ez;7%s!i4UA6uW z9Z7k~9)7~*8*WLCyg%VHepvL+iys?;$^}Ig^(k)1PV55|I15*&kIfe4uluGxSA`-5 z^Pi2Ik|S#Nr1IPPl%*+bzaGM_#UHJYO0uuA99J!F)T(XWw)HU!gOjn5JxW}kV)oH! zik>;prh6wvdw{LM_nRxJPUrTQ_Hx3X9~xBc@xx1pa)!$iV0~^&QsJzsE9~5sp}Zw; zxKj%Ypb!^cc>3@t0g*ME>vDSrsw-bE>HK%ReI>1b2JwLiOPsd?zJIL0x<=`Ea5Rvg z8wzxvYQp7ZBUNN7+^8Z))RMZ&b|uMQapNr$>XB+kD$-()Z9&cEzE#&BUh!qupd+T9 zT-OEPcP&VW?&{_U3fiI;Kl-Zz@1Hh`gsJ&!FbiFGvL{|b-ydyk$_rCdjvu+OF-^i-ltMk=Ng{-zP~K=F?!w}3CKxTBz2aPsuOEY zW5<8&FmH-|S1xtVf|>E?<=a#`TKZbWJ$(kH_;_h-E$AGp{E!we(S>$6ZVZ7k(q z=S}v?+&iRBwGVj`MSHwp1E;1wo?2rFH%;BBk*+5NZ2{i7#T)K7_<709S33+DpZIsd z()$52wO&tlB?cbgj|gwFka|el=A1Lh)Bli)-K$1(s`%|H#?Q!9u|yrP&ZJXqmMz+9$7Dfuotm*hrNa6- zB=$KZgWpxYA)Y##TQ0`te_a!ko+ks=-#52Pm1daCE1l9-%xZb1y#I9y%rwJ8o)pR?3OH`hZ=TOp)LAy4ShC_8q5 z0o$?C!~ddGG=`O}NBm)wyq32SA6bKH4T@X{3E3BV?_$I0VZj zdaw2k9-#1iOnQEv%$7B3GY(Dh)j|ciZx7v6dq}7_51#J`QtO#G#_J}bbb58F`6xjq zC;^-J!X9i*gd9VT(DqW|jDu5k+Caw7l5BtFE#Kc(d~9LP#Pc(;YbKi96V305Tr=S$ zv~UtyI1ep!%>Kr>gdgral>| zf8Z)?Moh819)gVD)MW|YNdXAi{-NZ!>T8@9HPw80)*{EV$Y}>qJmVc&_}WCcjHs8N zwKGop2dg{%g8*7L|>C$cbjrt`ZrWNE07t)AzRt=z~~D>}bRM;7(Sf_Y?# z8`(T#iJP%Fn#Z+v;)=p~{aCw?EV`kLsT)}`uOF#f8N+lQ*|Qtj^Sl_Jb!1!x1u`y1 z?W#}nS*|;>K-`hJFVNz(oW3rZrPq+dvJzc3<)veoD#G<0;;zTF%DQwN-%1;*pb0Ps=<~zUubn9KUsLwhJ)V$XGa-5Zc^>KIknKAQAfm+13 zk>c}1@bKdGa28_O9~u8M%lgPD?&77qfa3;JszfFql?tVHmG73)odWrmGJ!St_KXyN zi<;@{kx}Vvs-q|_@X`C77d`c95onQ3nxo(}D9Eh%jM}z&)fqgTeca$9DUT-1vthDVW0rrO5 z)@@&#u;$!fNzDbWD`*AKZ&Ulw|d} z`sGx}wxDPUYA;ASuJ1-NaC&e%!Hy#Hs?z9+yTNK*dF~HhI`{rynw5gv zwawBfE7CrX<|3OyAR}N_BHD_wtYEYG9g zk_BV68E)w@B zox~|-%_gP13rwa&X7mg102wKe>(w0~<9k5HPu*k=_&m|Bxq)|e58jnxWfkASN^%d# zM1f4wJ3y}Q0lDs}jDD3U(tD7kPh-t=Wm=}668WC49Mx|@sy#R zMc(rUL_%7;LnUSZU||~&H|X{NQ1%DuK^k1>kWOdVNxP|*mpfyzJ%~d(6a5}D5Bq^d z2;|jOdGTn@B7}#k!h2+1y})lI$MP+j6;pi>Ci{x#ghoOBK z2Bg@~bbA^4KufgDMG^&yj^hgXvOVmR_6s|_NTx21GLv0K|qqwyAgvGeYUd=3g`~N$1r1wX^Vv` zbnTAhe`mZHj$CUF)ARb$&1AS-A^$OCbejaWPb12_tF`>M+-8{B8IG{ey@V!snbM4K zLKBbS2yya+fjtWVh@pUNGwv&|;irD8xC(&)h~4 z2@}u93CGNk$qRu>27KsJNf$D-Un&dPE6O>IJwa5`;0z_%nzGp2rYUAV%J6axg4jF7 ze?_C9wBJ;k&y;^AgLJs(!k9Eg zKH+=MS(LUBkz`0LLrwco#pJ=KW7mDR6lw~X2(P*PpnSF4fY{5ItAIS+ljQLWPBG$n zO{A%`It~c8nFIinkc^M?6s55-6mqA=IHsIR@LZU39nqb=_sVUfMVz|EzL74uF~&NG z#FVMlRD~)@*0hKOON~T4m_Z?wHjE{`ZE*Kd7pQG<*sEqp$(&Y8x{~88|`w zIkK9oQ%eTOi?bu&5exnd5JwQKI3>Y{!Fce^(hG79jv0R402!9sL!LtIq;5`=y6H%2 za+=hnBdO_WQqzv4W~WKbI+B{7CN=L!>h?6L+ZIw8jo14xdbf}C5O%u$__NsM`tt*1 zx3)95wtdB>E0~ZZg3M!cw2E+bTTC0fLY+PU|A+&bNktUHc{QrfWhqKVRly6$`lJa8 zN>#zsgaIOzt-usB2GD~kS!G55mFATy+dht>Zh997uAQJ5S5-E5Z?Cql@NAr;4PWT7 z94eH`v68}(9gvXlL)GLu6Nck5m$}YN3{}?g#!v!5Y9xVm<+>|Bn6}gaiowiCdh+v; zIzXPlk|W4YMbg)ax%A2^qWAG0XQUh67%lk>F-O4m6Acf$@hywoTR%3J2dt9SF_(A~f%bP+xTVQf$#LooWCW zwBmg)WVUH9WZ-U;oMYxo)tdFA)~qkJiVb$DUMJ~vRO}?3?wa-Fv2jl>8~5k4c~6?n zd(v#)n`UkY%^Kz6#Z2}gnmmrg}tSG97iMR_l$^>0R+&EKNB=n<=Mf%gnBDp9j zoJp&EW=&r?pN8!#i=!)I>mOS>PEQ2i@Kr514Z{ex#7$W!f8tT=342gB-a}0`I)Nh3 zu5)qT!e@Ry&Y~j(nV;6 zBhAM{YuK52@q{+`y!QsD;-$&sRTd|G++v3F0{BdUBv?~%>#LX^{6BVX<=M`#K}Xzd zHJQ9}uaPfoFIUDjSlpd{{_U4vKLHCyqna7(kn+<ELqOFot)j2855$(09YSK27nR(-sPoy;z^+@5Y3628w zT&uI?t=oJyXxdtecz^#DJRL&J9Cc)?B}%-LMrd;A==KMW(+A8s+@Q15rtSi42Gr+$ z6Kq9zBwYm=Yu<@)EytaOAT!)~80YgiTa7k^jyi(kjH zWed95eHRVoqMPu}>=8SYTcatV94X8q8}i1NI-t_EiXXjJd2eX+eT~jXQ7ubGhn~M` z%73>>65waiMsb&w-CNUCcz&W9Hw}#~H3#0vYgop2HOvSw_rMWK-bRM`>tXgH?*<&L zL_^4aB8+cQWP_rVnewClients[i] = 255; continue; } somfy.emitState(num); + if(!sockServer.clientIsConnected(num)) { this->newClients[i] = 255; continue; } git.emitUpdateCheck(num); + if(!sockServer.clientIsConnected(num)) { this->newClients[i] = 255; continue; } net.emitSockets(num); esp_task_wdt_reset(); } diff --git a/src/Somfy.cpp b/src/Somfy.cpp index 946905d..30bf84a 100644 --- a/src/Somfy.cpp +++ b/src/Somfy.cpp @@ -3590,6 +3590,7 @@ void SomfyShadeController::emitState(uint8_t num) { for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) { SomfyShade *shade = &this->shades[i]; if(shade->getShadeId() == 255) continue; + esp_task_wdt_reset(); shade->emitState(num); } } diff --git a/src/SomfyController.ino b/src/SomfyController.ino index 9e0afe1..af80702 100644 --- a/src/SomfyController.ino +++ b/src/SomfyController.ino @@ -47,6 +47,18 @@ void setup() { Serial.begin(115200); Serial.println(); Serial.println("Startup/Boot...."); + esp_core_dump_summary_t summary; + if (esp_core_dump_get_summary(&summary) == ESP_OK) { + Serial.println("*** Previous crash coredump found ***"); + Serial.printf(" Task: %s\n", summary.exc_task); + Serial.printf(" PC: 0x%08x\n", summary.exc_pc); + Serial.printf(" Cause: %d\n", summary.ex_info.exc_cause); + Serial.printf(" Backtrace:"); + for (int i = 0; i < summary.exc_bt_info.depth; i++) { + Serial.printf(" 0x%08x", summary.exc_bt_info.bt[i]); + } + Serial.println(); + } Serial.println("Mounting File System..."); diff --git a/src/WResp.cpp b/src/WResp.cpp index 80545a6..bff3476 100644 --- a/src/WResp.cpp +++ b/src/WResp.cpp @@ -68,6 +68,35 @@ void JsonResponse::_safecat(const char *val, bool escape) { if(escape) strcat(this->buff, "\""); } +void AsyncJsonResp::beginResponse(AsyncWebServerRequest *request, char *buff, size_t buffSize) { + this->buff = buff; + this->buffSize = buffSize; + this->buff[0] = 0x00; + this->_nocomma = true; + this->_headersSent = false; + this->_stream = request->beginResponseStream("application/json"); +} +void AsyncJsonResp::endResponse() { + if(strlen(this->buff)) this->flush(); +} +void AsyncJsonResp::flush() { + if(this->_stream && strlen(this->buff) > 0) { + this->_stream->print(this->buff); + this->buff[0] = 0x00; + } +} +void AsyncJsonResp::_safecat(const char *val, bool escape) { + size_t len = (escape ? this->calcEscapedLength(val) : strlen(val)) + strlen(this->buff); + if(escape) len += 2; + if(len >= this->buffSize) { + this->flush(); + } + if(escape) strcat(this->buff, "\""); + if(escape) this->escapeString(val, &this->buff[strlen(this->buff)]); + else strcat(this->buff, val); + if(escape) strcat(this->buff, "\""); +} + void JsonFormatter::beginObject(const char *name) { if(name && strlen(name) > 0) this->appendElem(name); else if(!this->_nocomma) this->_safecat(","); diff --git a/src/WResp.h b/src/WResp.h index 2efa89e..516ae9e 100644 --- a/src/WResp.h +++ b/src/WResp.h @@ -1,6 +1,7 @@ #include #include #include +#include #include "Somfy.h" #ifndef wresp_h #define wresp_h @@ -63,6 +64,15 @@ class JsonResponse : public JsonFormatter { void endResponse(); void send(); }; +class AsyncJsonResp : public JsonFormatter { + protected: + void _safecat(const char *val, bool escape = false) override; + AsyncResponseStream *_stream = nullptr; + public: + void beginResponse(AsyncWebServerRequest *request, char *buff, size_t buffSize); + void endResponse(); + void flush(); +}; class JsonSockEvent : public JsonFormatter { protected: bool _closed = false; diff --git a/src/Web.cpp b/src/Web.cpp index 9f2492d..c831735 100644 --- a/src/Web.cpp +++ b/src/Web.cpp @@ -16,6 +16,7 @@ #include "Network.h" #include #include +#include extern ConfigSettings settings; extern SSDPClass SSDP; @@ -29,6 +30,7 @@ extern Network net; //#define WEB_MAX_RESPONSE 34768 #define WEB_MAX_RESPONSE 4096 static char g_content[WEB_MAX_RESPONSE]; +static char g_async_content[WEB_MAX_RESPONSE]; // General responses @@ -40,9 +42,10 @@ static const char _encoding_text[] = "text/plain"; static const char _encoding_html[] = "text/html"; static const char _encoding_json[] = "application/json"; -WebServer apiServer(8081); +WebServer apiServer(8082); WebServer server(80); AsyncWebServer aserver(81); +AsyncWebServer asyncApiServer(8081); void Web::startup() { Serial.println("Launching web server..."); aserver.serveStatic("/", LittleFS, "/").setDefaultFile("index.html"); @@ -60,6 +63,8 @@ void Web::startup() { request->send(response); }); aserver.begin(); + asyncApiServer.begin(); + Serial.println("Async API server started on port 8082"); } void Web::loop() { server.handleClient(); @@ -1075,6 +1080,741 @@ void Web::handleReboot(WebServer &server) { server.send(201, _encoding_json, "{\"status\":\"ERROR\",\"desc\":\"Invalid HTTP Method: \"}"); } } +// ===================================================== +// Async API Handlers (port 8082) +// ===================================================== +// Helper: get a query param as String, or empty if missing +static String asyncParam(AsyncWebServerRequest *request, const char *name) { + if(request->hasParam(name)) return request->getParam(name)->value(); + return String(); +} +static bool asyncHasParam(AsyncWebServerRequest *request, const char *name) { + return request->hasParam(name); +} + +// -- Serialization helpers (accept JsonFormatter& so both sync and async can use them) -- +static void serializeRoom(SomfyRoom *room, JsonFormatter &json) { + json.addElem("roomId", room->roomId); + json.addElem("name", room->name); + json.addElem("sortOrder", room->sortOrder); +} +static void serializeShadeRef(SomfyShade *shade, JsonFormatter &json) { + json.addElem("shadeId", shade->getShadeId()); + json.addElem("roomId", shade->roomId); + json.addElem("name", shade->name); + json.addElem("remoteAddress", (uint32_t)shade->getRemoteAddress()); + json.addElem("paired", shade->paired); + json.addElem("shadeType", static_cast(shade->shadeType)); + json.addElem("flipCommands", shade->flipCommands); + json.addElem("flipPosition", shade->flipCommands); + json.addElem("bitLength", shade->bitLength); + json.addElem("proto", static_cast(shade->proto)); + json.addElem("flags", shade->flags); + json.addElem("sunSensor", shade->hasSunSensor()); + json.addElem("hasLight", shade->hasLight()); + json.addElem("repeats", shade->repeats); +} +static void serializeShade(SomfyShade *shade, JsonFormatter &json) { + json.addElem("shadeId", shade->getShadeId()); + json.addElem("roomId", shade->roomId); + json.addElem("name", shade->name); + json.addElem("remoteAddress", (uint32_t)shade->getRemoteAddress()); + json.addElem("upTime", (uint32_t)shade->upTime); + json.addElem("downTime", (uint32_t)shade->downTime); + json.addElem("paired", shade->paired); + json.addElem("lastRollingCode", (uint32_t)shade->lastRollingCode); + json.addElem("position", shade->transformPosition(shade->currentPos)); + json.addElem("tiltType", static_cast(shade->tiltType)); + json.addElem("tiltPosition", shade->transformPosition(shade->currentTiltPos)); + json.addElem("tiltDirection", shade->tiltDirection); + json.addElem("tiltTime", (uint32_t)shade->tiltTime); + json.addElem("stepSize", (uint32_t)shade->stepSize); + json.addElem("tiltTarget", shade->transformPosition(shade->tiltTarget)); + json.addElem("target", shade->transformPosition(shade->target)); + json.addElem("myPos", shade->transformPosition(shade->myPos)); + json.addElem("myTiltPos", shade->transformPosition(shade->myTiltPos)); + json.addElem("direction", shade->direction); + json.addElem("shadeType", static_cast(shade->shadeType)); + json.addElem("bitLength", shade->bitLength); + json.addElem("proto", static_cast(shade->proto)); + json.addElem("flags", shade->flags); + json.addElem("flipCommands", shade->flipCommands); + json.addElem("flipPosition", shade->flipPosition); + json.addElem("inGroup", shade->isInGroup()); + json.addElem("sunSensor", shade->hasSunSensor()); + json.addElem("light", shade->hasLight()); + json.addElem("repeats", shade->repeats); + json.addElem("sortOrder", shade->sortOrder); + json.addElem("gpioUp", shade->gpioUp); + json.addElem("gpioDown", shade->gpioDown); + json.addElem("gpioMy", shade->gpioMy); + json.addElem("gpioLLTrigger", ((shade->gpioFlags & (uint8_t)gpio_flags_t::LowLevelTrigger) == 0) ? false : true); + json.addElem("simMy", shade->simMy()); + json.beginArray("linkedRemotes"); + for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) { + SomfyLinkedRemote &lremote = shade->linkedRemotes[i]; + if(lremote.getRemoteAddress() != 0) { + json.beginObject(); + json.addElem("remoteAddress", (uint32_t)lremote.getRemoteAddress()); + json.addElem("lastRollingCode", (uint32_t)lremote.lastRollingCode); + json.endObject(); + } + } + json.endArray(); +} +static void serializeGroupRef(SomfyGroup *group, JsonFormatter &json) { + group->updateFlags(); + json.addElem("groupId", group->getGroupId()); + json.addElem("roomId", group->roomId); + json.addElem("name", group->name); + json.addElem("remoteAddress", (uint32_t)group->getRemoteAddress()); + json.addElem("lastRollingCode", (uint32_t)group->lastRollingCode); + json.addElem("bitLength", group->bitLength); + json.addElem("proto", static_cast(group->proto)); + json.addElem("sunSensor", group->hasSunSensor()); + json.addElem("flipCommands", group->flipCommands); + json.addElem("flags", group->flags); + json.addElem("repeats", group->repeats); + json.addElem("sortOrder", group->sortOrder); +} +static void serializeGroup(SomfyGroup *group, JsonFormatter &json) { + serializeGroupRef(group, json); + json.beginArray("linkedShades"); + for(uint8_t i = 0; i < SOMFY_MAX_GROUPED_SHADES; i++) { + uint8_t shadeId = group->linkedShades[i]; + if(shadeId > 0 && shadeId < 255) { + SomfyShade *shade = somfy.getShadeById(shadeId); + if(shade) { + json.beginObject(); + serializeShadeRef(shade, json); + json.endObject(); + } + } + } + json.endArray(); +} +static void serializeRooms(JsonFormatter &json) { + for(uint8_t i = 0; i < SOMFY_MAX_ROOMS; i++) { + SomfyRoom *room = &somfy.rooms[i]; + if(room->roomId != 0) { + json.beginObject(); + serializeRoom(room, json); + json.endObject(); + } + } +} +static void serializeShades(JsonFormatter &json) { + for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) { + SomfyShade &shade = somfy.shades[i]; + if(shade.getShadeId() != 255) { + json.beginObject(); + serializeShade(&shade, json); + json.endObject(); + } + } +} +static void serializeGroups(JsonFormatter &json) { + for(uint8_t i = 0; i < SOMFY_MAX_GROUPS; i++) { + SomfyGroup &group = somfy.groups[i]; + if(group.getGroupId() != 255) { + json.beginObject(); + serializeGroup(&group, json); + json.endObject(); + } + } +} +static void serializeRepeaters(JsonFormatter &json) { + for(uint8_t i = 0; i < SOMFY_MAX_REPEATERS; i++) { + if(somfy.repeaters[i] != 0) json.addElem((uint32_t)somfy.repeaters[i]); + } +} +static void serializeTransceiverConfig(JsonFormatter &json) { + auto &cfg = somfy.transceiver.config; + json.addElem("type", cfg.type); + json.addElem("TXPin", cfg.TXPin); + json.addElem("RXPin", cfg.RXPin); + json.addElem("SCKPin", cfg.SCKPin); + json.addElem("MOSIPin", cfg.MOSIPin); + json.addElem("MISOPin", cfg.MISOPin); + json.addElem("CSNPin", cfg.CSNPin); + json.addElem("rxBandwidth", cfg.rxBandwidth); + json.addElem("frequency", cfg.frequency); + json.addElem("deviation", cfg.deviation); + json.addElem("txPower", cfg.txPower); + json.addElem("proto", static_cast(cfg.proto)); + json.addElem("enabled", cfg.enabled); + json.addElem("radioInit", cfg.radioInit); +} +static void serializeAppVersion(JsonFormatter &json, appver_t &ver) { + json.addElem("name", ver.name); + json.addElem("major", ver.major); + json.addElem("minor", ver.minor); + json.addElem("build", ver.build); + json.addElem("suffix", ver.suffix); +} +static void serializeGitVersion(JsonFormatter &json) { + json.addElem("available", git.updateAvailable); + json.addElem("status", git.status); + json.addElem("error", (int32_t)git.error); + json.addElem("cancelled", git.cancelled); + json.addElem("checkForUpdate", settings.checkForUpdate); + json.addElem("inetAvailable", git.inetAvailable); + json.beginObject("fwVersion"); + serializeAppVersion(json, settings.fwVersion); + json.endObject(); + json.beginObject("appVersion"); + serializeAppVersion(json, settings.appVersion); + json.endObject(); + json.beginObject("latest"); + serializeAppVersion(json, git.latest); + json.endObject(); +} +static void serializeGitRelease(GitRelease *rel, JsonFormatter &json) { + Timestamp ts; + char buff[20]; + sprintf(buff, "%llu", rel->id); + json.addElem("id", buff); + json.addElem("name", rel->name); + json.addElem("date", ts.getISOTime(rel->releaseDate)); + json.addElem("draft", rel->draft); + json.addElem("preRelease", rel->preRelease); + json.addElem("main", rel->main); + json.addElem("hasFS", rel->hasFS); + json.addElem("hwVersions", rel->hwVersions); + json.beginObject("version"); + serializeAppVersion(json, rel->version); + json.endObject(); +} + +// -- Async handler implementations -- +void Web::handleDiscovery(AsyncWebServerRequest *request) { + if(request->method() == HTTP_POST || request->method() == HTTP_GET) { + Serial.println("Async Discovery Requested"); + char connType[10] = "Unknown"; + if(net.connType == conn_types_t::ethernet) strcpy(connType, "Ethernet"); + else if(net.connType == conn_types_t::wifi) strcpy(connType, "Wifi"); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginObject(); + resp.addElem("serverId", settings.serverId); + resp.addElem("version", settings.fwVersion.name); + resp.addElem("latest", git.latest.name); + resp.addElem("model", "ESPSomfyRTS"); + resp.addElem("hostname", settings.hostname); + resp.addElem("authType", static_cast(settings.Security.type)); + resp.addElem("permissions", settings.Security.permissions); + resp.addElem("chipModel", settings.chipModel); + resp.addElem("connType", connType); + resp.addElem("checkForUpdate", settings.checkForUpdate); + resp.beginObject("memory"); + resp.addElem("max", ESP.getMaxAllocHeap()); + resp.addElem("free", ESP.getFreeHeap()); + resp.addElem("min", ESP.getMinFreeHeap()); + resp.addElem("total", ESP.getHeapSize()); + resp.addElem("uptime", (uint64_t)millis()); + resp.endObject(); + resp.beginArray("rooms"); + serializeRooms(resp); + resp.endArray(); + resp.beginArray("shades"); + serializeShades(resp); + resp.endArray(); + resp.beginArray("groups"); + serializeGroups(resp); + resp.endArray(); + resp.endObject(); + resp.endResponse(); + net.needsBroadcast = true; + } + else + request->send(500, _encoding_text, "Invalid http method"); +} +void Web::handleGetRooms(AsyncWebServerRequest *request) { + if(request->method() == HTTP_POST || request->method() == HTTP_GET) { + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginArray(); + serializeRooms(resp); + resp.endArray(); + resp.endResponse(); + } + else request->send(404, _encoding_text, _response_404); +} +void Web::handleGetShades(AsyncWebServerRequest *request) { + if(request->method() == HTTP_POST || request->method() == HTTP_GET) { + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginArray(); + serializeShades(resp); + resp.endArray(); + resp.endResponse(); + } + else request->send(404, _encoding_text, _response_404); +} +void Web::handleGetGroups(AsyncWebServerRequest *request) { + if(request->method() == HTTP_POST || request->method() == HTTP_GET) { + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginArray(); + serializeGroups(resp); + resp.endArray(); + resp.endResponse(); + } + else request->send(404, _encoding_text, _response_404); +} +void Web::handleController(AsyncWebServerRequest *request) { + if(request->method() == HTTP_POST || request->method() == HTTP_GET) { + settings.printAvailHeap(); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginObject(); + resp.addElem("maxRooms", (uint8_t)SOMFY_MAX_ROOMS); + resp.addElem("maxShades", (uint8_t)SOMFY_MAX_SHADES); + resp.addElem("maxGroups", (uint8_t)SOMFY_MAX_GROUPS); + resp.addElem("maxGroupedShades", (uint8_t)SOMFY_MAX_GROUPED_SHADES); + resp.addElem("maxLinkedRemotes", (uint8_t)SOMFY_MAX_LINKED_REMOTES); + resp.addElem("startingAddress", (uint32_t)somfy.startingAddress); + resp.beginObject("transceiver"); + resp.beginObject("config"); + serializeTransceiverConfig(resp); + resp.endObject(); + resp.endObject(); + resp.beginObject("version"); + serializeGitVersion(resp); + resp.endObject(); + resp.beginArray("rooms"); + serializeRooms(resp); + resp.endArray(); + resp.beginArray("shades"); + serializeShades(resp); + resp.endArray(); + resp.beginArray("groups"); + serializeGroups(resp); + resp.endArray(); + resp.beginArray("repeaters"); + serializeRepeaters(resp); + resp.endArray(); + resp.endObject(); + resp.endResponse(); + } + else request->send(404, _encoding_text, _response_404); +} +void Web::handleLogin(AsyncWebServerRequest *request, JsonVariant &json) { + if(request->method() == HTTP_OPTIONS) { request->send(200); return; } + char token[65]; + memset(&token, 0x00, sizeof(token)); + this->createAPIToken(request->client()->remoteIP(), token); + if(settings.Security.type == security_types::None) { + snprintf(g_async_content, sizeof(g_async_content), + "{\"type\":%u,\"apiKey\":\"%s\",\"msg\":\"Success\",\"success\":true}", + static_cast(settings.Security.type), token); + request->send(200, _encoding_json, g_async_content); + return; + } + char username[33] = ""; + char password[33] = ""; + char pin[5] = ""; + // Try query params first + if(asyncHasParam(request, "username")) strlcpy(username, asyncParam(request, "username").c_str(), sizeof(username)); + if(asyncHasParam(request, "password")) strlcpy(password, asyncParam(request, "password").c_str(), sizeof(password)); + if(asyncHasParam(request, "pin")) strlcpy(pin, asyncParam(request, "pin").c_str(), sizeof(pin)); + // Override from JSON body if present + if(!json.isNull()) { + JsonObject obj = json.as(); + if(obj.containsKey("username")) strlcpy(username, obj["username"], sizeof(username)); + if(obj.containsKey("password")) strlcpy(password, obj["password"], sizeof(password)); + if(obj.containsKey("pin")) strlcpy(pin, obj["pin"], sizeof(pin)); + } + bool success = false; + if(settings.Security.type == security_types::PinEntry) { + char ptok[65]; + memset(ptok, 0x00, sizeof(ptok)); + this->createAPIPinToken(request->client()->remoteIP(), pin, ptok); + if(String(ptok) == String(token)) success = true; + } + else if(settings.Security.type == security_types::Password) { + char ptok[65]; + memset(ptok, 0x00, sizeof(ptok)); + this->createAPIPasswordToken(request->client()->remoteIP(), username, password, ptok); + if(String(ptok) == String(token)) success = true; + } + if(success) { + snprintf(g_async_content, sizeof(g_async_content), + "{\"type\":%u,\"apiKey\":\"%s\",\"msg\":\"Success\",\"success\":true}", + static_cast(settings.Security.type), token); + request->send(200, _encoding_json, g_async_content); + } + else { + snprintf(g_async_content, sizeof(g_async_content), + "{\"type\":%u,\"msg\":\"Invalid credentials\",\"success\":false}", + static_cast(settings.Security.type)); + request->send(401, _encoding_json, g_async_content); + } +} +void Web::handleShadeCommand(AsyncWebServerRequest *request, JsonVariant &json) { + if(request->method() == HTTP_OPTIONS) { request->send(200); return; } + uint8_t shadeId = 255; + uint8_t target = 255; + uint8_t stepSize = 0; + int8_t repeat = -1; + somfy_commands command = somfy_commands::My; + // Try query params + if(asyncHasParam(request, "shadeId")) { + shadeId = asyncParam(request, "shadeId").toInt(); + if(asyncHasParam(request, "command")) command = translateSomfyCommand(asyncParam(request, "command")); + else if(asyncHasParam(request, "target")) target = asyncParam(request, "target").toInt(); + if(asyncHasParam(request, "repeat")) repeat = asyncParam(request, "repeat").toInt(); + if(asyncHasParam(request, "stepSize")) stepSize = asyncParam(request, "stepSize").toInt(); + } + else if(!json.isNull()) { + JsonObject obj = json.as(); + if(obj.containsKey("shadeId")) shadeId = obj["shadeId"]; + else { request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade id was supplied.\"}")); return; } + if(obj.containsKey("command")) { String scmd = obj["command"]; command = translateSomfyCommand(scmd); } + else if(obj.containsKey("target")) target = obj["target"].as(); + if(obj.containsKey("repeat")) repeat = obj["repeat"].as(); + if(obj.containsKey("stepSize")) stepSize = obj["stepSize"].as(); + } + else { request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade object supplied.\"}")); return; } + SomfyShade *shade = somfy.getShadeById(shadeId); + if(shade) { + if(target <= 100) shade->moveToTarget(shade->transformPosition(target)); + else shade->sendCommand(command, repeat > 0 ? repeat : shade->repeats, stepSize); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginObject(); + serializeShadeRef(shade, resp); + resp.endObject(); + resp.endResponse(); + } + else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade with the specified id not found.\"}")); +} +void Web::handleGroupCommand(AsyncWebServerRequest *request, JsonVariant &json) { + if(request->method() == HTTP_OPTIONS) { request->send(200); return; } + uint8_t groupId = 255; + uint8_t stepSize = 0; + int8_t repeat = -1; + somfy_commands command = somfy_commands::My; + if(asyncHasParam(request, "groupId")) { + groupId = asyncParam(request, "groupId").toInt(); + if(asyncHasParam(request, "command")) command = translateSomfyCommand(asyncParam(request, "command")); + if(asyncHasParam(request, "repeat")) repeat = asyncParam(request, "repeat").toInt(); + if(asyncHasParam(request, "stepSize")) stepSize = asyncParam(request, "stepSize").toInt(); + } + else if(!json.isNull()) { + JsonObject obj = json.as(); + if(obj.containsKey("groupId")) groupId = obj["groupId"]; + else { request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No group id was supplied.\"}")); return; } + if(obj.containsKey("command")) { String scmd = obj["command"]; command = translateSomfyCommand(scmd); } + if(obj.containsKey("repeat")) repeat = obj["repeat"].as(); + if(obj.containsKey("stepSize")) stepSize = obj["stepSize"].as(); + } + else { request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No group object supplied.\"}")); return; } + SomfyGroup *group = somfy.getGroupById(groupId); + if(group) { + group->sendCommand(command, repeat >= 0 ? repeat : group->repeats, stepSize); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginObject(); + serializeGroupRef(group, resp); + resp.endObject(); + resp.endResponse(); + } + else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Group with the specified id not found.\"}")); +} +void Web::handleTiltCommand(AsyncWebServerRequest *request, JsonVariant &json) { + if(request->method() == HTTP_OPTIONS) { request->send(200); return; } + uint8_t shadeId = 255; + uint8_t target = 255; + somfy_commands command = somfy_commands::My; + if(asyncHasParam(request, "shadeId")) { + shadeId = asyncParam(request, "shadeId").toInt(); + if(asyncHasParam(request, "command")) command = translateSomfyCommand(asyncParam(request, "command")); + else if(asyncHasParam(request, "target")) target = asyncParam(request, "target").toInt(); + } + else if(!json.isNull()) { + JsonObject obj = json.as(); + if(obj.containsKey("shadeId")) shadeId = obj["shadeId"]; + else { request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade id was supplied.\"}")); return; } + if(obj.containsKey("command")) { String scmd = obj["command"]; command = translateSomfyCommand(scmd); } + else if(obj.containsKey("target")) target = obj["target"].as(); + } + else { request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade object supplied.\"}")); return; } + SomfyShade *shade = somfy.getShadeById(shadeId); + if(shade) { + if(target <= 100) shade->moveToTiltTarget(shade->transformPosition(target)); + else shade->sendTiltCommand(command); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginObject(); + serializeShadeRef(shade, resp); + resp.endObject(); + resp.endResponse(); + } + else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade with the specified id not found.\"}")); +} +void Web::handleRepeatCommand(AsyncWebServerRequest *request, JsonVariant &json) { + if(request->method() == HTTP_OPTIONS) { request->send(200); return; } + uint8_t shadeId = 255; + uint8_t groupId = 255; + uint8_t stepSize = 0; + int8_t repeat = -1; + somfy_commands command = somfy_commands::My; + if(asyncHasParam(request, "shadeId")) shadeId = asyncParam(request, "shadeId").toInt(); + else if(asyncHasParam(request, "groupId")) groupId = asyncParam(request, "groupId").toInt(); + if(asyncHasParam(request, "command")) command = translateSomfyCommand(asyncParam(request, "command")); + if(asyncHasParam(request, "repeat")) repeat = asyncParam(request, "repeat").toInt(); + if(asyncHasParam(request, "stepSize")) stepSize = asyncParam(request, "stepSize").toInt(); + if(shadeId == 255 && groupId == 255 && !json.isNull()) { + JsonObject obj = json.as(); + if(obj.containsKey("shadeId")) shadeId = obj["shadeId"]; + if(obj.containsKey("groupId")) groupId = obj["groupId"]; + if(obj.containsKey("stepSize")) stepSize = obj["stepSize"]; + if(obj.containsKey("command")) { String scmd = obj["command"]; command = translateSomfyCommand(scmd); } + if(obj.containsKey("repeat")) repeat = obj["repeat"].as(); + } + if(shadeId != 255) { + SomfyShade *shade = somfy.getShadeById(shadeId); + if(!shade) { request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade reference could not be found.\"}")); return; } + if(shade->shadeType == shade_types::garage1 && command == somfy_commands::Prog) command = somfy_commands::Toggle; + if(!shade->isLastCommand(command)) shade->sendCommand(command, repeat >= 0 ? repeat : shade->repeats, stepSize); + else shade->repeatFrame(repeat >= 0 ? repeat : shade->repeats); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginArray(); + serializeShadeRef(shade, resp); + resp.endArray(); + resp.endResponse(); + } + else if(groupId != 255) { + SomfyGroup *group = somfy.getGroupById(groupId); + if(!group) { request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Group reference could not be found.\"}")); return; } + if(!group->isLastCommand(command)) group->sendCommand(command, repeat >= 0 ? repeat : group->repeats, stepSize); + else group->repeatFrame(repeat >= 0 ? repeat : group->repeats); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginObject(); + serializeGroupRef(group, resp); + resp.endObject(); + resp.endResponse(); + } + else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid Http method\"}")); +} +void Web::handleRoom(AsyncWebServerRequest *request) { + if(request->method() == HTTP_OPTIONS) { request->send(200); return; } + if(request->method() == HTTP_GET) { + if(asyncHasParam(request, "roomId")) { + int roomId = asyncParam(request, "roomId").toInt(); + SomfyRoom *room = somfy.getRoomById(roomId); + if(room) { + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginObject(); + serializeRoom(room, resp); + resp.endObject(); + resp.endResponse(); + } + else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Room Id not found.\"}")); + } + else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"You must supply a valid room id.\"}")); + } + else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid Http method\"}")); +} +void Web::handleShade(AsyncWebServerRequest *request) { + if(request->method() == HTTP_OPTIONS) { request->send(200); return; } + if(request->method() == HTTP_GET) { + if(asyncHasParam(request, "shadeId")) { + int shadeId = asyncParam(request, "shadeId").toInt(); + SomfyShade *shade = somfy.getShadeById(shadeId); + if(shade) { + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginObject(); + serializeShade(shade, resp); + resp.endObject(); + resp.endResponse(); + } + else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade Id not found.\"}")); + } + else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"You must supply a valid shade id.\"}")); + } + else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid Http method\"}")); +} +void Web::handleGroup(AsyncWebServerRequest *request) { + if(request->method() == HTTP_OPTIONS) { request->send(200); return; } + if(request->method() == HTTP_GET) { + if(asyncHasParam(request, "groupId")) { + int groupId = asyncParam(request, "groupId").toInt(); + SomfyGroup *group = somfy.getGroupById(groupId); + if(group) { + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginObject(); + serializeGroup(group, resp); + resp.endObject(); + resp.endResponse(); + } + else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Group Id not found.\"}")); + } + else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"You must supply a valid group id.\"}")); + } + else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid Http method\"}")); +} +void Web::handleSetPositions(AsyncWebServerRequest *request, JsonVariant &json) { + if(request->method() == HTTP_OPTIONS) { request->send(200); return; } + uint8_t shadeId = asyncHasParam(request, "shadeId") ? asyncParam(request, "shadeId").toInt() : 255; + int8_t pos = asyncHasParam(request, "position") ? asyncParam(request, "position").toInt() : -1; + int8_t tiltPos = asyncHasParam(request, "tiltPosition") ? asyncParam(request, "tiltPosition").toInt() : -1; + if(!json.isNull()) { + JsonObject obj = json.as(); + if(obj.containsKey("shadeId")) shadeId = obj["shadeId"]; + if(obj.containsKey("position")) pos = obj["position"]; + if(obj.containsKey("tiltPosition")) tiltPos = obj["tiltPosition"]; + } + if(shadeId != 255) { + SomfyShade *shade = somfy.getShadeById(shadeId); + if(shade) { + if(pos >= 0) shade->target = shade->currentPos = pos; + if(tiltPos >= 0 && shade->tiltType != tilt_types::none) shade->tiltTarget = shade->currentTiltPos = tiltPos; + shade->emitState(); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginObject(); + serializeShade(shade, resp); + resp.endObject(); + resp.endResponse(); + } + else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"An invalid shadeId was provided\"}")); + } + else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"shadeId was not provided\"}")); +} +void Web::handleSetSensor(AsyncWebServerRequest *request, JsonVariant &json) { + if(request->method() == HTTP_OPTIONS) { request->send(200); return; } + uint8_t shadeId = asyncHasParam(request, "shadeId") ? asyncParam(request, "shadeId").toInt() : 255; + uint8_t groupId = asyncHasParam(request, "groupId") ? asyncParam(request, "groupId").toInt() : 255; + int8_t sunny = asyncHasParam(request, "sunny") ? (toBoolean(asyncParam(request, "sunny").c_str(), false) ? 1 : 0) : -1; + int8_t windy = asyncHasParam(request, "windy") ? asyncParam(request, "windy").toInt() : -1; + int8_t repeat = asyncHasParam(request, "repeat") ? asyncParam(request, "repeat").toInt() : -1; + if(!json.isNull()) { + JsonObject obj = json.as(); + if(obj.containsKey("shadeId")) shadeId = obj["shadeId"].as(); + if(obj.containsKey("groupId")) groupId = obj["groupId"].as(); + if(obj.containsKey("sunny")) { + if(obj["sunny"].is()) sunny = obj["sunny"].as() ? 1 : 0; + else sunny = obj["sunny"].as(); + } + if(obj.containsKey("windy")) { + if(obj["windy"].is()) windy = obj["windy"].as() ? 1 : 0; + else windy = obj["windy"].as(); + } + if(obj.containsKey("repeat")) repeat = obj["repeat"].as(); + } + if(shadeId != 255) { + SomfyShade *shade = somfy.getShadeById(shadeId); + if(shade) { + shade->sendSensorCommand(windy, sunny, repeat >= 0 ? (uint8_t)repeat : shade->repeats); + shade->emitState(); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginObject(); + serializeShade(shade, resp); + resp.endObject(); + resp.endResponse(); + } + else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"An invalid shadeId was provided\"}")); + } + else if(groupId != 255) { + SomfyGroup *group = somfy.getGroupById(groupId); + if(group) { + group->sendSensorCommand(windy, sunny, repeat >= 0 ? (uint8_t)repeat : group->repeats); + group->emitState(); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginObject(); + serializeGroup(group, resp); + resp.endObject(); + resp.endResponse(); + } + else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"An invalid groupId was provided\"}")); + } + else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"shadeId was not provided\"}")); +} +void Web::handleDownloadFirmware(AsyncWebServerRequest *request) { + if(request->method() == HTTP_OPTIONS) { request->send(200); return; } + GitRepo repo; + GitRelease *rel = nullptr; + int8_t err = repo.getReleases(); + Serial.println("Async downloadFirmware called..."); + if(err == 0) { + if(asyncHasParam(request, "ver")) { + String ver = asyncParam(request, "ver"); + if(ver == "latest") rel = &repo.releases[0]; + else if(ver == "main") rel = &repo.releases[GIT_MAX_RELEASES]; + else { + for(uint8_t i = 0; i < GIT_MAX_RELEASES; i++) { + if(repo.releases[i].id == 0) continue; + if(strcmp(repo.releases[i].name, ver.c_str()) == 0) { rel = &repo.releases[i]; break; } + } + } + if(rel) { + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginObject(); + serializeGitRelease(rel, resp); + resp.endObject(); + resp.endResponse(); + strcpy(git.targetRelease, rel->name); + git.status = GIT_AWAITING_UPDATE; + } + else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Release not found in repo.\"}")); + } + else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Release version not supplied.\"}")); + } + else request->send(err, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Error communicating with Github.\"}")); +} +void Web::handleBackup(AsyncWebServerRequest *request) { + bool attach = false; + if(asyncHasParam(request, "attach")) attach = toBoolean(asyncParam(request, "attach").c_str(), false); + Serial.println("Async saving current shade information"); + somfy.writeBackup(); + if(somfy.backupData.length() == 0) { + request->send(500, _encoding_text, "backup failed"); + return; + } + if(attach) { + char filename[120]; + Timestamp ts; + char *iso = ts.getISOTime(); + for(uint8_t i = 0; i < strlen(iso); i++) { + if(iso[i] == '.') { iso[i] = '\0'; break; } + if(iso[i] == ':') iso[i] = '_'; + } + snprintf(filename, sizeof(filename), "attachment; filename=\"ESPSomfyRTS %s.backup\"", iso); + AsyncWebServerResponse *response = request->beginResponse(200, _encoding_text, somfy.backupData); + response->addHeader("Content-Disposition", filename); + response->addHeader("Access-Control-Expose-Headers", "Content-Disposition"); + request->send(response); + } + else { + request->send(200, _encoding_text, somfy.backupData); + } +} +void Web::handleReboot(AsyncWebServerRequest *request) { + if(request->method() == HTTP_OPTIONS) { request->send(200); return; } + if(request->method() == HTTP_POST || request->method() == HTTP_PUT) { + Serial.println("Async Rebooting ESP..."); + rebootDelay.reboot = true; + rebootDelay.rebootTime = millis() + 500; + request->send(200, _encoding_json, "{\"status\":\"OK\",\"desc\":\"Successfully started reboot\"}"); + } + else request->send(201, _encoding_json, "{\"status\":\"ERROR\",\"desc\":\"Invalid HTTP Method\"}"); +} +void Web::handleNotFound(AsyncWebServerRequest *request) { + if(request->method() == HTTP_OPTIONS) { request->send(200); return; } + snprintf(g_async_content, sizeof(g_async_content), "404 Service Not Found: %s", request->url().c_str()); + request->send(404, _encoding_text, g_async_content); +} + void Web::begin() { Serial.println("Creating Web MicroServices..."); server.enableCORS(true); @@ -1102,7 +1842,52 @@ void Web::begin() { apiServer.on("/downloadFirmware", []() { webServer.handleDownloadFirmware(apiServer); }); apiServer.on("/backup", []() { webServer.handleBackup(apiServer); }); apiServer.on("/reboot", []() { webServer.handleReboot(apiServer); }); - + + // Async API Server (port 8082) + DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); + DefaultHeaders::Instance().addHeader("Access-Control-Allow-Methods", "PUT,POST,GET,OPTIONS"); + DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "*"); + // GET endpoints + asyncApiServer.on("/discovery", WebRequestMethodComposite(HTTP_GET) | HTTP_POST, [](AsyncWebServerRequest *r) { webServer.handleDiscovery(r); }); + asyncApiServer.on("/rooms", WebRequestMethodComposite(HTTP_GET) | HTTP_POST, [](AsyncWebServerRequest *r) { webServer.handleGetRooms(r); }); + asyncApiServer.on("/shades", WebRequestMethodComposite(HTTP_GET) | HTTP_POST, [](AsyncWebServerRequest *r) { webServer.handleGetShades(r); }); + asyncApiServer.on("/groups", WebRequestMethodComposite(HTTP_GET) | HTTP_POST, [](AsyncWebServerRequest *r) { webServer.handleGetGroups(r); }); + asyncApiServer.on("/controller", WebRequestMethodComposite(HTTP_GET) | HTTP_POST, [](AsyncWebServerRequest *r) { webServer.handleController(r); }); + asyncApiServer.on("/room", HTTP_GET, [](AsyncWebServerRequest *r) { webServer.handleRoom(r); }); + asyncApiServer.on("/shade", HTTP_GET, [](AsyncWebServerRequest *r) { webServer.handleShade(r); }); + asyncApiServer.on("/group", HTTP_GET, [](AsyncWebServerRequest *r) { webServer.handleGroup(r); }); + asyncApiServer.on("/downloadFirmware", WebRequestMethodComposite(HTTP_GET) | HTTP_POST, [](AsyncWebServerRequest *r) { webServer.handleDownloadFirmware(r); }); + asyncApiServer.on("/backup", WebRequestMethodComposite(HTTP_GET) | HTTP_POST, [](AsyncWebServerRequest *r) { webServer.handleBackup(r); }); + asyncApiServer.on("/reboot", WebRequestMethodComposite(HTTP_POST) | HTTP_PUT, [](AsyncWebServerRequest *r) { webServer.handleReboot(r); }); + // JSON body endpoints + asyncApiServer.addHandler(new AsyncCallbackJsonWebHandler("/shadeCommand", + [](AsyncWebServerRequest *r, JsonVariant &j) { webServer.handleShadeCommand(r, j); })); + asyncApiServer.addHandler(new AsyncCallbackJsonWebHandler("/groupCommand", + [](AsyncWebServerRequest *r, JsonVariant &j) { webServer.handleGroupCommand(r, j); })); + asyncApiServer.addHandler(new AsyncCallbackJsonWebHandler("/tiltCommand", + [](AsyncWebServerRequest *r, JsonVariant &j) { webServer.handleTiltCommand(r, j); })); + asyncApiServer.addHandler(new AsyncCallbackJsonWebHandler("/repeatCommand", + [](AsyncWebServerRequest *r, JsonVariant &j) { webServer.handleRepeatCommand(r, j); })); + asyncApiServer.addHandler(new AsyncCallbackJsonWebHandler("/setPositions", + [](AsyncWebServerRequest *r, JsonVariant &j) { webServer.handleSetPositions(r, j); })); + asyncApiServer.addHandler(new AsyncCallbackJsonWebHandler("/setSensor", + [](AsyncWebServerRequest *r, JsonVariant &j) { webServer.handleSetSensor(r, j); })); + asyncApiServer.addHandler(new AsyncCallbackJsonWebHandler("/login", + [](AsyncWebServerRequest *r, JsonVariant &j) { webServer.handleLogin(r, j); })); + // GET fallback for command endpoints (query params) + asyncApiServer.on("/shadeCommand", HTTP_GET, [](AsyncWebServerRequest *r) { JsonVariant v; webServer.handleShadeCommand(r, v); }); + asyncApiServer.on("/groupCommand", HTTP_GET, [](AsyncWebServerRequest *r) { JsonVariant v; webServer.handleGroupCommand(r, v); }); + asyncApiServer.on("/tiltCommand", HTTP_GET, [](AsyncWebServerRequest *r) { JsonVariant v; webServer.handleTiltCommand(r, v); }); + asyncApiServer.on("/repeatCommand", HTTP_GET, [](AsyncWebServerRequest *r) { JsonVariant v; webServer.handleRepeatCommand(r, v); }); + asyncApiServer.on("/setPositions", HTTP_GET, [](AsyncWebServerRequest *r) { JsonVariant v; webServer.handleSetPositions(r, v); }); + asyncApiServer.on("/setSensor", HTTP_GET, [](AsyncWebServerRequest *r) { JsonVariant v; webServer.handleSetSensor(r, v); }); + asyncApiServer.on("/login", HTTP_GET, [](AsyncWebServerRequest *r) { JsonVariant v; webServer.handleLogin(r, v); }); + // OPTIONS preflight + not found + asyncApiServer.onNotFound([](AsyncWebServerRequest *r) { + if(r->method() == HTTP_OPTIONS) { r->send(200); return; } + webServer.handleNotFound(r); + }); + // Web Interface server.on("/tiltCommand", []() { webServer.handleTiltCommand(server); }); server.on("/repeatCommand", []() { webServer.handleRepeatCommand(server); }); diff --git a/src/Web.h b/src/Web.h index 3339295..649a088 100644 --- a/src/Web.h +++ b/src/Web.h @@ -1,4 +1,6 @@ #include +#include +#include #include "Somfy.h" #ifndef webserver_h #define webserver_h @@ -42,9 +44,25 @@ class Web { bool createAPIPasswordToken(const IPAddress ipAddress, const char *username, const char *password, char *token); bool isAuthenticated(WebServer &server, bool cfg = false); - //void chunkRoomsResponse(WebServer &server, const char *elem = nullptr); - //void chunkShadesResponse(WebServer &server, const char *elem = nullptr); - //void chunkGroupsResponse(WebServer &server, const char *elem = nullptr); - //void chunkGroupResponse(WebServer &server, SomfyGroup *, const char *prefix = nullptr); + // Async API handler overloads (port 8082) + void handleDiscovery(AsyncWebServerRequest *request); + void handleGetRooms(AsyncWebServerRequest *request); + void handleGetShades(AsyncWebServerRequest *request); + void handleGetGroups(AsyncWebServerRequest *request); + void handleController(AsyncWebServerRequest *request); + void handleRoom(AsyncWebServerRequest *request); + void handleShade(AsyncWebServerRequest *request); + void handleGroup(AsyncWebServerRequest *request); + void handleLogin(AsyncWebServerRequest *request, JsonVariant &json); + void handleShadeCommand(AsyncWebServerRequest *request, JsonVariant &json); + void handleGroupCommand(AsyncWebServerRequest *request, JsonVariant &json); + void handleTiltCommand(AsyncWebServerRequest *request, JsonVariant &json); + void handleRepeatCommand(AsyncWebServerRequest *request, JsonVariant &json); + void handleSetPositions(AsyncWebServerRequest *request, JsonVariant &json); + void handleSetSensor(AsyncWebServerRequest *request, JsonVariant &json); + void handleDownloadFirmware(AsyncWebServerRequest *request); + void handleBackup(AsyncWebServerRequest *request); + void handleReboot(AsyncWebServerRequest *request); + void handleNotFound(AsyncWebServerRequest *request); }; #endif From ed8137e6864018ae4329f34268bd712c2bf1ffdb Mon Sep 17 00:00:00 2001 From: cjkas Date: Mon, 23 Mar 2026 14:49:17 +0100 Subject: [PATCH 10/16] replace sync sockets --- .claude/settings.local.json | 3 +- .gitignore | 1 + .vscode/c_cpp_properties.json | 14 +- platformio.ini | 1 - src/Sockets.cpp | 257 ++++++++++++++++++---------------- src/Sockets.h | 5 +- src/WResp.cpp | 8 +- src/WResp.h | 7 +- 8 files changed, 154 insertions(+), 142 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 6bc1b04..9b45ded 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,7 +1,8 @@ { "permissions": { "allow": [ - "Bash(~/.platformio/penv/Scripts/platformio.exe run:*)" + "Bash(~/.platformio/penv/Scripts/platformio.exe run:*)", + "Bash(grep -n \"emitSockets\" \"c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/src\"/*.cpp)" ] } } diff --git a/.gitignore b/.gitignore index 66186f9..d11457f 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ data/ build/ coredump_report.txt coredump.bin +logs/ \ No newline at end of file diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 218700a..44054fa 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -12,21 +12,20 @@ "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/WebServer/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/AsyncUDP/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Ethernet/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/ESPmDNS/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Update/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/HTTPClient/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/WiFiClientSecure/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Preferences/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/LittleFS/src", "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/ESPAsyncWebServer/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/WiFi/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/FS/src", "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/AsyncTCP/src", "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/PubSubClient/src", "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/SmartRC-CC1101-Driver-Lib", - "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/WebSockets/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/WiFiClientSecure/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/SPI/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Ethernet/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/WiFi/src", "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/ArduinoJson/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/newlib/platform_include", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include", @@ -254,21 +253,20 @@ "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/WebServer/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/AsyncUDP/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Ethernet/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/ESPmDNS/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Update/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/HTTPClient/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/WiFiClientSecure/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Preferences/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/LittleFS/src", "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/ESPAsyncWebServer/src", + "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/WiFi/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/FS/src", "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/AsyncTCP/src", "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/PubSubClient/src", "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/SmartRC-CC1101-Driver-Lib", - "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/WebSockets/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/WiFiClientSecure/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/SPI/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Ethernet/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/WiFi/src", "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/ArduinoJson/src", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/newlib/platform_include", "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include", diff --git a/platformio.ini b/platformio.ini index 7ea5b5b..b237059 100644 --- a/platformio.ini +++ b/platformio.ini @@ -17,7 +17,6 @@ board = esp32dev framework = arduino lib_deps = bblanchon/ArduinoJson@^7.2.2 - links2004/WebSockets@^2.7.3 lsatan/SmartRC-CC1101-Driver-Lib@^2.5.7 knolleary/PubSubClient@^2.8 esp32async/ESPAsyncWebServer @ ^3.10.3 diff --git a/src/Sockets.cpp b/src/Sockets.cpp index 558f982..fb371e1 100644 --- a/src/Sockets.cpp +++ b/src/Sockets.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include "Sockets.h" #include "ConfigSettings.h" @@ -14,33 +13,56 @@ extern SomfyShadeController somfy; extern SocketEmitter sockEmit; extern GitUpdater git; +AsyncWebServer wsServer(8080); +AsyncWebSocket ws("/"); -WebSocketsServer sockServer = WebSocketsServer(8080); +#define MAX_WS_CLIENTS 5 +static uint32_t clientMap[MAX_WS_CLIENTS] = {0,0,0,0,0}; + +static uint8_t mapClientId(uint32_t asyncId) { + for(uint8_t i = 0; i < MAX_WS_CLIENTS; i++) + if(clientMap[i] == asyncId) return i; + return 255; +} +static uint32_t getAsyncId(uint8_t slot) { + if(slot < MAX_WS_CLIENTS) return clientMap[slot]; + return 0; +} +static uint8_t addClient(uint32_t asyncId) { + for(uint8_t i = 0; i < MAX_WS_CLIENTS; i++) { + if(clientMap[i] == 0) { clientMap[i] = asyncId; return i; } + } + return 255; +} +static void removeClient(uint32_t asyncId) { + for(uint8_t i = 0; i < MAX_WS_CLIENTS; i++) + if(clientMap[i] == asyncId) clientMap[i] = 0; +} #define MAX_SOCK_RESPONSE 2048 static char g_response[MAX_SOCK_RESPONSE]; bool room_t::isJoined(uint8_t num) { - for(uint8_t i = 0; i < sizeof(this->clients); i++) { - if(this->clients[i] == num) return true; - } - return false; + for(uint8_t i = 0; i < sizeof(this->clients); i++) { + if(this->clients[i] == num) return true; + } + return false; } bool room_t::join(uint8_t num) { - if(this->isJoined(num)) return true; - for(uint8_t i = 0; i < sizeof(this->clients); i++) { - if(this->clients[i] == 255) { - this->clients[i] = num; - return true; - } + if(this->isJoined(num)) return true; + for(uint8_t i = 0; i < sizeof(this->clients); i++) { + if(this->clients[i] == 255) { + this->clients[i] = num; + return true; + } } - return false; + return false; } -bool room_t::leave(uint8_t num) { - if(!this->isJoined(num)) return false; - for(uint8_t i = 0; i < sizeof(this->clients); i++) { - if(this->clients[i] == num) this->clients[i] = 255; - } +bool room_t::leave(uint8_t num) { + if(!this->isJoined(num)) return false; + for(uint8_t i = 0; i < sizeof(this->clients); i++) { + if(this->clients[i] == num) this->clients[i] = 255; + } return true; } void room_t::clear() { @@ -53,49 +75,44 @@ uint8_t room_t::activeClients() { } return n; } -/********************************************************************* - * ClientSocketEvent class members - ********************************************************************/ -/* -void ClientSocketEvent::prepareMessage(const char *evt, const char *payload) { - if(strlen(payload) + 5 >= sizeof(this->msg)) Serial.printf("Socket buffer overflow %d > 2048\n", strlen(payload) + 5 + strlen(evt)); - snprintf(this->msg, sizeof(this->msg), "42[%s,%s]", evt, payload); -} -void ClientSocketEvent::prepareMessage(const char *evt, JsonDocument &doc) { - memset(this->msg, 0x00, sizeof(this->msg)); - snprintf(this->msg, sizeof(this->msg), "42[%s,", evt); - serializeJson(doc, &this->msg[strlen(this->msg)], sizeof(this->msg) - strlen(this->msg) - 2); - strcat(this->msg, "]"); -} -*/ /********************************************************************* * SocketEmitter class members ********************************************************************/ void SocketEmitter::startup() { - + } void SocketEmitter::begin() { - sockServer.begin(); - sockServer.enableHeartbeat(3000, 2000, 2); - sockServer.onEvent(this->wsEvent); + ws.onEvent(SocketEmitter::wsEvent); + wsServer.addHandler(&ws); + wsServer.begin(); Serial.println("Socket Server Started..."); - //settings.printAvailHeap(); } void SocketEmitter::loop() { + ws.cleanupClients(); this->initClients(); - sockServer.loop(); } JsonSockEvent *SocketEmitter::beginEmit(const char *evt) { - this->json.beginEvent(&sockServer, evt, g_response, sizeof(g_response)); + this->json.beginEvent(&ws, evt, g_response, sizeof(g_response)); return &this->json; } -void SocketEmitter::endEmit(uint8_t num) { this->json.endEvent(num); esp_task_wdt_reset(); sockServer.loop(); } +void SocketEmitter::endEmit(uint8_t num) { + if(num == 255) { + this->json.endEvent(0); + } else { + uint32_t asyncId = getAsyncId(num); + this->json.endEvent(asyncId); + } + esp_task_wdt_reset(); +} void SocketEmitter::endEmitRoom(uint8_t room) { if(room < SOCK_MAX_ROOMS) { room_t *r = &this->rooms[room]; for(uint8_t i = 0; i < sizeof(r->clients); i++) { - if(r->clients[i] != 255) this->json.endEvent(r->clients[i]); + if(r->clients[i] != 255) { + uint32_t asyncId = getAsyncId(r->clients[i]); + if(asyncId != 0) this->json.endEvent(asyncId); + } } } } @@ -105,18 +122,19 @@ uint8_t SocketEmitter::activeClients(uint8_t room) { } void SocketEmitter::initClients() { for(uint8_t i = 0; i < sizeof(this->newClients); i++) { - uint8_t num = this->newClients[i]; - if(num != 255) { - if(sockServer.clientIsConnected(num)) { - Serial.printf("Initializing Socket Client %u\n", num); + uint8_t slot = this->newClients[i]; + if(slot != 255) { + uint32_t asyncId = getAsyncId(slot); + if(asyncId != 0 && ws.hasClient(asyncId)) { + Serial.printf("Initializing Socket Client %u (asyncId=%lu)\n", slot, asyncId); esp_task_wdt_reset(); - settings.emitSockets(num); - if(!sockServer.clientIsConnected(num)) { this->newClients[i] = 255; continue; } - somfy.emitState(num); - if(!sockServer.clientIsConnected(num)) { this->newClients[i] = 255; continue; } - git.emitUpdateCheck(num); - if(!sockServer.clientIsConnected(num)) { this->newClients[i] = 255; continue; } - net.emitSockets(num); + settings.emitSockets(slot); + if(!ws.hasClient(asyncId)) { this->newClients[i] = 255; continue; } + somfy.emitState(slot); + if(!ws.hasClient(asyncId)) { this->newClients[i] = 255; continue; } + git.emitUpdateCheck(slot); + if(!ws.hasClient(asyncId)) { this->newClients[i] = 255; continue; } + net.emitSockets(slot); esp_task_wdt_reset(); } this->newClients[i] = 255; @@ -132,75 +150,72 @@ void SocketEmitter::delayInit(uint8_t num) { } } } -void SocketEmitter::end() { - sockServer.close(); +void SocketEmitter::end() { + ws.closeAll(); + wsServer.end(); for(uint8_t i = 0; i < SOCK_MAX_ROOMS; i++) this->rooms[i].clear(); + memset(clientMap, 0, sizeof(clientMap)); } -void SocketEmitter::disconnect() { sockServer.disconnect(); } -void SocketEmitter::wsEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length) { - switch(type) { - case WStype_ERROR: - if(length > 0) - Serial.printf("Socket Error: %s\n", payload); - else - Serial.println("Socket Error: \n"); - break; - case WStype_DISCONNECTED: - if(length > 0) - Serial.printf("Socket [%u] Disconnected!\n [%s]", num, payload); - else - Serial.printf("Socket [%u] Disconnected!\n", num); - for(uint8_t i = 0; i < SOCK_MAX_ROOMS; i++) { - sockEmit.rooms[i].leave(num); - } - break; - case WStype_CONNECTED: - { - IPAddress ip = sockServer.remoteIP(num); - Serial.printf("Socket [%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload); - // Send all the current shade settings to the client. - sockServer.sendTXT(num, "Connected"); - //sockServer.loop(); - sockEmit.delayInit(num); - } - break; - case WStype_TEXT: - if(strncmp((char *)payload, "join:", 5) == 0) { - // In this instance the client wants to join a room. Let's do some - // work to get the ordinal of the room that the client wants to join. - uint8_t roomNum = atoi((char *)&payload[5]); - Serial.printf("Client %u joining room %u\n", num, roomNum); - if(roomNum < SOCK_MAX_ROOMS) sockEmit.rooms[roomNum].join(num); - } - else if(strncmp((char *)payload, "leave:", 6) == 0) { - uint8_t roomNum = atoi((char *)&payload[6]); - Serial.printf("Client %u leaving room %u\n", num, roomNum); - if(roomNum < SOCK_MAX_ROOMS) sockEmit.rooms[roomNum].leave(num); - } - else { - Serial.printf("Socket [%u] text: %s\n", num, payload); - } - // send message to client - // webSocket.sendTXT(num, "message here"); - - // send data to all connected clients - // sockServer.broadcastTXT("message here"); - break; - case WStype_BIN: - Serial.printf("[%u] get binary length: %u\n", num, length); - //hexdump(payload, length); - - // send message to client - // sockServer.sendBIN(num, payload, length); - break; - case WStype_PONG: - //Serial.printf("Pong from %u\n", num); - break; - case WStype_PING: - //Serial.printf("Ping from %u\n", num); - break; - default: - break; - } +void SocketEmitter::disconnect() { + ws.closeAll(); + memset(clientMap, 0, sizeof(clientMap)); +} +void SocketEmitter::wsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) { + uint32_t asyncId = client->id(); + switch(type) { + case WS_EVT_CONNECT: + { + uint8_t slot = addClient(asyncId); + if(slot == 255) { + Serial.printf("Socket: No free client slots, closing %lu\n", asyncId); + client->close(); + return; + } + IPAddress ip = client->remoteIP(); + Serial.printf("Socket [%lu] Connected from %d.%d.%d.%d (slot %u)\n", asyncId, ip[0], ip[1], ip[2], ip[3], slot); + client->text("Connected"); + client->setCloseClientOnQueueFull(false); + sockEmit.delayInit(slot); + } + break; + case WS_EVT_DISCONNECT: + { + uint8_t slot = mapClientId(asyncId); + Serial.printf("Socket [%lu] Disconnected (slot %u)\n", asyncId, slot); + if(slot != 255) { + for(uint8_t i = 0; i < SOCK_MAX_ROOMS; i++) + sockEmit.rooms[i].leave(slot); + } + removeClient(asyncId); + } + break; + case WS_EVT_DATA: + { + AwsFrameInfo *info = (AwsFrameInfo*)arg; + if(info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) { + uint8_t slot = mapClientId(asyncId); + data[len] = 0; + if(strncmp((char*)data, "join:", 5) == 0) { + uint8_t roomNum = atoi((char*)&data[5]); + Serial.printf("Client %u joining room %u\n", slot, roomNum); + if(roomNum < SOCK_MAX_ROOMS && slot != 255) sockEmit.rooms[roomNum].join(slot); + } + else if(strncmp((char*)data, "leave:", 6) == 0) { + uint8_t roomNum = atoi((char*)&data[6]); + Serial.printf("Client %u leaving room %u\n", slot, roomNum); + if(roomNum < SOCK_MAX_ROOMS && slot != 255) sockEmit.rooms[roomNum].leave(slot); + } + else { + Serial.printf("Socket [%lu] text: %s\n", asyncId, data); + } + } + } + break; + case WS_EVT_ERROR: + Serial.printf("Socket [%lu] Error\n", asyncId); + break; + case WS_EVT_PONG: + break; + } } diff --git a/src/Sockets.h b/src/Sockets.h index d76ab93..948b730 100644 --- a/src/Sockets.h +++ b/src/Sockets.h @@ -1,4 +1,4 @@ -#include +#include #include "WResp.h" #ifndef sockets_h #define sockets_h @@ -21,7 +21,6 @@ class SocketEmitter { void delayInit(uint8_t num); public: JsonSockEvent json; - //ClientSocketEvent evt; room_t rooms[SOCK_MAX_ROOMS]; uint8_t activeClients(uint8_t room); void initClients(); @@ -33,6 +32,6 @@ class SocketEmitter { JsonSockEvent * beginEmit(const char *evt); void endEmit(uint8_t num = 255); void endEmitRoom(uint8_t num); - static void wsEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length); + static void wsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len); }; #endif diff --git a/src/WResp.cpp b/src/WResp.cpp index bff3476..258dfd9 100644 --- a/src/WResp.cpp +++ b/src/WResp.cpp @@ -1,5 +1,5 @@ #include "WResp.h" -void JsonSockEvent::beginEvent(WebSocketsServer *server, const char *evt, char *buff, size_t buffSize) { +void JsonSockEvent::beginEvent(AsyncWebSocket *server, const char *evt, char *buff, size_t buffSize) { this->server = server; this->buff = buff; this->buffSize = buffSize; @@ -15,10 +15,10 @@ void JsonSockEvent::closeEvent() { this->_nocomma = true; this->_closed = true; } -void JsonSockEvent::endEvent(uint8_t num) { +void JsonSockEvent::endEvent(uint32_t clientId) { this->closeEvent(); - if(num == 255) this->server->broadcastTXT(this->buff); - else this->server->sendTXT(num, this->buff); + if(clientId == 0) this->server->textAll(this->buff); + else this->server->text(clientId, this->buff); } void JsonSockEvent::_safecat(const char *val, bool escape) { size_t len = (escape ? this->calcEscapedLength(val) : strlen(val)) + strlen(this->buff); diff --git a/src/WResp.h b/src/WResp.h index 516ae9e..4235ff7 100644 --- a/src/WResp.h +++ b/src/WResp.h @@ -1,5 +1,4 @@ #include -#include #include #include #include "Somfy.h" @@ -78,9 +77,9 @@ class JsonSockEvent : public JsonFormatter { bool _closed = false; void _safecat(const char *val, bool escape = false) override; public: - WebSocketsServer *server = nullptr; - void beginEvent(WebSocketsServer *server, const char *evt, char *buff, size_t buffSize); - void endEvent(uint8_t clientNum = 255); + AsyncWebSocket *server = nullptr; + void beginEvent(AsyncWebSocket *server, const char *evt, char *buff, size_t buffSize); + void endEvent(uint32_t clientId = 0); void closeEvent(); }; #endif From 73f39d67c3f627ec54936f3abcd2618b0cc6d6ca Mon Sep 17 00:00:00 2001 From: cjkas Date: Mon, 23 Mar 2026 15:15:03 +0100 Subject: [PATCH 11/16] refactor --- src/Web.cpp | 48 +++++++++++++++++++++++++++++++++++++++++++----- src/Web.h | 3 ++- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/src/Web.cpp b/src/Web.cpp index c831735..9488d1e 100644 --- a/src/Web.cpp +++ b/src/Web.cpp @@ -44,13 +44,13 @@ static const char _encoding_json[] = "application/json"; WebServer apiServer(8082); WebServer server(80); -AsyncWebServer aserver(81); +AsyncWebServer asyncServer(81); AsyncWebServer asyncApiServer(8081); void Web::startup() { Serial.println("Launching web server..."); - aserver.serveStatic("/", LittleFS, "/").setDefaultFile("index.html"); + asyncServer.serveStatic("/", LittleFS, "/").setDefaultFile("index.html"); - aserver.on("/loginContext", HTTP_GET, [](AsyncWebServerRequest *request) { + asyncServer.on("/loginContext", HTTP_GET, [](AsyncWebServerRequest *request) { AsyncJsonResponse *response = new AsyncJsonResponse(); JsonObject root = response->getRoot().to(); root["type"] = static_cast(settings.Security.type); @@ -62,9 +62,9 @@ void Web::startup() { response->setLength(); request->send(response); }); - aserver.begin(); + asyncServer.begin(); asyncApiServer.begin(); - Serial.println("Async API server started on port 8082"); + Serial.println("Async API server started on port 8081"); } void Web::loop() { server.handleClient(); @@ -121,6 +121,28 @@ bool Web::isAuthenticated(WebServer &server, bool cfg) { } return true; } +bool Web::isAuthenticated(AsyncWebServerRequest *request, bool cfg) { + Serial.println("Checking async authentication"); + if(settings.Security.type == security_types::None) return true; + else if(!cfg && (settings.Security.permissions & static_cast(security_permissions::ConfigOnly)) == 0x01) return true; + else if(request->hasHeader("apikey")) { + Serial.println("Checking API Key..."); + char token[65]; + memset(token, 0x00, sizeof(token)); + this->createAPIToken(request->client()->remoteIP(), token); + if(String(token) != request->getHeader("apikey")->value()) { + request->send(401, _encoding_text, "Unauthorized API Key"); + return false; + } + // Key is valid + } + else { + Serial.println("Not authenticated..."); + request->send(401, _encoding_text, "Unauthorized API Key"); + return false; + } + return true; +} bool Web::createAPIPinToken(const IPAddress ipAddress, const char *pin, char *token) { return this->createAPIToken((String(pin) + ":" + ipAddress.toString()).c_str(), token); } @@ -1330,6 +1352,7 @@ void Web::handleDiscovery(AsyncWebServerRequest *request) { request->send(500, _encoding_text, "Invalid http method"); } void Web::handleGetRooms(AsyncWebServerRequest *request) { + if(!this->isAuthenticated(request)) return; if(request->method() == HTTP_POST || request->method() == HTTP_GET) { AsyncJsonResp resp; resp.beginResponse(request, g_async_content, sizeof(g_async_content)); @@ -1341,6 +1364,7 @@ void Web::handleGetRooms(AsyncWebServerRequest *request) { else request->send(404, _encoding_text, _response_404); } void Web::handleGetShades(AsyncWebServerRequest *request) { + if(!this->isAuthenticated(request)) return; if(request->method() == HTTP_POST || request->method() == HTTP_GET) { AsyncJsonResp resp; resp.beginResponse(request, g_async_content, sizeof(g_async_content)); @@ -1352,6 +1376,7 @@ void Web::handleGetShades(AsyncWebServerRequest *request) { else request->send(404, _encoding_text, _response_404); } void Web::handleGetGroups(AsyncWebServerRequest *request) { + if(!this->isAuthenticated(request)) return; if(request->method() == HTTP_POST || request->method() == HTTP_GET) { AsyncJsonResp resp; resp.beginResponse(request, g_async_content, sizeof(g_async_content)); @@ -1363,6 +1388,7 @@ void Web::handleGetGroups(AsyncWebServerRequest *request) { else request->send(404, _encoding_text, _response_404); } void Web::handleController(AsyncWebServerRequest *request) { + if(!this->isAuthenticated(request)) return; if(request->method() == HTTP_POST || request->method() == HTTP_GET) { settings.printAvailHeap(); AsyncJsonResp resp; @@ -1453,6 +1479,7 @@ void Web::handleLogin(AsyncWebServerRequest *request, JsonVariant &json) { } void Web::handleShadeCommand(AsyncWebServerRequest *request, JsonVariant &json) { if(request->method() == HTTP_OPTIONS) { request->send(200); return; } + if(!this->isAuthenticated(request)) return; uint8_t shadeId = 255; uint8_t target = 255; uint8_t stepSize = 0; @@ -1491,6 +1518,7 @@ void Web::handleShadeCommand(AsyncWebServerRequest *request, JsonVariant &json) } void Web::handleGroupCommand(AsyncWebServerRequest *request, JsonVariant &json) { if(request->method() == HTTP_OPTIONS) { request->send(200); return; } + if(!this->isAuthenticated(request)) return; uint8_t groupId = 255; uint8_t stepSize = 0; int8_t repeat = -1; @@ -1524,6 +1552,7 @@ void Web::handleGroupCommand(AsyncWebServerRequest *request, JsonVariant &json) } void Web::handleTiltCommand(AsyncWebServerRequest *request, JsonVariant &json) { if(request->method() == HTTP_OPTIONS) { request->send(200); return; } + if(!this->isAuthenticated(request)) return; uint8_t shadeId = 255; uint8_t target = 255; somfy_commands command = somfy_commands::My; @@ -1555,6 +1584,7 @@ void Web::handleTiltCommand(AsyncWebServerRequest *request, JsonVariant &json) { } void Web::handleRepeatCommand(AsyncWebServerRequest *request, JsonVariant &json) { if(request->method() == HTTP_OPTIONS) { request->send(200); return; } + if(!this->isAuthenticated(request)) return; uint8_t shadeId = 255; uint8_t groupId = 255; uint8_t stepSize = 0; @@ -1602,6 +1632,7 @@ void Web::handleRepeatCommand(AsyncWebServerRequest *request, JsonVariant &json) } void Web::handleRoom(AsyncWebServerRequest *request) { if(request->method() == HTTP_OPTIONS) { request->send(200); return; } + if(!this->isAuthenticated(request)) return; if(request->method() == HTTP_GET) { if(asyncHasParam(request, "roomId")) { int roomId = asyncParam(request, "roomId").toInt(); @@ -1622,6 +1653,7 @@ void Web::handleRoom(AsyncWebServerRequest *request) { } void Web::handleShade(AsyncWebServerRequest *request) { if(request->method() == HTTP_OPTIONS) { request->send(200); return; } + if(!this->isAuthenticated(request)) return; if(request->method() == HTTP_GET) { if(asyncHasParam(request, "shadeId")) { int shadeId = asyncParam(request, "shadeId").toInt(); @@ -1642,6 +1674,7 @@ void Web::handleShade(AsyncWebServerRequest *request) { } void Web::handleGroup(AsyncWebServerRequest *request) { if(request->method() == HTTP_OPTIONS) { request->send(200); return; } + if(!this->isAuthenticated(request)) return; if(request->method() == HTTP_GET) { if(asyncHasParam(request, "groupId")) { int groupId = asyncParam(request, "groupId").toInt(); @@ -1662,6 +1695,7 @@ void Web::handleGroup(AsyncWebServerRequest *request) { } void Web::handleSetPositions(AsyncWebServerRequest *request, JsonVariant &json) { if(request->method() == HTTP_OPTIONS) { request->send(200); return; } + if(!this->isAuthenticated(request)) return; uint8_t shadeId = asyncHasParam(request, "shadeId") ? asyncParam(request, "shadeId").toInt() : 255; int8_t pos = asyncHasParam(request, "position") ? asyncParam(request, "position").toInt() : -1; int8_t tiltPos = asyncHasParam(request, "tiltPosition") ? asyncParam(request, "tiltPosition").toInt() : -1; @@ -1690,6 +1724,7 @@ void Web::handleSetPositions(AsyncWebServerRequest *request, JsonVariant &json) } void Web::handleSetSensor(AsyncWebServerRequest *request, JsonVariant &json) { if(request->method() == HTTP_OPTIONS) { request->send(200); return; } + if(!this->isAuthenticated(request)) return; uint8_t shadeId = asyncHasParam(request, "shadeId") ? asyncParam(request, "shadeId").toInt() : 255; uint8_t groupId = asyncHasParam(request, "groupId") ? asyncParam(request, "groupId").toInt() : 255; int8_t sunny = asyncHasParam(request, "sunny") ? (toBoolean(asyncParam(request, "sunny").c_str(), false) ? 1 : 0) : -1; @@ -1741,6 +1776,7 @@ void Web::handleSetSensor(AsyncWebServerRequest *request, JsonVariant &json) { } void Web::handleDownloadFirmware(AsyncWebServerRequest *request) { if(request->method() == HTTP_OPTIONS) { request->send(200); return; } + if(!this->isAuthenticated(request)) return; GitRepo repo; GitRelease *rel = nullptr; int8_t err = repo.getReleases(); @@ -1773,6 +1809,7 @@ void Web::handleDownloadFirmware(AsyncWebServerRequest *request) { else request->send(err, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Error communicating with Github.\"}")); } void Web::handleBackup(AsyncWebServerRequest *request) { + if(!this->isAuthenticated(request)) return; bool attach = false; if(asyncHasParam(request, "attach")) attach = toBoolean(asyncParam(request, "attach").c_str(), false); Serial.println("Async saving current shade information"); @@ -1801,6 +1838,7 @@ void Web::handleBackup(AsyncWebServerRequest *request) { } void Web::handleReboot(AsyncWebServerRequest *request) { if(request->method() == HTTP_OPTIONS) { request->send(200); return; } + if(!this->isAuthenticated(request)) return; if(request->method() == HTTP_POST || request->method() == HTTP_PUT) { Serial.println("Async Rebooting ESP..."); rebootDelay.reboot = true; diff --git a/src/Web.h b/src/Web.h index 649a088..6c39102 100644 --- a/src/Web.h +++ b/src/Web.h @@ -43,8 +43,9 @@ class Web { bool createAPIPinToken(const IPAddress ipAddress, const char *pin, char *token); bool createAPIPasswordToken(const IPAddress ipAddress, const char *username, const char *password, char *token); bool isAuthenticated(WebServer &server, bool cfg = false); + bool isAuthenticated(AsyncWebServerRequest *request, bool cfg = false); - // Async API handler overloads (port 8082) + // Async API handler overloads (port 8081) void handleDiscovery(AsyncWebServerRequest *request); void handleGetRooms(AsyncWebServerRequest *request); void handleGetShades(AsyncWebServerRequest *request); From a55dc7f66b4d8dd12add7cefaebf1b74fea41b8b Mon Sep 17 00:00:00 2001 From: cjkas Date: Tue, 24 Mar 2026 08:02:10 +0100 Subject: [PATCH 12/16] update to async web server --- .claude/settings.local.json | 8 - .vscode/c_cpp_properties.json | 526 ----- .vscode/extensions.json | 10 - .vscode/launch.json | 44 - src/WResp.cpp | 6 + src/WResp.h | 5 +- src/Web.cpp | 3424 +++++++++++---------------------- src/Web.h | 32 +- 8 files changed, 1089 insertions(+), 2966 deletions(-) delete mode 100644 .claude/settings.local.json delete mode 100644 .vscode/c_cpp_properties.json delete mode 100644 .vscode/extensions.json delete mode 100644 .vscode/launch.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 9b45ded..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(~/.platformio/penv/Scripts/platformio.exe run:*)", - "Bash(grep -n \"emitSockets\" \"c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/src\"/*.cpp)" - ] - } -} diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json deleted file mode 100644 index 44054fa..0000000 --- a/.vscode/c_cpp_properties.json +++ /dev/null @@ -1,526 +0,0 @@ -// -// !!! WARNING !!! AUTO-GENERATED FILE! -// PLEASE DO NOT MODIFY IT AND USE "platformio.ini": -// https://docs.platformio.org/page/projectconf/section_env_build.html#build-flags -// -{ - "configurations": [ - { - "name": "PlatformIO", - "includePath": [ - "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/include", - "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/WebServer/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/AsyncUDP/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Ethernet/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/ESPmDNS/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Update/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/HTTPClient/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/WiFiClientSecure/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Preferences/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/LittleFS/src", - "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/ESPAsyncWebServer/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/WiFi/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/FS/src", - "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/AsyncTCP/src", - "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/PubSubClient/src", - "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/SmartRC-CC1101-Driver-Lib", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/SPI/src", - "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/ArduinoJson/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/newlib/platform_include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include/esp_additions/freertos", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/port/xtensa/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include/esp_additions", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include/soc", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include/soc/esp32", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/port/esp32", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/port/esp32/private_include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/heap/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/log/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/include/apps", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/include/apps/sntp", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/lwip/src/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/port/esp32/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/port/esp32/include/arch", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/esp32", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/esp32/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/esp32/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/platform_port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/include/esp32", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/esp32", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_common/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/port/soc", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/port/public_compat", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/xtensa/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/xtensa/esp32/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/driver/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/driver/esp32/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_pm/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_ringbuf/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/efuse/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/efuse/esp32/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/vfs/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_wifi/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_event/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_netif/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_eth/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/tcpip_adapter/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_phy/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_phy/esp32/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_ipc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/app_trace/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_timer/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/mbedtls/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/esp_crt_bundle/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/app_update/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/spi_flash/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bootloader_support/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nvs_flash/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/pthread/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/xtensa", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/esp32", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espcoredump/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espcoredump/include/port/xtensa", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/esp_supplicant/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ieee802154/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/console", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/asio/asio/asio/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/asio/port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/osi/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/include/esp32/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/api/include/api", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/btc/profile/esp/blufi/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/btc/profile/esp/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/host/bluedroid/api/include/api", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_common/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_common/tinycrypt/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core/storage", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/btc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/common/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/client/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/server/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api/core/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api/models/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/cbor/port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/unity/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/unity/unity/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/cmock/CMock/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/coap/port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/coap/libcoap/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nghttp/port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nghttp/nghttp2/lib/includes", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-tls", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-tls/esp-tls-crypto", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_adc_cal/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hid/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/tcp_transport/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_http_client/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_http_server/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_https_ota/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_https_server/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_lcd/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_lcd/interface", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protobuf-c/protobuf-c", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/common", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/security", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/transports", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mdns/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_local_ctrl/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/sdmmc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_serial_slave_link/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_websocket_client/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/expat/expat/expat/lib", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/expat/port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wear_levelling/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/diskio", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/vfs", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freemodbus/freemodbus/common/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/idf_test/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/idf_test/include/esp32", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/jsmn/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json/cJSON", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/libsodium/libsodium/src/libsodium/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/libsodium/port_include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mqtt/esp-mqtt/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/openssl/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/perfmon/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/spiffs/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ulp/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wifi_provisioning/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/rmaker_common/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_diagnostics/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/rtc_store/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_insights/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_parser/upstream/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_parser/upstream", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_generator/upstream", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_schedule/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp_secure_cert_mgr/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rainmaker/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/gpio_button/button/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/qrcode/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ws2812_led", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_littlefs/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/tool", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/typedef", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/image", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/math", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/nn", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/layer", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/detect", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/model_zoo", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32-camera/driver/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32-camera/conversions/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/dotprod/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/support/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/support/mem/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/hann/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman_harris/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman_nuttall/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/nuttall/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/flat_top/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/iir/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/fir/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/add/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/sub/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/mul/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/addc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/mulc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/sqrt/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/mul/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/add/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/addc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/mulc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/sub/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/fft/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/dct/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/conv/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/common/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/mul/test/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/kalman/ekf/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/kalman/ekf_imu13states/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fb_gfx/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/dio_qspi/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/cores/esp32", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/variants/esp32", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/ArduinoOTA/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/BLE/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/BluetoothSerial/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/DNSServer/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/EEPROM/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/ESP32/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/FFat/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/HTTPUpdate/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/HTTPUpdateServer/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/I2S/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Insights/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/NetBIOS/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/RainMaker/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/SD/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/SD_MMC/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/SPIFFS/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/SimpleBLE/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Ticker/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/USB/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/WiFiProv/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Wire/src", - "" - ], - "browse": { - "limitSymbolsToIncludedHeaders": true, - "path": [ - "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/include", - "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/WebServer/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/AsyncUDP/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Ethernet/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/ESPmDNS/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Update/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/HTTPClient/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/WiFiClientSecure/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Preferences/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/LittleFS/src", - "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/ESPAsyncWebServer/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/WiFi/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/FS/src", - "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/AsyncTCP/src", - "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/PubSubClient/src", - "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/SmartRC-CC1101-Driver-Lib", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/SPI/src", - "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/libdeps/esp32dev/ArduinoJson/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/newlib/platform_include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include/esp_additions/freertos", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/port/xtensa/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include/esp_additions", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include/soc", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include/soc/esp32", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/port/esp32", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/port/esp32/private_include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/heap/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/log/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/include/apps", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/include/apps/sntp", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/lwip/src/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/port/esp32/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/port/esp32/include/arch", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/esp32", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/esp32/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/esp32/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/platform_port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/include/esp32", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/esp32", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_common/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/port/soc", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/port/public_compat", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/xtensa/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/xtensa/esp32/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/driver/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/driver/esp32/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_pm/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_ringbuf/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/efuse/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/efuse/esp32/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/vfs/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_wifi/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_event/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_netif/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_eth/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/tcpip_adapter/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_phy/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_phy/esp32/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_ipc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/app_trace/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_timer/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/mbedtls/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/esp_crt_bundle/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/app_update/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/spi_flash/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bootloader_support/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nvs_flash/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/pthread/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/xtensa", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/esp32", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espcoredump/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espcoredump/include/port/xtensa", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/esp_supplicant/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ieee802154/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/console", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/asio/asio/asio/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/asio/port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/osi/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/include/esp32/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/api/include/api", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/btc/profile/esp/blufi/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/btc/profile/esp/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/host/bluedroid/api/include/api", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_common/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_common/tinycrypt/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core/storage", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/btc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/common/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/client/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/server/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api/core/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api/models/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/cbor/port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/unity/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/unity/unity/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/cmock/CMock/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/coap/port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/coap/libcoap/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nghttp/port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nghttp/nghttp2/lib/includes", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-tls", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-tls/esp-tls-crypto", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_adc_cal/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hid/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/tcp_transport/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_http_client/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_http_server/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_https_ota/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_https_server/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_lcd/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_lcd/interface", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protobuf-c/protobuf-c", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/common", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/security", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/transports", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mdns/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_local_ctrl/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/sdmmc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_serial_slave_link/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_websocket_client/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/expat/expat/expat/lib", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/expat/port/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wear_levelling/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/diskio", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/vfs", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freemodbus/freemodbus/common/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/idf_test/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/idf_test/include/esp32", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/jsmn/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json/cJSON", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/libsodium/libsodium/src/libsodium/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/libsodium/port_include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mqtt/esp-mqtt/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/openssl/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/perfmon/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/spiffs/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ulp/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wifi_provisioning/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/rmaker_common/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_diagnostics/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/rtc_store/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_insights/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_parser/upstream/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_parser/upstream", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_generator/upstream", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_schedule/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp_secure_cert_mgr/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rainmaker/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/gpio_button/button/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/qrcode/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ws2812_led", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_littlefs/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/tool", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/typedef", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/image", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/math", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/nn", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/layer", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/detect", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/model_zoo", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32-camera/driver/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32-camera/conversions/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/dotprod/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/support/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/support/mem/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/hann/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman_harris/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman_nuttall/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/nuttall/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/flat_top/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/iir/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/fir/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/add/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/sub/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/mul/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/addc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/mulc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/sqrt/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/mul/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/add/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/addc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/mulc/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/sub/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/fft/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/dct/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/conv/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/common/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/mul/test/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/kalman/ekf/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/kalman/ekf_imu13states/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fb_gfx/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/dio_qspi/include", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/cores/esp32", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/variants/esp32", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/ArduinoOTA/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/BLE/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/BluetoothSerial/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/DNSServer/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/EEPROM/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/ESP32/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/FFat/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/HTTPUpdate/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/HTTPUpdateServer/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/I2S/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Insights/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/NetBIOS/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/RainMaker/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/SD/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/SD_MMC/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/SPIFFS/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/SimpleBLE/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Ticker/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/USB/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/WiFiProv/src", - "C:/Users/oem/.platformio/packages/framework-arduinoespressif32/libraries/Wire/src", - "" - ] - }, - "defines": [ - "PLATFORMIO=60119", - "ARDUINO_ESP32_DEV", - "CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=1", - "CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF=1", - "CONFIG_ESP_COREDUMP_CHECKSUM_CRC32=1", - "CONFIG_ESP_TASK_WDT_PANIC=1", - "CONFIG_ESP_COREDUMP_DECODE_INFO=1", - "HAVE_CONFIG_H", - "MBEDTLS_CONFIG_FILE=\"mbedtls/esp_config.h\"", - "UNITY_INCLUDE_CONFIG_H", - "WITH_POSIX", - "_GNU_SOURCE", - "IDF_VER=\"v4.4.7-dirty\"", - "ESP_PLATFORM", - "_POSIX_READER_WRITER_LOCKS", - "ARDUINO_ARCH_ESP32", - "ESP32", - "F_CPU=240000000L", - "ARDUINO=10812", - "ARDUINO_VARIANT=\"esp32\"", - "ARDUINO_BOARD=\"Espressif ESP32 Dev Module\"", - "ARDUINO_PARTITION_huge_app", - "" - ], - "cStandard": "gnu99", - "cppStandard": "gnu++11", - "compilerPath": "C:/Users/oem/.platformio/packages/toolchain-xtensa-esp32/bin/xtensa-esp32-elf-gcc.exe", - "compilerArgs": [ - "-mlongcalls", - "" - ] - } - ], - "version": 4 -} diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 080e70d..0000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - // See http://go.microsoft.com/fwlink/?LinkId=827846 - // for the documentation about the extensions.json format - "recommendations": [ - "platformio.platformio-ide" - ], - "unwantedRecommendations": [ - "ms-vscode.cpptools-extension-pack" - ] -} diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index c6b0639..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,44 +0,0 @@ -// AUTOMATICALLY GENERATED FILE. PLEASE DO NOT MODIFY IT MANUALLY -// -// PlatformIO Debugging Solution -// -// Documentation: https://docs.platformio.org/en/latest/plus/debugging.html -// Configuration: https://docs.platformio.org/en/latest/projectconf/sections/env/options/debug/index.html - -{ - "version": "0.2.0", - "configurations": [ - { - "type": "platformio-debug", - "request": "launch", - "name": "PIO Debug", - "executable": "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/build/esp32dev/firmware.elf", - "projectEnvName": "esp32devdbg", - "toolchainBinDir": "C:/Users/oem/.platformio/packages/toolchain-xtensa-esp32/bin", - "internalConsoleOptions": "openOnSessionStart", - "preLaunchTask": { - "type": "PlatformIO", - "task": "Pre-Debug" - } - }, - { - "type": "platformio-debug", - "request": "launch", - "name": "PIO Debug (skip Pre-Debug)", - "executable": "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/build/esp32dev/firmware.elf", - "projectEnvName": "esp32devdbg", - "toolchainBinDir": "C:/Users/oem/.platformio/packages/toolchain-xtensa-esp32/bin", - "internalConsoleOptions": "openOnSessionStart" - }, - { - "type": "platformio-debug", - "request": "launch", - "name": "PIO Debug (without uploading)", - "executable": "c:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/.pio/build/esp32dev/firmware.elf", - "projectEnvName": "esp32devdbg", - "toolchainBinDir": "C:/Users/oem/.platformio/packages/toolchain-xtensa-esp32/bin", - "internalConsoleOptions": "openOnSessionStart", - "loadMode": "manual" - } - ] -} diff --git a/src/WResp.cpp b/src/WResp.cpp index 258dfd9..4c3d379 100644 --- a/src/WResp.cpp +++ b/src/WResp.cpp @@ -1,4 +1,6 @@ #include "WResp.h" +#include +#include void JsonSockEvent::beginEvent(AsyncWebSocket *server, const char *evt, char *buff, size_t buffSize) { this->server = server; this->buff = buff; @@ -69,6 +71,7 @@ void JsonResponse::_safecat(const char *val, bool escape) { } void AsyncJsonResp::beginResponse(AsyncWebServerRequest *request, char *buff, size_t buffSize) { + this->_request = request; this->buff = buff; this->buffSize = buffSize; this->buff[0] = 0x00; @@ -78,6 +81,9 @@ void AsyncJsonResp::beginResponse(AsyncWebServerRequest *request, char *buff, si } void AsyncJsonResp::endResponse() { if(strlen(this->buff)) this->flush(); + if(this->_request && this->_stream) { + this->_request->send(this->_stream); + } } void AsyncJsonResp::flush() { if(this->_stream && strlen(this->buff) > 0) { diff --git a/src/WResp.h b/src/WResp.h index 4235ff7..6412c40 100644 --- a/src/WResp.h +++ b/src/WResp.h @@ -1,10 +1,10 @@ -#include -#include #include #include "Somfy.h" #ifndef wresp_h #define wresp_h +class WebServer; + class JsonFormatter { protected: char *buff; @@ -66,6 +66,7 @@ class JsonResponse : public JsonFormatter { class AsyncJsonResp : public JsonFormatter { protected: void _safecat(const char *val, bool escape = false) override; + AsyncWebServerRequest *_request = nullptr; AsyncResponseStream *_stream = nullptr; public: void beginResponse(AsyncWebServerRequest *request, char *buff, size_t buffSize); diff --git a/src/Web.cpp b/src/Web.cpp index 9488d1e..cc54e17 100644 --- a/src/Web.cpp +++ b/src/Web.cpp @@ -1,5 +1,4 @@ #include -#include #include #include #include @@ -29,7 +28,6 @@ extern Network net; //#define WEB_MAX_RESPONSE 34768 #define WEB_MAX_RESPONSE 4096 -static char g_content[WEB_MAX_RESPONSE]; static char g_async_content[WEB_MAX_RESPONSE]; @@ -42,13 +40,10 @@ static const char _encoding_text[] = "text/plain"; static const char _encoding_html[] = "text/html"; static const char _encoding_json[] = "application/json"; -WebServer apiServer(8082); -WebServer server(80); -AsyncWebServer asyncServer(81); +AsyncWebServer asyncServer(80); AsyncWebServer asyncApiServer(8081); void Web::startup() { Serial.println("Launching web server..."); - asyncServer.serveStatic("/", LittleFS, "/").setDefaultFile("index.html"); asyncServer.on("/loginContext", HTTP_GET, [](AsyncWebServerRequest *request) { AsyncJsonResponse *response = new AsyncJsonResponse(); @@ -62,64 +57,13 @@ void Web::startup() { response->setLength(); request->send(response); }); - asyncServer.begin(); asyncApiServer.begin(); Serial.println("Async API server started on port 8081"); } void Web::loop() { - server.handleClient(); delay(1); - apiServer.handleClient(); - delay(1); -} -void Web::sendCORSHeaders(WebServer &server) { - //server.sendHeader(F("Connection"), F("Keep-Alive")); - //server.sendHeader(F("Keep-Alive"), F("timeout=5, max=1000")); - //server.sendHeader(F("Access-Control-Allow-Origin"), F("*")); - //server.sendHeader(F("Access-Control-Max-Age"), F("600")); - //server.sendHeader(F("Access-Control-Allow-Methods"), F("PUT,POST,GET,OPTIONS")); - //server.sendHeader(F("Access-Control-Allow-Headers"), F("*")); -} -void Web::sendCacheHeaders(uint32_t seconds) { - server.sendHeader(F("Cache-Control"), F("public, max-age=604800, immutable")); } void Web::end() { - //server.end(); -} -void Web::handleDeserializationError(WebServer &server, DeserializationError &err) { - switch (err.code()) { - case DeserializationError::InvalidInput: - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid JSON payload\"}")); - break; - case DeserializationError::NoMemory: - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Out of memory parsing JSON\"}")); - break; - default: - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"General JSON Deserialization failed\"}")); - break; - } -} -bool Web::isAuthenticated(WebServer &server, bool cfg) { - Serial.println("Checking authentication"); - if(settings.Security.type == security_types::None) return true; - else if(!cfg && (settings.Security.permissions & static_cast(security_permissions::ConfigOnly)) == 0x01) return true; - else if(server.hasHeader("apikey")) { - // Api key was supplied. - Serial.println("Checking API Key..."); - char token[65]; - memset(token, 0x00, sizeof(token)); - this->createAPIToken(server.client().remoteIP(), token); - // Compare the tokens. - if(String(token) != server.header("apikey")) return false; - server.sendHeader("apikey", token); - } - else { - // Send a 401 - Serial.println("Not authenticated..."); - server.send(401, "Unauthorized API Key"); - return false; - } - return true; } bool Web::isAuthenticated(AsyncWebServerRequest *request, bool cfg) { Serial.println("Checking async authentication"); @@ -174,936 +118,8 @@ bool Web::createAPIToken(const IPAddress ipAddress, char *token) { else createAPIToken(ipAddress.toString().c_str(), token); return true; } -void Web::handleLogout(WebServer &server) { - Serial.println("Logging out of webserver"); - server.sendHeader("Location", "/"); - server.sendHeader("Cache-Control", "no-cache"); - server.sendHeader("Set-Cookie", "ESPSOMFYID=0"); - server.send(301); -} -void Web::handleLogin(WebServer &server) { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - StaticJsonDocument<256> doc; - JsonObject obj = doc.to(); - char token[65]; - memset(&token, 0x00, sizeof(token)); - this->createAPIToken(server.client().remoteIP(), token); - obj["type"] = static_cast(settings.Security.type); - if(settings.Security.type == security_types::None) { - obj["apiKey"] = token; - obj["msg"] = "Success"; - obj["success"] = true; - serializeJson(doc, g_content); - server.send(200, _encoding_json, g_content); - return; - } - Serial.println("Web logging in..."); - char username[33] = ""; - char password[33] = ""; - char pin[5] = ""; - memset(username, 0x00, sizeof(username)); - memset(password, 0x00, sizeof(password)); - memset(pin, 0x00, sizeof(pin)); - if(server.hasArg("plain")) { - DynamicJsonDocument docin(512); - DeserializationError err = deserializeJson(docin, server.arg("plain")); - if (err) { - this->handleDeserializationError(server, err); - return; - } - else { - JsonObject objin = docin.as(); - if(objin.containsKey("username") && objin["username"]) strlcpy(username, objin["username"], sizeof(username)); - if(objin.containsKey("password") && objin["password"]) strlcpy(password, objin["password"], sizeof(password)); - if(objin.containsKey("pin") && objin["pin"]) strlcpy(pin, objin["pin"], sizeof(pin)); - } - } - else { - if(server.hasArg("username")) strlcpy(username, server.arg("username").c_str(), sizeof(username)); - if(server.hasArg("password")) strlcpy(password, server.arg("password").c_str(), sizeof(password)); - if(server.hasArg("pin")) strlcpy(pin, server.arg("pin").c_str(), sizeof(pin)); - } - // At this point we should have all the data we need to login. - if(settings.Security.type == security_types::PinEntry) { - Serial.print("Validating pin "); - Serial.println(pin); - if(strlen(pin) == 0 || strcmp(pin, settings.Security.pin) != 0) { - obj["success"] = false; - obj["msg"] = "Invalid Pin Entry"; - } - else { - obj["success"] = true; - obj["msg"] = "Login successful"; - obj["apiKey"] = token; - } - } - else if(settings.Security.type == security_types::Password) { - if(strlen(username) == 0 || strlen(password) == 0 || strcmp(username, settings.Security.username) != 0 || strcmp(password, settings.Security.password) != 0) { - obj["success"] = false; - obj["msg"] = "Invalid username or password"; - } - else { - obj["success"] = true; - obj["msg"] = "Login successful"; - obj["apiKey"] = token; - } - } - serializeJson(doc, g_content); - server.send(200, _encoding_json, g_content); - return; -} -void Web::handleStreamFile(WebServer &server, const char *filename, const char *encoding) { - if(git.lockFS) { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Filesystem update in progress\"}")); - return; - } - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - esp_task_wdt_reset(); - // Try gzip variant first; streamFile() auto-adds Content-Encoding: gzip for .gz files - String gzFilename = String(filename) + ".gz"; - File file = LittleFS.open(gzFilename.c_str(), "r"); - if (!file) { - file = LittleFS.open(filename, "r"); - } - if (!file) { - Serial.print("Error opening "); - Serial.println(filename); - server.send(500, _encoding_text, "Error opening file"); - return; - } - esp_task_wdt_delete(NULL); - server.streamFile(file, encoding); - file.close(); - esp_task_wdt_add(NULL); - esp_task_wdt_reset(); -} -void Web::handleController(WebServer &server) { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - settings.printAvailHeap(); - if (method == HTTP_POST || method == HTTP_GET) { - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginObject(); - resp.addElem("maxRooms", (uint8_t)SOMFY_MAX_ROOMS); - resp.addElem("maxShades", (uint8_t)SOMFY_MAX_SHADES); - resp.addElem("maxGroups", (uint8_t)SOMFY_MAX_GROUPS); - resp.addElem("maxGroupedShades", (uint8_t)SOMFY_MAX_GROUPED_SHADES); - resp.addElem("maxLinkedRemotes", (uint8_t)SOMFY_MAX_LINKED_REMOTES); - resp.addElem("startingAddress", (uint32_t)somfy.startingAddress); - - resp.beginObject("transceiver"); - somfy.transceiver.toJSON(resp); - resp.endObject(); - resp.beginObject("version"); - git.toJSON(resp); - resp.endObject(); - resp.beginArray("rooms"); - somfy.toJSONRooms(resp); - resp.endArray(); - resp.beginArray("shades"); - somfy.toJSONShades(resp); - resp.endArray(); - resp.beginArray("groups"); - somfy.toJSONGroups(resp); - resp.endArray(); - resp.beginArray("repeaters"); - somfy.toJSONRepeaters(resp); - resp.endArray(); - resp.endObject(); - resp.endResponse(); - } - else server.send(404, _encoding_text, _response_404); -} -void Web::handleLoginContext(WebServer &server) { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginObject(); - resp.addElem("type", static_cast(settings.Security.type)); - resp.addElem("permissions", settings.Security.permissions); - resp.addElem("serverId", settings.serverId); - resp.addElem("version", settings.fwVersion.name); - resp.addElem("model", "ESPSomfyRTS"); - resp.addElem("hostname", settings.hostname); - resp.endObject(); - resp.endResponse(); -} -void Web::handleGetRepeaters(WebServer &server) { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - if (method == HTTP_POST || method == HTTP_GET) { - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginArray(); - somfy.toJSONRepeaters(resp); - resp.endArray(); - resp.endResponse(); - } - else server.send(404, _encoding_text, _response_404); -} -void Web::handleGetRooms(WebServer &server) { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - if (method == HTTP_POST || method == HTTP_GET) { - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginArray(); - somfy.toJSONRooms(resp); - resp.endArray(); - resp.endResponse(); - } - else server.send(404, _encoding_text, _response_404); -} -void Web::handleGetShades(WebServer &server) { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - if (method == HTTP_POST || method == HTTP_GET) { - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginArray(); - somfy.toJSONShades(resp); - resp.endArray(); - resp.endResponse(); - } - else server.send(404, _encoding_text, _response_404); -} -void Web::handleGetGroups(WebServer &server) { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - if (method == HTTP_POST || method == HTTP_GET) { - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginArray(); - somfy.toJSONGroups(resp); - resp.endArray(); - resp.endResponse(); - } - else server.send(404, _encoding_text, _response_404); -} -void Web::handleShadeCommand(WebServer& server) { - webServer.sendCORSHeaders(server); - if (server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - uint8_t shadeId = 255; - uint8_t target = 255; - uint8_t stepSize = 0; - int8_t repeat = -1; - somfy_commands command = somfy_commands::My; - if (method == HTTP_GET || method == HTTP_PUT || method == HTTP_POST) { - if (server.hasArg("shadeId")) { - shadeId = atoi(server.arg("shadeId").c_str()); - if (server.hasArg("command")) command = translateSomfyCommand(server.arg("command")); - else if (server.hasArg("target")) target = atoi(server.arg("target").c_str()); - if (server.hasArg("repeat")) repeat = atoi(server.arg("repeat").c_str()); - if(server.hasArg("stepSize")) stepSize = atoi(server.arg("stepSize").c_str()); - } - else if (server.hasArg("plain")) { - Serial.println("Sending Shade Command"); - DynamicJsonDocument doc(512); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - this->handleDeserializationError(server, err); - return; - } - else { - JsonObject obj = doc.as(); - if (obj.containsKey("shadeId")) shadeId = obj["shadeId"]; - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade id was supplied.\"}")); - if (obj.containsKey("command")) { - String scmd = obj["command"]; - command = translateSomfyCommand(scmd); - } - else if (obj.containsKey("target")) { - target = obj["target"].as(); - } - if (obj.containsKey("repeat")) repeat = obj["repeat"].as(); - if(obj.containsKey("stepSize")) stepSize = obj["stepSize"].as(); - } - } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade object supplied.\"}")); - SomfyShade* shade = somfy.getShadeById(shadeId); - if (shade) { - Serial.print("Received:"); - Serial.println(server.arg("plain")); - // Send the command to the shade. - if (target <= 100) - shade->moveToTarget(shade->transformPosition(target)); - else - shade->sendCommand(command, repeat > 0 ? repeat : shade->repeats, stepSize); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginObject(); - shade->toJSONRef(resp); - resp.endObject(); - resp.endResponse(); - } - else { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade with the specified id not found.\"}")); - } - } - else - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid Http method\"}")); -} -void Web::handleRepeatCommand(WebServer& server) { - webServer.sendCORSHeaders(server); - HTTPMethod method = server.method(); - if (method == HTTP_OPTIONS) { server.send(200, "OK"); return; } - uint8_t shadeId = 255; - uint8_t groupId = 255; - uint8_t stepSize = 0; - int8_t repeat = -1; - somfy_commands command = somfy_commands::My; - if (method == HTTP_GET || method == HTTP_PUT || method == HTTP_POST) { - if(server.hasArg("shadeId")) shadeId = atoi(server.arg("shadeId").c_str()); - else if(server.hasArg("groupId")) groupId = atoi(server.arg("groupId").c_str()); - if(server.hasArg("command")) command = translateSomfyCommand(server.arg("command")); - if(server.hasArg("repeat")) repeat = atoi(server.arg("repeat").c_str()); - if(server.hasArg("stepSize")) stepSize = atoi(server.arg("stepSize").c_str()); - if(shadeId == 255 && groupId == 255 && server.hasArg("plain")) { - DynamicJsonDocument doc(512); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - this->handleDeserializationError(server, err); - return; - } - else { - JsonObject obj = doc.as(); - if (obj.containsKey("shadeId")) shadeId = obj["shadeId"]; - if(obj.containsKey("groupId")) groupId = obj["groupId"]; - if(obj.containsKey("stepSize")) stepSize = obj["stepSize"]; - if (obj.containsKey("command")) { - String scmd = obj["command"]; - command = translateSomfyCommand(scmd); - } - if (obj.containsKey("repeat")) repeat = obj["repeat"].as(); - } - } - //DynamicJsonDocument sdoc(512); - //JsonObject sobj = sdoc.to(); - if(shadeId != 255) { - SomfyShade *shade = somfy.getShadeById(shadeId); - if(!shade) { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade reference could not be found.\"}")); - return; - } - if(shade->shadeType == shade_types::garage1 && command == somfy_commands::Prog) command = somfy_commands::Toggle; - if(!shade->isLastCommand(command)) { - // We are going to send this as a new command. - shade->sendCommand(command, repeat >= 0 ? repeat : shade->repeats, stepSize); - } - else { - shade->repeatFrame(repeat >= 0 ? repeat : shade->repeats); - } - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginArray(); - shade->toJSONRef(resp); - resp.endArray(); - resp.endResponse(); - } - else if(groupId != 255) { - SomfyGroup * group = somfy.getGroupById(groupId); - if(!group) { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Group reference could not be found.\"}")); - return; - } - if(!group->isLastCommand(command)) { - // We are going to send this as a new command. - group->sendCommand(command, repeat >= 0 ? repeat : group->repeats, stepSize); - } - else - group->repeatFrame(repeat >= 0 ? repeat : group->repeats); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginObject(); - group->toJSONRef(resp); - resp.endObject(); - resp.endResponse(); - - //group->toJSON(sobj); - //serializeJson(sdoc, g_content); - //server.send(200, _encoding_json, g_content); - } - } - else { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid Http method\"}")); - } -} -void Web::handleGroupCommand(WebServer &server) { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - uint8_t groupId = 255; - uint8_t stepSize = 0; - int8_t repeat = -1; - somfy_commands command = somfy_commands::My; - if (method == HTTP_GET || method == HTTP_PUT || method == HTTP_POST) { - if (server.hasArg("groupId")) { - groupId = atoi(server.arg("groupId").c_str()); - if (server.hasArg("command")) command = translateSomfyCommand(server.arg("command")); - if(server.hasArg("repeat")) repeat = atoi(server.arg("repeat").c_str()); - if(server.hasArg("stepSize")) stepSize = atoi(server.arg("stepSize").c_str()); - } - else if (server.hasArg("plain")) { - Serial.println("Sending Group Command"); - DynamicJsonDocument doc(256); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - this->handleDeserializationError(server, err); - return; - } - else { - JsonObject obj = doc.as(); - if (obj.containsKey("groupId")) groupId = obj["groupId"]; - else { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No group id was supplied.\"}")); - return; - } - if (obj.containsKey("command")) { - String scmd = obj["command"]; - command = translateSomfyCommand(scmd); - } - if(obj.containsKey("repeat")) repeat = obj["repeat"].as(); - if(obj.containsKey("stepSize")) stepSize = obj["stepSize"].as(); - } - } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No group object supplied.\"}")); - SomfyGroup * group = somfy.getGroupById(groupId); - if (group) { - Serial.print("Received:"); - Serial.println(server.arg("plain")); - // Send the command to the group. - group->sendCommand(command, repeat >= 0 ? repeat : group->repeats, stepSize); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginObject(); - group->toJSONRef(resp); - resp.endObject(); - resp.endResponse(); - } - else { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Group with the specified id not found.\"}")); - } - } - else - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid Http method\"}")); -} -void Web::handleTiltCommand(WebServer &server) { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - uint8_t shadeId = 255; - uint8_t target = 255; - somfy_commands command = somfy_commands::My; - if (method == HTTP_GET || method == HTTP_PUT || method == HTTP_POST) { - if (server.hasArg("shadeId")) { - shadeId = atoi(server.arg("shadeId").c_str()); - if (server.hasArg("command")) command = translateSomfyCommand(server.arg("command")); - else if(server.hasArg("target")) target = atoi(server.arg("target").c_str()); - } - else if (server.hasArg("plain")) { - Serial.println("Sending Shade Tilt Command"); - DynamicJsonDocument doc(256); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - this->handleDeserializationError(server, err); - return; - } - else { - JsonObject obj = doc.as(); - if (obj.containsKey("shadeId")) shadeId = obj["shadeId"]; - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade id was supplied.\"}")); - if (obj.containsKey("command")) { - String scmd = obj["command"]; - command = translateSomfyCommand(scmd); - } - else if(obj.containsKey("target")) { - target = obj["target"].as(); - } - } - } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade object supplied.\"}")); - SomfyShade* shade = somfy.getShadeById(shadeId); - if (shade) { - Serial.print("Received:"); - Serial.println(server.arg("plain")); - // Send the command to the shade. - if(target <= 100) - shade->moveToTiltTarget(shade->transformPosition(target)); - else - shade->sendTiltCommand(command); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginObject(); - shade->toJSONRef(resp); - resp.endObject(); - resp.endResponse(); - } - else { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade with the specified id not found.\"}")); - } - } - else - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid Http method\"}")); -} -void Web::handleRoom(WebServer &server) { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - if (method == HTTP_GET) { - if (server.hasArg("roomId")) { - int roomId = atoi(server.arg("roomId").c_str()); - SomfyRoom* room = somfy.getRoomById(roomId); - if (room) { - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginObject(); - room->toJSON(resp); - resp.endObject(); - resp.endResponse(); - } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Room Id not found.\"}")); - } - else { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"You must supply a valid room id.\"}")); - } - } - else if (method == HTTP_PUT || method == HTTP_POST) { - // We are updating an existing room. - if (server.hasArg("plain")) { - Serial.println("Updating a room"); - DynamicJsonDocument doc(512); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - this->handleDeserializationError(server, err); - return; - } - else { - JsonObject obj = doc.as(); - if (obj.containsKey("roomId")) { - SomfyRoom* room = somfy.getRoomById(obj["roomId"]); - if (room) { - uint8_t err = room->fromJSON(obj); - if(err == 0) { - room->save(); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginObject(); - room->toJSON(resp); - resp.endObject(); - resp.endResponse(); - } - else { - snprintf(g_content, sizeof(g_content), "{\"status\":\"DATA\",\"desc\":\"Data Error.\", \"code\":%d}", err); - server.send(500, _encoding_json, g_content); - } - } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Room Id not found.\"}")); - } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No room id was supplied.\"}")); - } - } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No room object supplied.\"}")); - } - else - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid Http method\"}")); -} -void Web::handleShade(WebServer &server) { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - if (method == HTTP_GET) { - if (server.hasArg("shadeId")) { - int shadeId = atoi(server.arg("shadeId").c_str()); - SomfyShade* shade = somfy.getShadeById(shadeId); - if (shade) { - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginObject(); - shade->toJSON(resp); - resp.endObject(); - resp.endResponse(); - } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade Id not found.\"}")); - } - else { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"You must supply a valid shade id.\"}")); - } - } - else if (method == HTTP_PUT || method == HTTP_POST) { - // We are updating an existing shade. - if (server.hasArg("plain")) { - Serial.println("Updating a shade"); - DynamicJsonDocument doc(512); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - this->handleDeserializationError(server, err); - return; - } - else { - JsonObject obj = doc.as(); - if (obj.containsKey("shadeId")) { - SomfyShade* shade = somfy.getShadeById(obj["shadeId"]); - if (shade) { - uint8_t err = shade->fromJSON(obj); - if(err == 0) { - shade->save(); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginObject(); - shade->toJSON(resp); - resp.endObject(); - resp.endResponse(); - } - else { - snprintf(g_content, sizeof(g_content), "{\"status\":\"DATA\",\"desc\":\"Data Error.\", \"code\":%d}", err); - server.send(500, _encoding_json, g_content); - } - } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade Id not found.\"}")); - } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade id was supplied.\"}")); - } - } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade object supplied.\"}")); - } - else - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid Http method\"}")); -} -void Web::handleGroup(WebServer &server) { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - if (method == HTTP_GET) { - if (server.hasArg("groupId")) { - int groupId = atoi(server.arg("groupId").c_str()); - SomfyGroup* group = somfy.getGroupById(groupId); - if (group) { - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginObject(); - group->toJSON(resp); - resp.endObject(); - resp.endResponse(); - } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Group Id not found.\"}")); - } - else { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"You must supply a valid shade id.\"}")); - } - } - else if (method == HTTP_PUT || method == HTTP_POST) { - // We are updating an existing group. - if (server.hasArg("plain")) { - Serial.println("Updating a group"); - DynamicJsonDocument doc(512); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - this->handleDeserializationError(server, err); - return; - } - else { - JsonObject obj = doc.as(); - if (obj.containsKey("groupId")) { - SomfyGroup* group = somfy.getGroupById(obj["groupId"]); - if (group) { - group->fromJSON(obj); - group->save(); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginObject(); - group->toJSON(resp); - resp.endObject(); - resp.endResponse(); - } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Group Id not found.\"}")); - } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No group id was supplied.\"}")); - } - } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No group object supplied.\"}")); - } - else - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid Http method\"}")); -} -void Web::handleDiscovery(WebServer &server) { - HTTPMethod method = apiServer.method(); - if (method == HTTP_POST || method == HTTP_GET) { - Serial.println("Discovery Requested"); - char connType[10] = "Unknown"; - if(net.connType == conn_types_t::ethernet) strcpy(connType, "Ethernet"); - else if(net.connType == conn_types_t::wifi) strcpy(connType, "Wifi"); - - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginObject(); - resp.addElem("serverId", settings.serverId); - resp.addElem("version", settings.fwVersion.name); - resp.addElem("latest", git.latest.name); - resp.addElem("model", "ESPSomfyRTS"); - resp.addElem("hostname", settings.hostname); - resp.addElem("authType", static_cast(settings.Security.type)); - resp.addElem("permissions", settings.Security.permissions); - resp.addElem("chipModel", settings.chipModel); - resp.addElem("connType", connType); - resp.addElem("checkForUpdate", settings.checkForUpdate); - resp.beginObject("memory"); - resp.addElem("max", ESP.getMaxAllocHeap()); - resp.addElem("free", ESP.getFreeHeap()); - resp.addElem("min", ESP.getMinFreeHeap()); - resp.addElem("total", ESP.getHeapSize()); - resp.addElem("uptime", (uint64_t)millis()); - resp.endObject(); - resp.beginArray("rooms"); - somfy.toJSONRooms(resp); - resp.endArray(); - resp.beginArray("shades"); - somfy.toJSONShades(resp); - resp.endArray(); - resp.beginArray("groups"); - somfy.toJSONGroups(resp); - resp.endArray(); - resp.endObject(); - resp.endResponse(); - net.needsBroadcast = true; - } - else - server.send(500, _encoding_text, "Invalid http method"); -} -void Web::handleBackup(WebServer &server, bool attach) { - webServer.sendCORSHeaders(server); - if(server.hasArg("attach")) attach = toBoolean(server.arg("attach").c_str(), attach); - if(attach) { - char filename[120]; - Timestamp ts; - char * iso = ts.getISOTime(); - // Replace the invalid characters as quickly as we can. - for(uint8_t i = 0; i < strlen(iso); i++) { - switch(iso[i]) { - case '.': - // Just trim off the ms. - iso[i] = '\0'; - break; - case ':': - iso[i] = '_'; - break; - } - } - snprintf(filename, sizeof(filename), "attachment; filename=\"ESPSomfyRTS %s.backup\"", iso); - Serial.println(filename); - server.sendHeader(F("Content-Disposition"), filename); - server.sendHeader(F("Access-Control-Expose-Headers"), F("Content-Disposition")); - } - Serial.println("Saving current shade information"); - somfy.writeBackup(); - if(somfy.backupData.length() == 0) { - server.send(500, _encoding_text, "backup failed"); - return; - } - server.send(200, _encoding_text, somfy.backupData); -} -void Web::handleSetPositions(WebServer &server) { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - uint8_t shadeId = (server.hasArg("shadeId")) ? atoi(server.arg("shadeId").c_str()) : 255; - int8_t pos = (server.hasArg("position")) ? atoi(server.arg("position").c_str()) : -1; - int8_t tiltPos = (server.hasArg("tiltPosition")) ? atoi(server.arg("tiltPosition").c_str()) : -1; - if(server.hasArg("plain")) { - DynamicJsonDocument doc(512); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - this->handleDeserializationError(server, err); - return; - } - else { - JsonObject obj = doc.as(); - if(obj.containsKey("shadeId")) shadeId = obj["shadeId"]; - if(obj.containsKey("position")) pos = obj["position"]; - if(obj.containsKey("tiltPosition")) tiltPos = obj["tiltPosition"]; - } - } - if(shadeId != 255) { - SomfyShade *shade = somfy.getShadeById(shadeId); - if(shade) { - if(pos >= 0) shade->target = shade->currentPos = pos; - if(tiltPos >= 0 && shade->tiltType != tilt_types::none) shade->tiltTarget = shade->currentTiltPos = tiltPos; - shade->emitState(); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginObject(); - shade->toJSON(resp); - resp.endObject(); - resp.endResponse(); - } - else - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"An invalid shadeId was provided\"}")); - } - else { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"shadeId was not provided\"}")); - } -} -void Web::handleSetSensor(WebServer &server) { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - uint8_t shadeId = (server.hasArg("shadeId")) ? atoi(server.arg("shadeId").c_str()) : 255; - uint8_t groupId = (server.hasArg("groupId")) ? atoi(server.arg("groupId").c_str()) : 255; - int8_t sunny = (server.hasArg("sunny")) ? toBoolean(server.arg("sunny").c_str(), false) ? 1 : 0 : -1; - int8_t windy = (server.hasArg("windy")) ? atoi(server.arg("windy").c_str()) : -1; - int8_t repeat = (server.hasArg("repeat")) ? atoi(server.arg("repeat").c_str()) : -1; - if(server.hasArg("plain")) { - DynamicJsonDocument doc(512); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - this->handleDeserializationError(server, err); - return; - } - else { - JsonObject obj = doc.as(); - if(obj.containsKey("shadeId")) shadeId = obj["shadeId"].as(); - if(obj.containsKey("groupId")) groupId = obj["groupId"].as(); - if(obj.containsKey("sunny")) { - if(obj["sunny"].is()) - sunny = obj["sunny"].as() ? 1 : 0; - else - sunny = obj["sunny"].as(); - } - if(obj.containsKey("windy")) { - if(obj["windy"].is()) - windy = obj["windy"].as() ? 1 : 0; - else - windy = obj["windy"].as(); - } - if(obj.containsKey("repeat")) repeat = obj["repeat"].as(); - } - } - if(shadeId != 255) { - SomfyShade *shade = somfy.getShadeById(shadeId); - if(shade) { - shade->sendSensorCommand(windy, sunny, repeat >= 0 ? (uint8_t)repeat : shade->repeats); - shade->emitState(); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginObject(); - shade->toJSON(resp); - resp.endObject(); - resp.endResponse(); - } - else - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"An invalid shadeId was provided\"}")); - - } - else if(groupId != 255) { - SomfyGroup *group = somfy.getGroupById(groupId); - if(group) { - group->sendSensorCommand(windy, sunny, repeat >= 0 ? (uint8_t)repeat : group->repeats); - group->emitState(); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginObject(); - group->toJSON(resp); - resp.endObject(); - resp.endResponse(); - } - else - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"An invalid groupId was provided\"}")); - } - else { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"shadeId was not provided\"}")); - } -} -void Web::handleDownloadFirmware(WebServer &server) { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - GitRepo repo; - GitRelease *rel = nullptr; - int8_t err = repo.getReleases(); - Serial.println("downloadFirmware called..."); - if(err == 0) { - if(server.hasArg("ver")) { - if(strcmp(server.arg("ver").c_str(), "latest") == 0) rel = &repo.releases[0]; - else if(strcmp(server.arg("ver").c_str(), "main") == 0) { - rel = &repo.releases[GIT_MAX_RELEASES]; - } - else { - for(uint8_t i = 0; i < GIT_MAX_RELEASES; i++) { - if(repo.releases[i].id == 0) continue; - if(strcmp(repo.releases[i].name, server.arg("ver").c_str()) == 0) { - rel = &repo.releases[i]; - } - } - } - if(rel) { - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginObject(); - rel->toJSON(resp); - resp.endObject(); - resp.endResponse(); - strcpy(git.targetRelease, rel->name); - git.status = GIT_AWAITING_UPDATE; - } - else - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Release not found in repo.\"}")); - } - else - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Release version not supplied.\"}")); - } - else { - server.send(err, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Error communicating with Github.\"}")); - } -} -void Web::handleNotFound(WebServer &server) { - HTTPMethod method = server.method(); - Serial.printf("Request %s 404-%d ", server.uri().c_str(), method); - switch (method) { - case HTTP_POST: - Serial.print("POST "); - break; - case HTTP_GET: - Serial.print("GET "); - break; - case HTTP_PUT: - Serial.print("PUT "); - break; - case HTTP_OPTIONS: - Serial.println("OPTIONS "); - server.send(200, "OK"); - return; - default: - Serial.print("["); - Serial.print(method); - Serial.print("]"); - break; - - } - snprintf(g_content, sizeof(g_content), "404 Service Not Found: %s", server.uri().c_str()); - server.send(404, _encoding_text, g_content); -} -void Web::handleReboot(WebServer &server) { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - if (method == HTTP_POST || method == HTTP_PUT) { - Serial.println("Rebooting ESP..."); - rebootDelay.reboot = true; - rebootDelay.rebootTime = millis() + 500; - server.send(200, "application/json", "{\"status\":\"OK\",\"desc\":\"Successfully started reboot\"}"); - } - else { - server.send(201, _encoding_json, "{\"status\":\"ERROR\",\"desc\":\"Invalid HTTP Method: \"}"); - } -} // ===================================================== -// Async API Handlers (port 8082) +// Async API Handlers // ===================================================== // Helper: get a query param as String, or empty if missing static String asyncParam(AsyncWebServerRequest *request, const char *name) { @@ -1855,33 +871,7 @@ void Web::handleNotFound(AsyncWebServerRequest *request) { void Web::begin() { Serial.println("Creating Web MicroServices..."); - server.enableCORS(true); - const char *keys[1] = {"apikey"}; - server.collectHeaders(keys, 1); - // API Server Handlers - apiServer.collectHeaders(keys, 1); - apiServer.enableCORS(true); - apiServer.on("/discovery", []() { webServer.handleDiscovery(apiServer); }); - apiServer.on("/rooms", []() {webServer.handleGetRooms(apiServer); }); - apiServer.on("/shades", []() { webServer.handleGetShades(apiServer); }); - apiServer.on("/groups", []() { webServer.handleGetGroups(apiServer); }); - apiServer.on("/login", []() { webServer.handleLogin(apiServer); }); - apiServer.onNotFound([]() { webServer.handleNotFound(apiServer); }); - apiServer.on("/controller", []() { webServer.handleController(apiServer); }); - apiServer.on("/shadeCommand", []() { webServer.handleShadeCommand(apiServer); }); - apiServer.on("/groupCommand", []() { webServer.handleGroupCommand(apiServer); }); - apiServer.on("/tiltCommand", []() { webServer.handleTiltCommand(apiServer); }); - apiServer.on("/repeatCommand", []() { webServer.handleRepeatCommand(apiServer); }); - apiServer.on("/room", HTTP_GET, [] () { webServer.handleRoom(apiServer); }); - apiServer.on("/shade", HTTP_GET, [] () { webServer.handleShade(apiServer); }); - apiServer.on("/group", HTTP_GET, [] () { webServer.handleGroup(apiServer); }); - apiServer.on("/setPositions", []() { webServer.handleSetPositions(apiServer); }); - apiServer.on("/setSensor", []() { webServer.handleSetSensor(apiServer); }); - apiServer.on("/downloadFirmware", []() { webServer.handleDownloadFirmware(apiServer); }); - apiServer.on("/backup", []() { webServer.handleBackup(apiServer); }); - apiServer.on("/reboot", []() { webServer.handleReboot(apiServer); }); - - // Async API Server (port 8082) + // Async API Server (port 8081) DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); DefaultHeaders::Instance().addHeader("Access-Control-Allow-Methods", "PUT,POST,GET,OPTIONS"); DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "*"); @@ -1927,133 +917,168 @@ void Web::begin() { }); // Web Interface - server.on("/tiltCommand", []() { webServer.handleTiltCommand(server); }); - server.on("/repeatCommand", []() { webServer.handleRepeatCommand(server); }); - server.on("/shadeCommand", []() { webServer.handleShadeCommand(server); }); - server.on("/groupCommand", []() { webServer.handleGroupCommand(server); }); - server.on("/setPositions", []() { webServer.handleSetPositions(server); }); - server.on("/setSensor", []() { webServer.handleSetSensor(server); }); - server.on("/upnp.xml", []() { SSDP.schema(server.client()); }); - server.on("/", []() { webServer.handleStreamFile(server, "/index.html", _encoding_html); }); - server.on("/login", []() { webServer.handleLogin(server); }); - server.on("/loginContext", []() { webServer.handleLoginContext(server); }); - server.on("/shades.cfg", []() { webServer.handleStreamFile(server, "/shades.cfg", _encoding_text); }); - server.on("/shades.tmp", []() { webServer.handleStreamFile(server, "/shades.tmp", _encoding_text); }); - server.on("/getReleases", []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } + + // Web Interface + // Command endpoints - delegate to async handler methods + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/tiltCommand", + [](AsyncWebServerRequest *request, JsonVariant &json) { webServer.handleTiltCommand(request, json); })); + asyncServer.on("/tiltCommand", HTTP_GET, [](AsyncWebServerRequest *request) { JsonVariant v; webServer.handleTiltCommand(request, v); }); + + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/repeatCommand", + [](AsyncWebServerRequest *request, JsonVariant &json) { webServer.handleRepeatCommand(request, json); })); + asyncServer.on("/repeatCommand", HTTP_GET, [](AsyncWebServerRequest *request) { JsonVariant v; webServer.handleRepeatCommand(request, v); }); + + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/shadeCommand", + [](AsyncWebServerRequest *request, JsonVariant &json) { webServer.handleShadeCommand(request, json); })); + asyncServer.on("/shadeCommand", HTTP_GET, [](AsyncWebServerRequest *request) { JsonVariant v; webServer.handleShadeCommand(request, v); }); + + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/groupCommand", + [](AsyncWebServerRequest *request, JsonVariant &json) { webServer.handleGroupCommand(request, json); })); + asyncServer.on("/groupCommand", HTTP_GET, [](AsyncWebServerRequest *request) { JsonVariant v; webServer.handleGroupCommand(request, v); }); + + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/setPositions", + [](AsyncWebServerRequest *request, JsonVariant &json) { webServer.handleSetPositions(request, json); })); + asyncServer.on("/setPositions", HTTP_GET, [](AsyncWebServerRequest *request) { JsonVariant v; webServer.handleSetPositions(request, v); }); + + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/setSensor", + [](AsyncWebServerRequest *request, JsonVariant &json) { webServer.handleSetSensor(request, json); })); + asyncServer.on("/setSensor", HTTP_GET, [](AsyncWebServerRequest *request) { JsonVariant v; webServer.handleSetSensor(request, v); }); + + asyncServer.on("/upnp.xml", HTTP_GET, [](AsyncWebServerRequest *request) { + AsyncResponseStream *response = request->beginResponseStream("text/xml"); + SSDP.schema(*response); + request->send(response); + }); + + // / and /loginContext are already handled by serveStatic and startup() + + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/login", + [](AsyncWebServerRequest *request, JsonVariant &json) { webServer.handleLogin(request, json); })); + asyncServer.on("/login", HTTP_GET, [](AsyncWebServerRequest *request) { JsonVariant v; webServer.handleLogin(request, v); }); + + asyncServer.on("/shades.cfg", HTTP_GET, [](AsyncWebServerRequest *request) { + request->send(LittleFS, "/shades.cfg", _encoding_text); + }); + asyncServer.on("/shades.tmp", HTTP_GET, [](AsyncWebServerRequest *request) { + request->send(LittleFS, "/shades.tmp", _encoding_text); + }); + + asyncServer.on("/getReleases", HTTP_GET, [](AsyncWebServerRequest *request) { GitRepo repo; repo.getReleases(); git.setCurrentRelease(repo); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); resp.beginObject(); - repo.toJSON(resp); + // Inline GitRepo::toJSON + resp.beginObject("fwVersion"); + serializeAppVersion(resp, settings.fwVersion); + resp.endObject(); + resp.beginObject("appVersion"); + serializeAppVersion(resp, settings.appVersion); + resp.endObject(); + resp.beginArray("releases"); + for(uint8_t i = 0; i < GIT_MAX_RELEASES + 1; i++) { + if(repo.releases[i].id == 0) continue; + resp.beginObject(); + serializeGitRelease(&repo.releases[i], resp); + resp.endObject(); + } + resp.endArray(); resp.endObject(); resp.endResponse(); }); - server.on("/downloadFirmware", []() { webServer.handleDownloadFirmware(server); }); - server.on("/cancelFirmware", []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - // If we are currently downloading the filesystem we cannot cancel. + + asyncServer.on("/downloadFirmware", HTTP_GET, [](AsyncWebServerRequest *request) { webServer.handleDownloadFirmware(request); }); + + asyncServer.on("/cancelFirmware", HTTP_GET, [](AsyncWebServerRequest *request) { if(!git.lockFS) { git.status = GIT_UPDATE_CANCELLING; - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); resp.beginObject(); - git.toJSON(resp); + serializeGitVersion(resp); resp.endObject(); resp.endResponse(); git.cancelled = true; } else { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Cannot cancel during filesystem update.\"}")); + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Cannot cancel during filesystem update.\"}")); } }); - server.on("/backup", []() { webServer.handleBackup(server, true); }); - server.on("/restore", HTTP_POST, []() { - webServer.sendCORSHeaders(server); - server.sendHeader("Connection", "close"); - if(webServer.uploadSuccess) { - server.send(200, _encoding_json, "{\"status\":\"Success\",\"desc\":\"Restoring Shade settings\"}"); - restore_options_t opts; - if(server.hasArg("data")) { - Serial.println(server.arg("data")); - StaticJsonDocument<256> doc; - DeserializationError err = deserializeJson(doc, server.arg("data")); - if (err) { - webServer.handleDeserializationError(server, err); - return; + + asyncServer.on("/backup", HTTP_GET, [](AsyncWebServerRequest *request) { webServer.handleBackup(request); }); + + asyncServer.on("/restore", HTTP_POST, + [](AsyncWebServerRequest *request) { + if(webServer.uploadSuccess) { + request->send(200, _encoding_json, "{\"status\":\"Success\",\"desc\":\"Restoring Shade settings\"}"); + restore_options_t opts; + if(asyncHasParam(request, "data")) { + String dataStr = asyncParam(request, "data"); + Serial.println(dataStr); + StaticJsonDocument<256> doc; + DeserializationError err = deserializeJson(doc, dataStr); + if(err) { + request->send(500, "application/json", "{\"status\":\"ERROR\",\"desc\":\"JSON parse error\"}"); + return; + } + else { + JsonObject obj = doc.as(); + opts.fromJSON(obj); + } } else { - JsonObject obj = doc.as(); - opts.fromJSON(obj); + Serial.println("No restore options sent. Using defaults..."); + opts.shades = true; } + ShadeConfigFile::restore(&somfy, "/shades.tmp", opts); + Serial.println("Rebooting ESP for restored settings..."); + rebootDelay.reboot = true; + rebootDelay.rebootTime = millis() + 1000; } - else { - Serial.println("No restore options sent. Using defaults..."); - opts.shades = true; - } - ShadeConfigFile::restore(&somfy, "/shades.tmp", opts); - Serial.println("Rebooting ESP for restored settings..."); - rebootDelay.reboot = true; - rebootDelay.rebootTime = millis() + 1000; - } - }, []() { + }, + [](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { esp_task_wdt_reset(); - HTTPUpload& upload = server.upload(); - if (upload.status == UPLOAD_FILE_START) { + if(index == 0) { webServer.uploadSuccess = false; - Serial.printf("Restore: %s\n", upload.filename.c_str()); - // Begin by opening a new temporary file. + Serial.printf("Restore: %s\n", filename.c_str()); File fup = LittleFS.open("/shades.tmp", "w"); fup.close(); } - else if (upload.status == UPLOAD_FILE_WRITE) { + if(len > 0) { File fup = LittleFS.open("/shades.tmp", "a"); - //upload.buf[upload.currentSize] = 0x00; - //Serial.print((char *)upload.buf); - fup.write(upload.buf, upload.currentSize); + fup.write(data, len); fup.close(); } - else if (upload.status == UPLOAD_FILE_END) { + if(final) { webServer.uploadSuccess = true; } - }); - server.on("/index.js", []() { webServer.sendCacheHeaders(604800); webServer.handleStreamFile(server, "/index.js", "text/javascript"); }); - server.on("/main.css", []() { webServer.sendCacheHeaders(604800); webServer.handleStreamFile(server, "/main.css", "text/css"); }); - server.on("/widgets.css", []() { webServer.sendCacheHeaders(604800); webServer.handleStreamFile(server, "/widgets.css", "text/css"); }); - server.on("/icons.css", []() { webServer.sendCacheHeaders(604800); webServer.handleStreamFile(server, "/icons.css", "text/css"); }); - server.on("/favicon.png", []() { webServer.sendCacheHeaders(604800); webServer.handleStreamFile(server, "/favicon.png", "image/png"); }); - server.on("/icon.png", []() { webServer.sendCacheHeaders(604800); webServer.handleStreamFile(server, "/icon.png", "image/png"); }); - server.on("/icon.svg", []() { webServer.sendCacheHeaders(604800); webServer.handleStreamFile(server, "/icon.svg", "image/svg+xml"); }); - server.on("/apple-icon.png", []() { webServer.sendCacheHeaders(604800); webServer.handleStreamFile(server, "/apple-icon.png", "image/png"); }); - server.onNotFound([]() { webServer.handleNotFound(server); }); - server.on("/controller", []() { webServer.handleController(server); }); - server.on("/rooms", []() { webServer.handleGetRooms(server); }); - server.on("/shades", []() { webServer.handleGetShades(server); }); - server.on("/groups", []() { webServer.handleGetGroups(server); }); - server.on("/room", []() { webServer.handleRoom(server); }); - server.on("/shade", []() { webServer.handleShade(server); }); - server.on("/group", []() { webServer.handleGroup(server); }); - server.on("/getNextRoom", []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); + + // Static file routes removed - handled by serveStatic in startup() + + asyncServer.on("/controller", WebRequestMethodComposite(HTTP_GET) | HTTP_POST, [](AsyncWebServerRequest *request) { webServer.handleController(request); }); + asyncServer.on("/rooms", WebRequestMethodComposite(HTTP_GET) | HTTP_POST, [](AsyncWebServerRequest *request) { webServer.handleGetRooms(request); }); + asyncServer.on("/shades", WebRequestMethodComposite(HTTP_GET) | HTTP_POST, [](AsyncWebServerRequest *request) { webServer.handleGetShades(request); }); + asyncServer.on("/groups", WebRequestMethodComposite(HTTP_GET) | HTTP_POST, [](AsyncWebServerRequest *request) { webServer.handleGetGroups(request); }); + asyncServer.on("/room", WebRequestMethodComposite(HTTP_GET) | HTTP_POST, [](AsyncWebServerRequest *request) { webServer.handleRoom(request); }); + asyncServer.on("/shade", WebRequestMethodComposite(HTTP_GET) | HTTP_POST, [](AsyncWebServerRequest *request) { webServer.handleShade(request); }); + asyncServer.on("/group", WebRequestMethodComposite(HTTP_GET) | HTTP_POST, [](AsyncWebServerRequest *request) { webServer.handleGroup(request); }); + + asyncServer.on("/getNextRoom", HTTP_GET, [](AsyncWebServerRequest *request) { + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); resp.beginObject(); resp.addElem("roomId", somfy.getNextRoomId()); resp.endObject(); resp.endResponse(); }); - server.on("/getNextShade", []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } + + asyncServer.on("/getNextShade", HTTP_GET, [](AsyncWebServerRequest *request) { uint8_t shadeId = somfy.getNextShadeId(); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); resp.beginObject(); resp.addElem("shadeId", shadeId); resp.addElem("remoteAddress", (uint32_t)somfy.getNextRemoteAddress(shadeId)); @@ -2062,12 +1087,12 @@ void Web::begin() { resp.addElem("proto", static_cast(somfy.transceiver.config.proto)); resp.endObject(); resp.endResponse(); - }); - server.on("/getNextGroup", []() { - webServer.sendCORSHeaders(server); + }); + + asyncServer.on("/getNextGroup", HTTP_GET, [](AsyncWebServerRequest *request) { uint8_t groupId = somfy.getNextGroupId(); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); resp.beginObject(); resp.addElem("groupId", groupId); resp.addElem("remoteAddress", (uint32_t)somfy.getNextRemoteAddress(groupId)); @@ -2075,443 +1100,355 @@ void Web::begin() { resp.addElem("proto", static_cast(somfy.transceiver.config.proto)); resp.endObject(); resp.endResponse(); - }); - server.on("/addRoom", []() { - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - SomfyRoom * room = nullptr; - if (method == HTTP_POST || method == HTTP_PUT) { + }); + + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/addRoom", + [](AsyncWebServerRequest *request, JsonVariant &json) { + if(json.isNull()) { request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No room object supplied.\"}")); return; } Serial.println("Adding a room"); - DynamicJsonDocument doc(512); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - webServer.handleDeserializationError(server, err); + JsonObject obj = json.as(); + if(somfy.roomCount() > SOMFY_MAX_ROOMS) { + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Maximum number of rooms exceeded.\"}")); return; } - else { - JsonObject obj = doc.as(); - Serial.println("Counting rooms"); - if (somfy.roomCount() > SOMFY_MAX_ROOMS) { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Maximum number of rooms exceeded.\"}")); - return; - } - else { - Serial.println("Adding room"); - room = somfy.addRoom(obj); - if (!room) { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Error adding room.\"}")); - return; - } - } + SomfyRoom *room = somfy.addRoom(obj); + if(room) { + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginObject(); + serializeRoom(room, resp); + resp.endObject(); + resp.endResponse(); } - } - if (room) { - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginObject(); - room->toJSON(resp); - resp.endObject(); - resp.endResponse(); - } - else { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Error saving Somfy Room.\"}")); - } - }); - server.on("/addShade", []() { - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - SomfyShade* shade = nullptr; - if (method == HTTP_POST || method == HTTP_PUT) { + else { + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Error adding room.\"}")); + } + })); + + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/addShade", + [](AsyncWebServerRequest *request, JsonVariant &json) { + if(json.isNull()) { request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade object supplied.\"}")); return; } Serial.println("Adding a shade"); - DynamicJsonDocument doc(1024); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - webServer.handleDeserializationError(server, err); + JsonObject obj = json.as(); + if(somfy.shadeCount() > SOMFY_MAX_SHADES) { + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Maximum number of shades exceeded.\"}")); return; } - else { - JsonObject obj = doc.as(); - Serial.println("Counting shades"); - if (somfy.shadeCount() > SOMFY_MAX_SHADES) { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Maximum number of shades exceeded.\"}")); - return; - } - else { - Serial.println("Adding shade"); - shade = somfy.addShade(obj); - if (!shade) { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Error adding shade.\"}")); - return; - } - } + SomfyShade *shade = somfy.addShade(obj); + if(shade) { + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginObject(); + serializeShade(shade, resp); + resp.endObject(); + resp.endResponse(); } - } - if (shade) { - //Serial.println("Serializing shade"); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginObject(); - shade->toJSON(resp); - resp.endObject(); - resp.endResponse(); - } - else { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Error saving Somfy Shade.\"}")); - } - }); - server.on("/addGroup", []() { - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - SomfyGroup * group = nullptr; - if (method == HTTP_POST || method == HTTP_PUT) { + else { + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Error adding shade.\"}")); + } + })); + + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/addGroup", + [](AsyncWebServerRequest *request, JsonVariant &json) { + if(json.isNull()) { request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No group object supplied.\"}")); return; } Serial.println("Adding a group"); - DynamicJsonDocument doc(512); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - webServer.handleDeserializationError(server, err); + JsonObject obj = json.as(); + if(somfy.groupCount() > SOMFY_MAX_GROUPS) { + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Maximum number of groups exceeded.\"}")); return; } + SomfyGroup *group = somfy.addGroup(obj); + if(group) { + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginObject(); + serializeGroup(group, resp); + resp.endObject(); + resp.endResponse(); + } else { - JsonObject obj = doc.as(); - Serial.println("Counting shades"); - if (somfy.groupCount() > SOMFY_MAX_GROUPS) { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Maximum number of groups exceeded.\"}")); - return; - } - else { - Serial.println("Adding group"); - group = somfy.addGroup(obj); - if (!group) { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Error adding group.\"}")); - return; + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Error adding group.\"}")); + } + })); + + asyncServer.on("/groupOptions", HTTP_GET, [](AsyncWebServerRequest *request) { + if(asyncHasParam(request, "groupId")) { + int groupId = atoi(asyncParam(request, "groupId").c_str()); + SomfyGroup* group = somfy.getGroupById(groupId); + if(group) { + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginObject(); + serializeGroup(group, resp); + resp.beginArray("availShades"); + for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) { + SomfyShade *shade = &somfy.shades[i]; + if(shade->getShadeId() != 255) { + bool isLinked = false; + for(uint8_t j = 0; j < SOMFY_MAX_GROUPED_SHADES; j++) { + if(group->linkedShades[j] == shade->getShadeId()) { + isLinked = true; + break; + } + } + if(!isLinked) { + resp.beginObject(); + serializeShadeRef(shade, resp); + resp.endObject(); + } } } + resp.endArray(); + resp.endObject(); + resp.endResponse(); } - } - if (group) { - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginObject(); - group->toJSON(resp); - resp.endObject(); - resp.endResponse(); + else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Group Id not found.\"}")); } else { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Error saving Somfy Group.\"}")); - } - }); - server.on("/groupOptions", []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - if (method == HTTP_GET || method == HTTP_POST) { - if (server.hasArg("groupId")) { - int groupId = atoi(server.arg("groupId").c_str()); - SomfyGroup* group = somfy.getGroupById(groupId); - if (group) { - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginObject(); - group->toJSON(resp); - resp.beginArray("availShades"); - for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) { - SomfyShade *shade = &somfy.shades[i]; - if(shade->getShadeId() != 255) { - bool isLinked = false; - for(uint8_t j = 0; j < SOMFY_MAX_GROUPED_SHADES; j++) { - if(group->linkedShades[j] == shade->getShadeId()) { - isLinked = true; - break; - } - } - if(!isLinked) { - resp.beginObject(); - shade->toJSONRef(resp); - resp.endObject(); - } - } - } - resp.endArray(); - resp.endObject(); - resp.endResponse(); - } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Group Id not found.\"}")); - } - else { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"You must supply a valid group id.\"}")); - } - } - - }); - server.on("/saveRoom", []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - if (method == HTTP_PUT || method == HTTP_POST) { - // We are updating an existing room. - if (server.hasArg("plain")) { - Serial.println("Updating a room"); - DynamicJsonDocument doc(512); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - webServer.handleDeserializationError(server, err); - return; - } - else { - JsonObject obj = doc.as(); - if (obj.containsKey("roomId")) { - SomfyRoom* room = somfy.getRoomById(obj["roomId"]); - if (room) { - room->fromJSON(obj); - room->save(); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginObject(); - room->toJSON(resp); - resp.endObject(); - resp.endResponse(); - } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Room Id not found.\"}")); - } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No room id was supplied.\"}")); - } - } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No room object supplied.\"}")); + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"You must supply a valid group id.\"}")); } }); - server.on("/saveShade", []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - if (method == HTTP_PUT || method == HTTP_POST) { - // We are updating an existing shade. - if (server.hasArg("plain")) { - Serial.println("Updating a shade"); - DynamicJsonDocument doc(1024); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - webServer.handleDeserializationError(server, err); - return; + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/saveRoom", + [](AsyncWebServerRequest *request, JsonVariant &json) { + if(json.isNull()) { request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No room object supplied.\"}")); return; } + Serial.println("Updating a room"); + JsonObject obj = json.as(); + if(obj.containsKey("roomId")) { + SomfyRoom* room = somfy.getRoomById(obj["roomId"]); + if(room) { + room->fromJSON(obj); + room->save(); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginObject(); + serializeRoom(room, resp); + resp.endObject(); + resp.endResponse(); } - else { - JsonObject obj = doc.as(); - if (obj.containsKey("shadeId")) { - SomfyShade* shade = somfy.getShadeById(obj["shadeId"]); - if (shade) { - int8_t err = shade->fromJSON(obj); - if(err == 0) { - shade->save(); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginObject(); - shade->toJSON(resp); - resp.endObject(); - resp.endResponse(); - } - else { - snprintf(g_content, sizeof(g_content), "{\"status\":\"DATA\",\"desc\":\"Data Error.\", \"code\":%d}", err); - server.send(500, _encoding_json, g_content); - } - } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade Id not found.\"}")); + else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Room Id not found.\"}")); + } + else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No room id was supplied.\"}")); + })); + + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/saveShade", + [](AsyncWebServerRequest *request, JsonVariant &json) { + if(json.isNull()) { request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade object supplied.\"}")); return; } + Serial.println("Updating a shade"); + JsonObject obj = json.as(); + if(obj.containsKey("shadeId")) { + SomfyShade* shade = somfy.getShadeById(obj["shadeId"]); + if(shade) { + int8_t err = shade->fromJSON(obj); + if(err == 0) { + shade->save(); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginObject(); + serializeShade(shade, resp); + resp.endObject(); + resp.endResponse(); } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade id was supplied.\"}")); - } - } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade object supplied.\"}")); - } - }); - server.on("/saveGroup", []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - if (method == HTTP_PUT || method == HTTP_POST) { - // We are updating an existing shade. - if (server.hasArg("plain")) { - Serial.println("Updating a group"); - DynamicJsonDocument doc(512); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - webServer.handleDeserializationError(server, err); - return; - } - else { - JsonObject obj = doc.as(); - if (obj.containsKey("groupId")) { - SomfyGroup* group = somfy.getGroupById(obj["groupId"]); - if (group) { - group->fromJSON(obj); - group->save(); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginObject(); - group->toJSON(resp); - resp.endObject(); - resp.endResponse(); - } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Group Id not found.\"}")); + else { + snprintf(g_async_content, sizeof(g_async_content), "{\"status\":\"DATA\",\"desc\":\"Data Error.\", \"code\":%d}", err); + request->send(500, _encoding_json, g_async_content); } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No group id was supplied.\"}")); } + else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade Id not found.\"}")); } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No group object supplied.\"}")); - } - }); - server.on("/setMyPosition", []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - uint8_t shadeId = 255; - int8_t pos = -1; - int8_t tilt = -1; - if (method == HTTP_GET || method == HTTP_PUT || method == HTTP_POST) { - if (server.hasArg("shadeId")) { - shadeId = atoi(server.arg("shadeId").c_str()); - if(server.hasArg("pos")) pos = atoi(server.arg("pos").c_str()); - if(server.hasArg("tilt")) tilt = atoi(server.arg("tilt").c_str()); - } - else if (server.hasArg("plain")) { - DynamicJsonDocument doc(256); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - webServer.handleDeserializationError(server, err); - return; - } - else { - JsonObject obj = doc.as(); - if (obj.containsKey("shadeId")) shadeId = obj["shadeId"]; - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade id was supplied.\"}")); - if(obj.containsKey("pos")) pos = obj["pos"].as(); - if(obj.containsKey("tilt")) tilt = obj["tilt"].as(); + else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade id was supplied.\"}")); + })); + + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/saveGroup", + [](AsyncWebServerRequest *request, JsonVariant &json) { + if(json.isNull()) { request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No group object supplied.\"}")); return; } + Serial.println("Updating a group"); + JsonObject obj = json.as(); + if(obj.containsKey("groupId")) { + SomfyGroup* group = somfy.getGroupById(obj["groupId"]); + if(group) { + group->fromJSON(obj); + group->save(); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginObject(); + serializeGroup(group, resp); + resp.endObject(); + resp.endResponse(); } + else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Group Id not found.\"}")); + } + else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No group id was supplied.\"}")); + })); + + // setMyPosition - supports both GET with query params and POST/PUT with JSON body + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/setMyPosition", + [](AsyncWebServerRequest *request, JsonVariant &json) { + uint8_t shadeId = 255; + int8_t pos = -1; + int8_t tilt = -1; + if(!json.isNull()) { + JsonObject obj = json.as(); + if(obj.containsKey("shadeId")) shadeId = obj["shadeId"]; + else { request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade id was supplied.\"}")); return; } + if(obj.containsKey("pos")) pos = obj["pos"].as(); + if(obj.containsKey("tilt")) tilt = obj["tilt"].as(); } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade object supplied.\"}")); SomfyShade* shade = somfy.getShadeById(shadeId); - if (shade) { - // Send the command to the shade. + if(shade) { if(tilt < 0) tilt = shade->myPos; if(shade->tiltType == tilt_types::none) tilt = -1; if(pos >= 0 && pos <= 100) shade->setMyPosition(shade->transformPosition(pos), shade->transformPosition(tilt)); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginObject(); - shade->toJSONRef(resp); - resp.endObject(); - resp.endResponse(); - } - else { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade with the specified id not found.\"}")); - } - } - else - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid Http method\"}")); - }); - server.on("/setRollingCode", []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - if (method == HTTP_PUT || method == HTTP_POST) { - uint8_t shadeId = 255; - uint16_t rollingCode = 0; - if (server.hasArg("plain")) { - // Its coming in the body. - StaticJsonDocument<129> doc; - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - webServer.handleDeserializationError(server, err); - return; - } - else { - JsonObject obj = doc.as(); - if (obj.containsKey("shadeId")) shadeId = obj["shadeId"]; - if(obj.containsKey("rollingCode")) rollingCode = obj["rollingCode"]; - } - } - else if (server.hasArg("shadeId")) { - shadeId = atoi(server.arg("shadeId").c_str()); - rollingCode = atoi(server.arg("rollingCode").c_str()); - } - SomfyShade* shade = nullptr; - if (shadeId != 255) shade = somfy.getShadeById(shadeId); - if (!shade) { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade not found to set rolling code\"}")); - } - else { - shade->setRollingCode(rollingCode); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); resp.beginObject(); - shade->toJSON(resp); + serializeShadeRef(shade, resp); resp.endObject(); resp.endResponse(); } - } - }); - server.on("/setPaired", []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - uint8_t shadeId = 255; - bool paired = false; - if(server.hasArg("plain")) { - DynamicJsonDocument doc(512); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if(err) { - webServer.handleDeserializationError(server, err); - return; - } else { - JsonObject obj = doc.as(); - if (obj.containsKey("shadeId")) shadeId = obj["shadeId"]; - if(obj.containsKey("paired")) paired = obj["paired"]; + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade with the specified id not found.\"}")); } + })); + asyncServer.on("/setMyPosition", HTTP_GET, [](AsyncWebServerRequest *request) { + uint8_t shadeId = 255; + int8_t pos = -1; + int8_t tilt = -1; + if(asyncHasParam(request, "shadeId")) { + shadeId = atoi(asyncParam(request, "shadeId").c_str()); + if(asyncHasParam(request, "pos")) pos = atoi(asyncParam(request, "pos").c_str()); + if(asyncHasParam(request, "tilt")) tilt = atoi(asyncParam(request, "tilt").c_str()); } - else if (server.hasArg("shadeId")) - shadeId = atoi(server.arg("shadeId").c_str()); - if(server.hasArg("paired")) - paired = toBoolean(server.arg("paired").c_str(), false); - SomfyShade* shade = nullptr; - if (shadeId != 255) shade = somfy.getShadeById(shadeId); - if (!shade) { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade not found to pair\"}")); + else { request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade object supplied.\"}")); return; } + SomfyShade* shade = somfy.getShadeById(shadeId); + if(shade) { + if(tilt < 0) tilt = shade->myPos; + if(shade->tiltType == tilt_types::none) tilt = -1; + if(pos >= 0 && pos <= 100) + shade->setMyPosition(shade->transformPosition(pos), shade->transformPosition(tilt)); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginObject(); + serializeShadeRef(shade, resp); + resp.endObject(); + resp.endResponse(); } else { - shade->paired = paired; - shade->save(); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade with the specified id not found.\"}")); + } + }); + + // setRollingCode - supports both query params and JSON body + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/setRollingCode", + [](AsyncWebServerRequest *request, JsonVariant &json) { + uint8_t shadeId = 255; + uint16_t rollingCode = 0; + if(!json.isNull()) { + JsonObject obj = json.as(); + if(obj.containsKey("shadeId")) shadeId = obj["shadeId"]; + if(obj.containsKey("rollingCode")) rollingCode = obj["rollingCode"]; + } + SomfyShade* shade = nullptr; + if(shadeId != 255) shade = somfy.getShadeById(shadeId); + if(!shade) { + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade not found to set rolling code\"}")); + } + else { + shade->setRollingCode(rollingCode); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginObject(); + serializeShade(shade, resp); + resp.endObject(); + resp.endResponse(); + } + })); + asyncServer.on("/setRollingCode", HTTP_GET, [](AsyncWebServerRequest *request) { + uint8_t shadeId = 255; + uint16_t rollingCode = 0; + if(asyncHasParam(request, "shadeId")) { + shadeId = atoi(asyncParam(request, "shadeId").c_str()); + rollingCode = atoi(asyncParam(request, "rollingCode").c_str()); + } + SomfyShade* shade = nullptr; + if(shadeId != 255) shade = somfy.getShadeById(shadeId); + if(!shade) { + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade not found to set rolling code\"}")); + } + else { + shade->setRollingCode(rollingCode); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); resp.beginObject(); - shade->toJSON(resp); + serializeShade(shade, resp); resp.endObject(); resp.endResponse(); } }); - server.on("/unpairShade", []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - if (method == HTTP_PUT || method == HTTP_POST) { + + // setPaired - supports both query params and JSON body + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/setPaired", + [](AsyncWebServerRequest *request, JsonVariant &json) { uint8_t shadeId = 255; - if (server.hasArg("plain")) { - // Its coming in the body. - DynamicJsonDocument doc(512); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - webServer.handleDeserializationError(server, err); - return; - } - else { - JsonObject obj = doc.as(); - if (obj.containsKey("shadeId")) shadeId = obj["shadeId"]; - } + bool paired = false; + if(!json.isNull()) { + JsonObject obj = json.as(); + if(obj.containsKey("shadeId")) shadeId = obj["shadeId"]; + if(obj.containsKey("paired")) paired = obj["paired"]; } - else if (server.hasArg("shadeId")) - shadeId = atoi(server.arg("shadeId").c_str()); SomfyShade* shade = nullptr; - if (shadeId != 255) shade = somfy.getShadeById(shadeId); - if (!shade) { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade not found to unpair\"}")); + if(shadeId != 255) shade = somfy.getShadeById(shadeId); + if(!shade) { + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade not found to pair\"}")); + } + else { + shade->paired = paired; + shade->save(); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginObject(); + serializeShade(shade, resp); + resp.endObject(); + resp.endResponse(); + } + })); + asyncServer.on("/setPaired", HTTP_GET, [](AsyncWebServerRequest *request) { + uint8_t shadeId = 255; + bool paired = false; + if(asyncHasParam(request, "shadeId")) + shadeId = atoi(asyncParam(request, "shadeId").c_str()); + if(asyncHasParam(request, "paired")) + paired = toBoolean(asyncParam(request, "paired").c_str(), false); + SomfyShade* shade = nullptr; + if(shadeId != 255) shade = somfy.getShadeById(shadeId); + if(!shade) { + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade not found to pair\"}")); + } + else { + shade->paired = paired; + shade->save(); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginObject(); + serializeShade(shade, resp); + resp.endObject(); + resp.endResponse(); + } + }); + + // unpairShade + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/unpairShade", + [](AsyncWebServerRequest *request, JsonVariant &json) { + uint8_t shadeId = 255; + if(!json.isNull()) { + JsonObject obj = json.as(); + if(obj.containsKey("shadeId")) shadeId = obj["shadeId"]; + } + SomfyShade* shade = nullptr; + if(shadeId != 255) shade = somfy.getShadeById(shadeId); + if(!shade) { + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade not found to unpair\"}")); } else { if(shade->bitLength == 56) @@ -2520,408 +1457,376 @@ void Web::begin() { shade->sendCommand(somfy_commands::Prog, 1); shade->paired = false; shade->save(); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); resp.beginObject(); - shade->toJSON(resp); + serializeShade(shade, resp); resp.endObject(); resp.endResponse(); } + })); + asyncServer.on("/unpairShade", HTTP_GET, [](AsyncWebServerRequest *request) { + uint8_t shadeId = 255; + if(asyncHasParam(request, "shadeId")) + shadeId = atoi(asyncParam(request, "shadeId").c_str()); + SomfyShade* shade = nullptr; + if(shadeId != 255) shade = somfy.getShadeById(shadeId); + if(!shade) { + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade not found to unpair\"}")); } - }); - server.on("/linkRepeater", []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - if (method == HTTP_PUT || method == HTTP_POST) { - // We are adding a linked repeater. + else { + if(shade->bitLength == 56) + shade->sendCommand(somfy_commands::Prog, 7); + else + shade->sendCommand(somfy_commands::Prog, 1); + shade->paired = false; + shade->save(); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginObject(); + serializeShade(shade, resp); + resp.endObject(); + resp.endResponse(); + } + }); + + // linkRepeater + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/linkRepeater", + [](AsyncWebServerRequest *request, JsonVariant &json) { uint32_t address = 0; - if (server.hasArg("plain")) { + if(!json.isNull()) { Serial.println("Linking a repeater"); - DynamicJsonDocument doc(512); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - webServer.handleDeserializationError(server, err); - return; - } - else { - JsonObject obj = doc.as(); - if (obj.containsKey("address")) address = obj["address"]; - else if(obj.containsKey("remoteAddress")) address = obj["remoteAddress"]; - } + JsonObject obj = json.as(); + if(obj.containsKey("address")) address = obj["address"]; + else if(obj.containsKey("remoteAddress")) address = obj["remoteAddress"]; + } + if(address == 0) { + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No repeater address was supplied.\"}")); } - else if(server.hasArg("address")) - address = atoi(server.arg("address").c_str()); - if(address == 0) - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No repeater address was supplied.\"}")); else { somfy.linkRepeater(address); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); resp.beginArray(); - somfy.toJSONRepeaters(resp); + serializeRepeaters(resp); resp.endArray(); resp.endResponse(); } + })); + asyncServer.on("/linkRepeater", HTTP_GET, [](AsyncWebServerRequest *request) { + uint32_t address = 0; + if(asyncHasParam(request, "address")) + address = atoi(asyncParam(request, "address").c_str()); + if(address == 0) { + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No repeater address was supplied.\"}")); + } + else { + somfy.linkRepeater(address); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginArray(); + serializeRepeaters(resp); + resp.endArray(); + resp.endResponse(); } }); - server.on("/unlinkRepeater", []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - if (method == HTTP_PUT || method == HTTP_POST) { - // We are adding a linked repeater. + + // unlinkRepeater + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/unlinkRepeater", + [](AsyncWebServerRequest *request, JsonVariant &json) { uint32_t address = 0; - if (server.hasArg("plain")) { + if(!json.isNull()) { Serial.println("Unlinking a repeater"); - DynamicJsonDocument doc(512); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - webServer.handleDeserializationError(server, err); - return; - } - else { - JsonObject obj = doc.as(); - if (obj.containsKey("address")) address = obj["address"]; - else if(obj.containsKey("remoteAddress")) address = obj["remoteAddress"]; - } + JsonObject obj = json.as(); + if(obj.containsKey("address")) address = obj["address"]; + else if(obj.containsKey("remoteAddress")) address = obj["remoteAddress"]; + } + if(address == 0) { + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No repeater address was supplied.\"}")); } - else if(server.hasArg("address")) - address = atoi(server.arg("address").c_str()); - if(address == 0) - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No repeater address was supplied.\"}")); else { somfy.unlinkRepeater(address); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); resp.beginArray(); - somfy.toJSONRepeaters(resp); + serializeRepeaters(resp); resp.endArray(); resp.endResponse(); } + })); + asyncServer.on("/unlinkRepeater", HTTP_GET, [](AsyncWebServerRequest *request) { + uint32_t address = 0; + if(asyncHasParam(request, "address")) + address = atoi(asyncParam(request, "address").c_str()); + if(address == 0) { + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No repeater address was supplied.\"}")); + } + else { + somfy.unlinkRepeater(address); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginArray(); + serializeRepeaters(resp); + resp.endArray(); + resp.endResponse(); } }); - - server.on("/unlinkRemote", []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - if (method == HTTP_PUT || method == HTTP_POST) { - // We are updating an existing shade by adding a linked remote. - if (server.hasArg("plain")) { - DynamicJsonDocument doc(512); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - webServer.handleDeserializationError(server, err); - return; - } - else { - JsonObject obj = doc.as(); - if (obj.containsKey("shadeId")) { - SomfyShade* shade = somfy.getShadeById(obj["shadeId"]); - if (shade) { - if (obj.containsKey("remoteAddress")) { - shade->unlinkRemote(obj["remoteAddress"]); - } - else { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Remote address not provided.\"}")); - } - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginObject(); - shade->toJSON(resp); - resp.endObject(); - resp.endResponse(); - } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade Id not found.\"}")); + + // unlinkRemote + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/unlinkRemote", + [](AsyncWebServerRequest *request, JsonVariant &json) { + if(json.isNull()) { request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No remote object supplied.\"}")); return; } + JsonObject obj = json.as(); + if(obj.containsKey("shadeId")) { + SomfyShade* shade = somfy.getShadeById(obj["shadeId"]); + if(shade) { + if(obj.containsKey("remoteAddress")) { + shade->unlinkRemote(obj["remoteAddress"]); } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade id was supplied.\"}")); - } - } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No remote object supplied.\"}")); - } - }); - server.on("/linkRemote", []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - if (method == HTTP_PUT || method == HTTP_POST) { - // We are updating an existing shade by adding a linked remote. - if (server.hasArg("plain")) { - Serial.println("Linking a remote"); - DynamicJsonDocument doc(512); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - webServer.handleDeserializationError(server, err); - return; - } - else { - JsonObject obj = doc.as(); - if (obj.containsKey("shadeId")) { - SomfyShade* shade = somfy.getShadeById(obj["shadeId"]); - if (shade) { - if (obj.containsKey("remoteAddress")) { - if (obj.containsKey("rollingCode")) shade->linkRemote(obj["remoteAddress"], obj["rollingCode"]); - else shade->linkRemote(obj["remoteAddress"]); - } - else { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Remote address not provided.\"}")); - } - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginObject(); - shade->toJSON(resp); - resp.endObject(); - resp.endResponse(); - } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade Id not found.\"}")); + else { + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Remote address not provided.\"}")); } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade id was supplied.\"}")); - } - } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No remote object supplied.\"}")); - } - }); - server.on("/linkToGroup", []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - if (method == HTTP_PUT || method == HTTP_POST) { - if (server.hasArg("plain")) { - Serial.println("Linking a shade to a group"); - DynamicJsonDocument doc(512); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - webServer.handleDeserializationError(server, err); - return; - } - else { - JsonObject obj = doc.as(); - uint8_t shadeId = obj.containsKey("shadeId") ? obj["shadeId"] : 0; - uint8_t groupId = obj.containsKey("groupId") ? obj["groupId"] : 0; - if(groupId == 0) { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Group id not provided.\"}")); - return; - } - if(shadeId == 0) { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade id not provided.\"}")); - return; - } - SomfyGroup * group = somfy.getGroupById(groupId); - if(!group) { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Group id not found.\"}")); - return; - } - SomfyShade * shade = somfy.getShadeById(shadeId); - if(!shade) { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade id not found.\"}")); - return; - } - group->linkShade(shadeId); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); resp.beginObject(); - group->toJSON(resp); + serializeShade(shade, resp); resp.endObject(); resp.endResponse(); } + else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade Id not found.\"}")); } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No linking object supplied.\"}")); - } - }); - server.on("/unlinkFromGroup", []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - if (method == HTTP_PUT || method == HTTP_POST) { - if (server.hasArg("plain")) { - Serial.println("Unlinking a shade from a group"); - DynamicJsonDocument doc(512); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - switch (err.code()) { - case DeserializationError::InvalidInput: - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid JSON payload\"}")); - break; - case DeserializationError::NoMemory: - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Out of memory parsing JSON\"}")); - break; - default: - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"General JSON Deserialization failed\"}")); - break; + else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade id was supplied.\"}")); + })); + + // linkRemote + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/linkRemote", + [](AsyncWebServerRequest *request, JsonVariant &json) { + if(json.isNull()) { request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No remote object supplied.\"}")); return; } + Serial.println("Linking a remote"); + JsonObject obj = json.as(); + if(obj.containsKey("shadeId")) { + SomfyShade* shade = somfy.getShadeById(obj["shadeId"]); + if(shade) { + if(obj.containsKey("remoteAddress")) { + if(obj.containsKey("rollingCode")) shade->linkRemote(obj["remoteAddress"], obj["rollingCode"]); + else shade->linkRemote(obj["remoteAddress"]); } - } - else { - JsonObject obj = doc.as(); - uint8_t shadeId = obj.containsKey("shadeId") ? obj["shadeId"] : 0; - uint8_t groupId = obj.containsKey("groupId") ? obj["groupId"] : 0; - if(groupId == 0) { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Group id not provided.\"}")); - return; + else { + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Remote address not provided.\"}")); } - if(shadeId == 0) { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade id not provided.\"}")); - return; - } - SomfyGroup * group = somfy.getGroupById(groupId); - if(!group) { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Group id not found.\"}")); - return; - } - SomfyShade * shade = somfy.getShadeById(shadeId); - if(!shade) { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade id not found.\"}")); - return; - } - group->unlinkShade(shadeId); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); resp.beginObject(); - group->toJSON(resp); + serializeShade(shade, resp); resp.endObject(); resp.endResponse(); } + else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade Id not found.\"}")); } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No unlinking object supplied.\"}")); - } - }); - server.on("/deleteRoom", []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - uint8_t roomId = 0; - if (method == HTTP_GET || method == HTTP_PUT || method == HTTP_POST) { - if (server.hasArg("roomId")) { - roomId = atoi(server.arg("roomId").c_str()); + else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade id was supplied.\"}")); + })); + + // linkToGroup + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/linkToGroup", + [](AsyncWebServerRequest *request, JsonVariant &json) { + if(json.isNull()) { request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No linking object supplied.\"}")); return; } + Serial.println("Linking a shade to a group"); + JsonObject obj = json.as(); + uint8_t shadeId = obj.containsKey("shadeId") ? obj["shadeId"] : 0; + uint8_t groupId = obj.containsKey("groupId") ? obj["groupId"] : 0; + if(groupId == 0) { + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Group id not provided.\"}")); + return; } - else if (server.hasArg("plain")) { + if(shadeId == 0) { + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade id not provided.\"}")); + return; + } + SomfyGroup *group = somfy.getGroupById(groupId); + if(!group) { + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Group id not found.\"}")); + return; + } + SomfyShade *shade = somfy.getShadeById(shadeId); + if(!shade) { + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade id not found.\"}")); + return; + } + group->linkShade(shadeId); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginObject(); + serializeGroup(group, resp); + resp.endObject(); + resp.endResponse(); + })); + + // unlinkFromGroup + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/unlinkFromGroup", + [](AsyncWebServerRequest *request, JsonVariant &json) { + if(json.isNull()) { request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No unlinking object supplied.\"}")); return; } + Serial.println("Unlinking a shade from a group"); + JsonObject obj = json.as(); + uint8_t shadeId = obj.containsKey("shadeId") ? obj["shadeId"] : 0; + uint8_t groupId = obj.containsKey("groupId") ? obj["groupId"] : 0; + if(groupId == 0) { + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Group id not provided.\"}")); + return; + } + if(shadeId == 0) { + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade id not provided.\"}")); + return; + } + SomfyGroup *group = somfy.getGroupById(groupId); + if(!group) { + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Group id not found.\"}")); + return; + } + SomfyShade *shade = somfy.getShadeById(shadeId); + if(!shade) { + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade id not found.\"}")); + return; + } + group->unlinkShade(shadeId); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginObject(); + serializeGroup(group, resp); + resp.endObject(); + resp.endResponse(); + })); + + // deleteRoom - supports GET with query params and POST/PUT with JSON body + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/deleteRoom", + [](AsyncWebServerRequest *request, JsonVariant &json) { + uint8_t roomId = 0; + if(!json.isNull()) { Serial.println("Deleting a Room"); - DynamicJsonDocument doc(256); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - webServer.handleDeserializationError(server, err); - return; - } - else { - JsonObject obj = doc.as(); - if (obj.containsKey("roomId")) roomId = obj["roomId"]; - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No room id was supplied.\"}")); - } + JsonObject obj = json.as(); + if(obj.containsKey("roomId")) roomId = obj["roomId"]; + else { request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No room id was supplied.\"}")); return; } } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No room object supplied.\"}")); + SomfyRoom* room = somfy.getRoomById(roomId); + if(!room) request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Room with the specified id not found.\"}")); + else { + somfy.deleteRoom(roomId); + request->send(200, _encoding_json, F("{\"status\":\"SUCCESS\",\"desc\":\"Room deleted.\"}")); + } + })); + asyncServer.on("/deleteRoom", HTTP_GET, [](AsyncWebServerRequest *request) { + uint8_t roomId = 0; + if(asyncHasParam(request, "roomId")) { + roomId = atoi(asyncParam(request, "roomId").c_str()); } + else { request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No room object supplied.\"}")); return; } SomfyRoom* room = somfy.getRoomById(roomId); - if (!room) server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Room with the specified id not found.\"}")); + if(!room) request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Room with the specified id not found.\"}")); else { somfy.deleteRoom(roomId); - server.send(200, _encoding_json, F("{\"status\":\"SUCCESS\",\"desc\":\"Room deleted.\"}")); + request->send(200, _encoding_json, F("{\"status\":\"SUCCESS\",\"desc\":\"Room deleted.\"}")); } - }); - server.on("/deleteShade", []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - uint8_t shadeId = 255; - if (method == HTTP_GET || method == HTTP_PUT || method == HTTP_POST) { - if (server.hasArg("shadeId")) { - shadeId = atoi(server.arg("shadeId").c_str()); - } - else if (server.hasArg("plain")) { + }); + + // deleteShade - supports GET with query params and POST/PUT with JSON body + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/deleteShade", + [](AsyncWebServerRequest *request, JsonVariant &json) { + uint8_t shadeId = 255; + if(!json.isNull()) { Serial.println("Deleting a shade"); - DynamicJsonDocument doc(256); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - webServer.handleDeserializationError(server, err); - return; - } - else { - JsonObject obj = doc.as(); - if (obj.containsKey("shadeId")) shadeId = obj["shadeId"]; - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade id was supplied.\"}")); - } + JsonObject obj = json.as(); + if(obj.containsKey("shadeId")) shadeId = obj["shadeId"]; + else { request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade id was supplied.\"}")); return; } } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade object supplied.\"}")); + SomfyShade* shade = somfy.getShadeById(shadeId); + if(!shade) request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade with the specified id not found.\"}")); + else if(shade->isInGroup()) { + request->send(400, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"This shade is a member of a group and cannot be deleted.\"}")); + } + else { + somfy.deleteShade(shadeId); + request->send(200, _encoding_json, F("{\"status\":\"SUCCESS\",\"desc\":\"Shade deleted.\"}")); + } + })); + asyncServer.on("/deleteShade", HTTP_GET, [](AsyncWebServerRequest *request) { + uint8_t shadeId = 255; + if(asyncHasParam(request, "shadeId")) { + shadeId = atoi(asyncParam(request, "shadeId").c_str()); } + else { request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade object supplied.\"}")); return; } SomfyShade* shade = somfy.getShadeById(shadeId); - if (!shade) server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade with the specified id not found.\"}")); + if(!shade) request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade with the specified id not found.\"}")); else if(shade->isInGroup()) { - server.send(400, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"This shade is a member of a group and cannot be deleted.\"}")); + request->send(400, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"This shade is a member of a group and cannot be deleted.\"}")); } else { somfy.deleteShade(shadeId); - server.send(200, _encoding_json, F("{\"status\":\"SUCCESS\",\"desc\":\"Shade deleted.\"}")); + request->send(200, _encoding_json, F("{\"status\":\"SUCCESS\",\"desc\":\"Shade deleted.\"}")); } - }); - server.on("/deleteGroup", []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - uint8_t groupId = 255; - if (method == HTTP_GET || method == HTTP_PUT || method == HTTP_POST) { - if (server.hasArg("groupId")) { - groupId = atoi(server.arg("groupId").c_str()); - } - else if (server.hasArg("plain")) { + }); + + // deleteGroup - supports GET with query params and POST/PUT with JSON body + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/deleteGroup", + [](AsyncWebServerRequest *request, JsonVariant &json) { + uint8_t groupId = 255; + if(!json.isNull()) { Serial.println("Deleting a group"); - DynamicJsonDocument doc(256); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - webServer.handleDeserializationError(server, err); - return; - } - else { - JsonObject obj = doc.as(); - if (obj.containsKey("groupId")) groupId = obj["groupId"]; - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No group id was supplied.\"}")); - } + JsonObject obj = json.as(); + if(obj.containsKey("groupId")) groupId = obj["groupId"]; + else { request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No group id was supplied.\"}")); return; } } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No group object supplied.\"}")); + SomfyGroup *group = somfy.getGroupById(groupId); + if(!group) request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Group with the specified id not found.\"}")); + else { + somfy.deleteGroup(groupId); + request->send(200, _encoding_json, F("{\"status\":\"SUCCESS\",\"desc\":\"Group deleted.\"}")); + } + })); + asyncServer.on("/deleteGroup", HTTP_GET, [](AsyncWebServerRequest *request) { + uint8_t groupId = 255; + if(asyncHasParam(request, "groupId")) { + groupId = atoi(asyncParam(request, "groupId").c_str()); } - SomfyGroup * group = somfy.getGroupById(groupId); - if (!group) server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Group with the specified id not found.\"}")); + else { request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No group object supplied.\"}")); return; } + SomfyGroup *group = somfy.getGroupById(groupId); + if(!group) request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Group with the specified id not found.\"}")); else { somfy.deleteGroup(groupId); - server.send(200, _encoding_json, F("{\"status\":\"SUCCESS\",\"desc\":\"Group deleted.\"}")); + request->send(200, _encoding_json, F("{\"status\":\"SUCCESS\",\"desc\":\"Group deleted.\"}")); } - }); - server.on("/updateFirmware", HTTP_POST, []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - if (Update.hasError()) - server.send(500, _encoding_json, "{\"status\":\"ERROR\",\"desc\":\"Error updating firmware: \"}"); - else - server.send(200, _encoding_json, "{\"status\":\"SUCCESS\",\"desc\":\"Successfully updated firmware\"}"); - rebootDelay.reboot = true; - rebootDelay.rebootTime = millis() + 500; - }, []() { - HTTPUpload& upload = server.upload(); - if (upload.status == UPLOAD_FILE_START) { + }); + + // updateFirmware - file upload + asyncServer.on("/updateFirmware", HTTP_POST, + [](AsyncWebServerRequest *request) { + if(Update.hasError()) + request->send(500, _encoding_json, "{\"status\":\"ERROR\",\"desc\":\"Error updating firmware: \"}"); + else + request->send(200, _encoding_json, "{\"status\":\"SUCCESS\",\"desc\":\"Successfully updated firmware\"}"); + rebootDelay.reboot = true; + rebootDelay.rebootTime = millis() + 500; + }, + [](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { + if(index == 0) { webServer.uploadSuccess = false; - Serial.printf("Update: %s - %d\n", upload.filename.c_str(), upload.totalSize); - //if(!Update.begin(upload.totalSize, U_SPIFFS)) { - if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size + Serial.printf("Update: %s\n", filename.c_str()); + if(!Update.begin(UPDATE_SIZE_UNKNOWN)) { Update.printError(Serial); } else { - somfy.transceiver.end(); // Shut down the radio so we do not get any interrupts during this process. + somfy.transceiver.end(); mqtt.end(); } } - else if(upload.status == UPLOAD_FILE_ABORTED) { - Serial.printf("Upload of %s aborted\n", upload.filename.c_str()); - Update.abort(); - } - else if (upload.status == UPLOAD_FILE_WRITE) { - /* flashing firmware to ESP*/ - if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { + if(len > 0) { + if(Update.write(data, len) != len) { Update.printError(Serial); - Serial.printf("Upload of %s aborted invalid size %d\n", upload.filename.c_str(), upload.currentSize); + Serial.printf("Upload of %s aborted invalid size %d\n", filename.c_str(), len); Update.abort(); } } - else if (upload.status == UPLOAD_FILE_END) { - if (Update.end(true)) { //true to set the size to the current progress - Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); + if(final) { + if(Update.end(true)) { + Serial.printf("Update Success: %u\nRebooting...\n", index + len); webServer.uploadSuccess = true; } else { @@ -2930,75 +1835,65 @@ void Web::begin() { } esp_task_wdt_reset(); }); - server.on("/updateShadeConfig", HTTP_POST, []() { - if(git.lockFS) { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Filesystem update in progress\"}")); - return; - } - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - server.sendHeader("Connection", "close"); - server.send(200, _encoding_json, "{\"status\":\"ERROR\",\"desc\":\"Updating Shade Config: \"}"); - }, []() { - HTTPUpload& upload = server.upload(); - if (upload.status == UPLOAD_FILE_START) { + + // updateShadeConfig - file upload + asyncServer.on("/updateShadeConfig", HTTP_POST, + [](AsyncWebServerRequest *request) { + if(git.lockFS) { + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Filesystem update in progress\"}")); + return; + } + request->send(200, _encoding_json, "{\"status\":\"ERROR\",\"desc\":\"Updating Shade Config: \"}"); + }, + [](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { + if(index == 0) { Serial.printf("Update: shades.cfg\n"); File fup = LittleFS.open("/shades.tmp", "w"); fup.close(); } - else if (upload.status == UPLOAD_FILE_WRITE) { - /* flashing littlefs to ESP*/ - if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { - File fup = LittleFS.open("/shades.tmp", "a"); - fup.write(upload.buf, upload.currentSize); - fup.close(); - } + if(len > 0) { + File fup = LittleFS.open("/shades.tmp", "a"); + fup.write(data, len); + fup.close(); } - else if (upload.status == UPLOAD_FILE_END) { + if(final) { somfy.loadShadesFile("/shades.tmp"); } }); - server.on("/updateApplication", HTTP_POST, []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - server.sendHeader("Connection", "close"); - if (Update.hasError()) - server.send(500, _encoding_json, "{\"status\":\"ERROR\",\"desc\":\"Error updating application: \"}"); - else - server.send(200, _encoding_json, "{\"status\":\"SUCCESS\",\"desc\":\"Successfully updated application\"}"); - rebootDelay.reboot = true; - rebootDelay.rebootTime = millis() + 500; - }, []() { - HTTPUpload& upload = server.upload(); - if (upload.status == UPLOAD_FILE_START) { + + // updateApplication - file upload + asyncServer.on("/updateApplication", HTTP_POST, + [](AsyncWebServerRequest *request) { + if(Update.hasError()) + request->send(500, _encoding_json, "{\"status\":\"ERROR\",\"desc\":\"Error updating application: \"}"); + else + request->send(200, _encoding_json, "{\"status\":\"SUCCESS\",\"desc\":\"Successfully updated application\"}"); + rebootDelay.reboot = true; + rebootDelay.rebootTime = millis() + 500; + }, + [](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { + if(index == 0) { webServer.uploadSuccess = false; - Serial.printf("Update: %s %d\n", upload.filename.c_str(), upload.totalSize); - //if(!Update.begin(upload.totalSize, U_SPIFFS)) { - if (!Update.begin(UPDATE_SIZE_UNKNOWN, U_SPIFFS)) { //start with max available size and tell it we are updating the file system. + Serial.printf("Update: %s\n", filename.c_str()); + if(!Update.begin(UPDATE_SIZE_UNKNOWN, U_SPIFFS)) { Update.printError(Serial); } else { - somfy.transceiver.end(); // Shut down the radio so we do not get any interrupts during this process. + somfy.transceiver.end(); mqtt.end(); } } - else if(upload.status == UPLOAD_FILE_ABORTED) { - Serial.printf("Upload of %s aborted\n", upload.filename.c_str()); - Update.abort(); - somfy.commit(); - } - else if (upload.status == UPLOAD_FILE_WRITE) { - /* flashing littlefs to ESP*/ - if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { + if(len > 0) { + if(Update.write(data, len) != len) { Update.printError(Serial); - Serial.printf("Upload of %s aborted invalid size %d\n", upload.filename.c_str(), upload.currentSize); + Serial.printf("Upload of %s aborted invalid size %d\n", filename.c_str(), len); Update.abort(); } } - else if (upload.status == UPLOAD_FILE_END) { - if (Update.end(true)) { //true to set the size to the current progress + if(final) { + if(Update.end(true)) { webServer.uploadSuccess = true; - Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); + Serial.printf("Update Success: %u\nRebooting...\n", index + len); somfy.commit(); } else { @@ -3008,22 +1903,18 @@ void Web::begin() { } esp_task_wdt_reset(); }); - server.on("/scanaps", []() { - webServer.sendCORSHeaders(server); + + asyncServer.on("/scanaps", HTTP_GET, [](AsyncWebServerRequest *request) { esp_task_wdt_reset(); - - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } esp_task_wdt_delete(NULL); if(net.softAPOpened) WiFi.disconnect(false); int n = WiFi.scanNetworks(false, true); esp_task_wdt_add(NULL); - Serial.print("Scanned "); Serial.print(n); Serial.println(" networks"); - // Ok we need to chunk this response as well. - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); resp.beginObject(); resp.beginObject("connected"); resp.addElem("name", settings.WIFI.ssid); @@ -3033,7 +1924,7 @@ void Web::begin() { resp.endObject(); resp.beginArray("accessPoints"); for(int i = 0; i < n; ++i) { - if(WiFi.SSID(i).length() == 0 || WiFi.RSSI(i) < -95) continue; // Ignore hidden and weak networks that we cannot connect to anyway. + if(WiFi.SSID(i).length() == 0 || WiFi.RSSI(i) < -95) continue; resp.beginObject(); resp.addElem("name", WiFi.SSID(i).c_str()); resp.addElem("channel", (int32_t)WiFi.channel(i)); @@ -3044,533 +1935,374 @@ void Web::begin() { resp.endArray(); resp.endObject(); resp.endResponse(); - }); - server.on("/reboot", []() { webServer.handleReboot(server);}); - server.on("/saveSecurity", []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - DynamicJsonDocument doc(512); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - Serial.print("Error parsing JSON "); - Serial.println(err.c_str()); - String msg = err.c_str(); - server.send(400, _encoding_html, "Error parsing JSON body
" + msg); - } - else { - JsonObject obj = doc.as(); - HTTPMethod method = server.method(); - if (method == HTTP_POST || method == HTTP_PUT) { - settings.Security.fromJSON(obj); - settings.Security.save(); - char token[65]; - webServer.createAPIToken(server.client().remoteIP(), token); - obj["apiKey"] = token; - DynamicJsonDocument sdoc(1024); - JsonObject sobj = sdoc.to(); - settings.Security.toJSON(sobj); - serializeJson(sdoc, g_content); - server.send(200, _encoding_json, g_content); + }); + + asyncServer.on("/reboot", WebRequestMethodComposite(HTTP_POST) | HTTP_PUT, [](AsyncWebServerRequest *request) { webServer.handleReboot(request); }); + + // saveSecurity + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/saveSecurity", + [](AsyncWebServerRequest *request, JsonVariant &json) { + if(json.isNull()) { + request->send(400, _encoding_html, "Error parsing JSON body"); + return; } - else { - server.send(201, "application/json", "{\"status\":\"ERROR\",\"desc\":\"Invalid HTTP Method: \"}"); - } - } - }); - server.on("/getSecurity", []() { - webServer.sendCORSHeaders(server); + JsonObject obj = json.as(); + settings.Security.fromJSON(obj); + settings.Security.save(); + char token[65]; + webServer.createAPIToken(request->client()->remoteIP(), token); + DynamicJsonDocument sdoc(1024); + JsonObject sobj = sdoc.to(); + settings.Security.toJSON(sobj); + sobj["apiKey"] = token; + serializeJson(sdoc, g_async_content, sizeof(g_async_content)); + request->send(200, _encoding_json, g_async_content); + })); + + // getSecurity + asyncServer.on("/getSecurity", HTTP_GET, [](AsyncWebServerRequest *request) { DynamicJsonDocument doc(512); JsonObject obj = doc.to(); settings.Security.toJSON(obj); - serializeJson(doc, g_content); - server.send(200, _encoding_json, g_content); - }); - server.on("/saveRadio", []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - DynamicJsonDocument doc(512); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - Serial.print("Error parsing JSON "); - Serial.println(err.c_str()); - String msg = err.c_str(); - server.send(400, _encoding_html, "Error parsing JSON body
" + msg); - } - else { - JsonObject obj = doc.as(); - HTTPMethod method = server.method(); - if (method == HTTP_POST || method == HTTP_PUT) { - somfy.transceiver.fromJSON(obj); - somfy.transceiver.save(); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginObject(); - somfy.transceiver.toJSON(resp); - resp.endObject(); - resp.endResponse(); + serializeJson(doc, g_async_content, sizeof(g_async_content)); + request->send(200, _encoding_json, g_async_content); + }); + + // saveRadio + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/saveRadio", + [](AsyncWebServerRequest *request, JsonVariant &json) { + if(json.isNull()) { + request->send(400, _encoding_html, "Error parsing JSON body"); + return; } - else { - server.send(201, "application/json", "{\"status\":\"ERROR\",\"desc\":\"Invalid HTTP Method: \"}"); - } - } - }); - server.on("/getRadio", []() { - webServer.sendCORSHeaders(server); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); + JsonObject obj = json.as(); + somfy.transceiver.fromJSON(obj); + somfy.transceiver.save(); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); + resp.beginObject(); + serializeTransceiverConfig(resp); + resp.endObject(); + resp.endResponse(); + })); + + // getRadio + asyncServer.on("/getRadio", HTTP_GET, [](AsyncWebServerRequest *request) { + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); resp.beginObject(); - somfy.transceiver.toJSON(resp); + serializeTransceiverConfig(resp); resp.endObject(); resp.endResponse(); - }); - server.on("/sendRemoteCommand", []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - if (method == HTTP_GET || method == HTTP_PUT || method == HTTP_POST) { + }); + + // sendRemoteCommand - supports GET with query params and POST/PUT with JSON body + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/sendRemoteCommand", + [](AsyncWebServerRequest *request, JsonVariant &json) { somfy_frame_t frame; uint8_t repeats = 0; - if (server.hasArg("address")) { - frame.remoteAddress = atoi(server.arg("address").c_str()); - if (server.hasArg("encKey")) frame.encKey = atoi(server.arg("encKey").c_str()); - if (server.hasArg("command")) frame.cmd = translateSomfyCommand(server.arg("command")); - if (server.hasArg("rcode")) frame.rollingCode = atoi(server.arg("rcode").c_str()); - if (server.hasArg("repeats")) repeats = atoi(server.arg("repeats").c_str()); + if(!json.isNull()) { + JsonObject obj = json.as(); + String scmd; + if(obj.containsKey("address")) frame.remoteAddress = obj["address"]; + if(obj.containsKey("command")) scmd = obj["command"].as(); + if(obj.containsKey("repeats")) repeats = obj["repeats"]; + if(obj.containsKey("rcode")) frame.rollingCode = obj["rcode"]; + if(obj.containsKey("encKey")) frame.encKey = obj["encKey"]; + frame.cmd = translateSomfyCommand(scmd.c_str()); } - else if (server.hasArg("plain")) { - StaticJsonDocument<128> doc; - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - webServer.handleDeserializationError(server, err); - return; - } - else { - JsonObject obj = doc.as(); - String scmd; - if (obj.containsKey("address")) frame.remoteAddress = obj["address"]; - if (obj.containsKey("command")) scmd = obj["command"].as(); - if (obj.containsKey("repeats")) repeats = obj["repeats"]; - if (obj.containsKey("rcode")) frame.rollingCode = obj["rcode"]; - if (obj.containsKey("encKey")) frame.encKey = obj["encKey"]; - frame.cmd = translateSomfyCommand(scmd.c_str()); - } - } - if (frame.remoteAddress > 0 && frame.rollingCode > 0) { + if(frame.remoteAddress > 0 && frame.rollingCode > 0) { somfy.sendFrame(frame, repeats); - server.send(200, _encoding_json, F("{\"status\":\"SUCCESS\",\"desc\":\"Command Sent\"}")); + request->send(200, _encoding_json, F("{\"status\":\"SUCCESS\",\"desc\":\"Command Sent\"}")); } else - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No address or rolling code provided\"}")); + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No address or rolling code provided\"}")); + })); + asyncServer.on("/sendRemoteCommand", HTTP_GET, [](AsyncWebServerRequest *request) { + somfy_frame_t frame; + uint8_t repeats = 0; + if(asyncHasParam(request, "address")) { + frame.remoteAddress = atoi(asyncParam(request, "address").c_str()); + if(asyncHasParam(request, "encKey")) frame.encKey = atoi(asyncParam(request, "encKey").c_str()); + if(asyncHasParam(request, "command")) frame.cmd = translateSomfyCommand(asyncParam(request, "command")); + if(asyncHasParam(request, "rcode")) frame.rollingCode = atoi(asyncParam(request, "rcode").c_str()); + if(asyncHasParam(request, "repeats")) repeats = atoi(asyncParam(request, "repeats").c_str()); } - }); - server.on("/setgeneral", []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - DynamicJsonDocument doc(512); - - Serial.print("Plain: "); - Serial.print(server.method()); - Serial.println(server.arg("plain")); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - webServer.handleDeserializationError(server, err); - return; + if(frame.remoteAddress > 0 && frame.rollingCode > 0) { + somfy.sendFrame(frame, repeats); + request->send(200, _encoding_json, F("{\"status\":\"SUCCESS\",\"desc\":\"Command Sent\"}")); } - else { - JsonObject obj = doc.as(); - HTTPMethod method = server.method(); - if (method == HTTP_POST || method == HTTP_PUT) { - // Parse out all the inputs. - if (obj.containsKey("hostname") || obj.containsKey("ssdpBroadcast") || obj.containsKey("checkForUpdate")) { - bool checkForUpdate = settings.checkForUpdate; - settings.fromJSON(obj); - settings.save(); - if(settings.checkForUpdate != checkForUpdate) git.emitUpdateCheck(); - if(obj.containsKey("hostname")) net.updateHostname(); + else + request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No address or rolling code provided\"}")); + }); + + // setgeneral + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/setgeneral", + [](AsyncWebServerRequest *request, JsonVariant &json) { + if(json.isNull()) { + request->send(500, "application/json", "{\"status\":\"ERROR\",\"desc\":\"JSON parse error\"}"); + return; + } + JsonObject obj = json.as(); + if(obj.containsKey("hostname") || obj.containsKey("ssdpBroadcast") || obj.containsKey("checkForUpdate")) { + bool checkForUpdate = settings.checkForUpdate; + settings.fromJSON(obj); + settings.save(); + if(settings.checkForUpdate != checkForUpdate) git.emitUpdateCheck(); + if(obj.containsKey("hostname")) net.updateHostname(); + } + if(obj.containsKey("ntpServer") || obj.containsKey("ntpServer")) { + settings.NTP.fromJSON(obj); + settings.NTP.save(); + } + request->send(200, "application/json", "{\"status\":\"OK\",\"desc\":\"Successfully set General Settings\"}"); + })); + + // setNetwork + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/setNetwork", + [](AsyncWebServerRequest *request, JsonVariant &json) { + if(json.isNull()) { + request->send(400, _encoding_html, "Error parsing JSON body"); + return; + } + JsonObject obj = json.as(); + bool reboot = false; + if(obj.containsKey("connType") && obj["connType"].as() != static_cast(settings.connType)) { + settings.connType = static_cast(obj["connType"].as()); + settings.save(); + reboot = true; + } + if(obj.containsKey("wifi")) { + JsonObject objWifi = obj["wifi"]; + if(settings.connType == conn_types_t::wifi) { + if(objWifi.containsKey("ssid") && objWifi["ssid"].as().compareTo(settings.WIFI.ssid) != 0) { + if(WiFi.softAPgetStationNum() == 0) reboot = true; + } + if(objWifi.containsKey("passphrase") && objWifi["passphrase"].as().compareTo(settings.WIFI.passphrase) != 0) { + if(WiFi.softAPgetStationNum() == 0) reboot = true; + } } - if (obj.containsKey("ntpServer") || obj.containsKey("ntpServer")) { - settings.NTP.fromJSON(obj); - settings.NTP.save(); - } - server.send(200, "application/json", "{\"status\":\"OK\",\"desc\":\"Successfully set General Settings\"}"); + settings.WIFI.fromJSON(objWifi); + settings.WIFI.save(); + } + if(obj.containsKey("ethernet")) { + JsonObject objEth = obj["ethernet"]; + if(settings.connType == conn_types_t::ethernet || settings.connType == conn_types_t::ethernetpref) + reboot = true; + settings.Ethernet.fromJSON(objEth); + settings.Ethernet.save(); + } + if(reboot) { + Serial.println("Rebooting ESP for new Network settings..."); + rebootDelay.reboot = true; + rebootDelay.rebootTime = millis() + 1000; + } + request->send(200, "application/json", "{\"status\":\"OK\",\"desc\":\"Successfully set Network Settings\"}"); + })); + + // setIP + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/setIP", + [](AsyncWebServerRequest *request, JsonVariant &json) { + if(json.isNull()) { + request->send(500, "application/json", "{\"status\":\"ERROR\",\"desc\":\"JSON parse error\"}"); + return; + } + Serial.println("Setting IP..."); + JsonObject obj = json.as(); + settings.IP.fromJSON(obj); + settings.IP.save(); + request->send(200, "application/json", "{\"status\":\"OK\",\"desc\":\"Successfully set Network Settings\"}"); + })); + + // connectwifi + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/connectwifi", + [](AsyncWebServerRequest *request, JsonVariant &json) { + if(json.isNull()) { + request->send(500, "application/json", "{\"status\":\"ERROR\",\"desc\":\"JSON parse error\"}"); + return; + } + JsonObject obj = json.as(); + Serial.println("Settings WIFI connection..."); + String ssid = ""; + String passphrase = ""; + if(obj.containsKey("ssid")) ssid = obj["ssid"].as(); + if(obj.containsKey("passphrase")) passphrase = obj["passphrase"].as(); + bool reboot = false; + if(ssid.compareTo(settings.WIFI.ssid) != 0) reboot = true; + if(passphrase.compareTo(settings.WIFI.passphrase) != 0) reboot = true; + if(!settings.WIFI.ssidExists(ssid.c_str()) && ssid.length() > 0) { + request->send(400, _encoding_json, "{\"status\":\"ERROR\",\"desc\":\"WiFi Network Does not exist\"}"); } else { - server.send(201, "application/json", "{\"status\":\"ERROR\",\"desc\":\"Invalid HTTP Method: \"}"); - } - } - }); - server.on("/setNetwork", []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - DynamicJsonDocument doc(1024); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - Serial.print("Error parsing JSON "); - Serial.println(err.c_str()); - String msg = err.c_str(); - server.send(400, _encoding_html, "Error parsing JSON body
" + msg); - } - else { - JsonObject obj = doc.as(); - HTTPMethod method = server.method(); - if (method == HTTP_POST || method == HTTP_PUT) { - // Parse out all the inputs. - bool reboot = false; - if(obj.containsKey("connType") && obj["connType"].as() != static_cast(settings.connType)) { - settings.connType = static_cast(obj["connType"].as()); - settings.save(); - reboot = true; - } - if(obj.containsKey("wifi")) { - JsonObject objWifi = obj["wifi"]; - if(settings.connType == conn_types_t::wifi) { - if(objWifi.containsKey("ssid") && objWifi["ssid"].as().compareTo(settings.WIFI.ssid) != 0) { - if(WiFi.softAPgetStationNum() == 0) reboot = true; - } - if(objWifi.containsKey("passphrase") && objWifi["passphrase"].as().compareTo(settings.WIFI.passphrase) != 0) { - if(WiFi.softAPgetStationNum() == 0) reboot = true; - } - } - settings.WIFI.fromJSON(objWifi); - settings.WIFI.save(); - } - if(obj.containsKey("ethernet")) - { - JsonObject objEth = obj["ethernet"]; - // This is an ethernet connection so if anything changes we need to reboot. - if(settings.connType == conn_types_t::ethernet || settings.connType == conn_types_t::ethernetpref) - reboot = true; - settings.Ethernet.fromJSON(objEth); - settings.Ethernet.save(); - } - if (reboot) { - Serial.println("Rebooting ESP for new Network settings..."); + SETCHARPROP(settings.WIFI.ssid, ssid.c_str(), sizeof(settings.WIFI.ssid)); + SETCHARPROP(settings.WIFI.passphrase, passphrase.c_str(), sizeof(settings.WIFI.passphrase)); + settings.WIFI.save(); + settings.WIFI.print(); + request->send(201, _encoding_json, "{\"status\":\"OK\",\"desc\":\"Successfully set server connection\"}"); + if(reboot) { + Serial.println("Rebooting ESP for new WiFi settings..."); rebootDelay.reboot = true; rebootDelay.rebootTime = millis() + 1000; } - server.send(200, "application/json", "{\"status\":\"OK\",\"desc\":\"Successfully set Network Settings\"}"); } - else { - server.send(201, "application/json", "{\"status\":\"ERROR\",\"desc\":\"Invalid HTTP Method: \"}"); - } - } - }); - server.on("/setIP", []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - Serial.println("Setting IP..."); - DynamicJsonDocument doc(1024); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - webServer.handleDeserializationError(server, err); - return; - } - else { - JsonObject obj = doc.as(); - HTTPMethod method = server.method(); - if (method == HTTP_POST || method == HTTP_PUT) { - settings.IP.fromJSON(obj); - settings.IP.save(); - server.send(200, "application/json", "{\"status\":\"OK\",\"desc\":\"Successfully set Network Settings\"}"); - } - else { - server.send(201, _encoding_json, "{\"status\":\"ERROR\",\"desc\":\"Invalid HTTP Method: \"}"); - } - } - }); - server.on("/connectwifi", []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - Serial.println("Settings WIFI connection..."); - DynamicJsonDocument doc(512); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - webServer.handleDeserializationError(server, err); - return; - } - else { - JsonObject obj = doc.as(); - HTTPMethod method = server.method(); - //Serial.print(F("HTTP Method: ")); - //Serial.println(server.method()); - if (method == HTTP_POST || method == HTTP_PUT) { - String ssid = ""; - String passphrase = ""; - if (obj.containsKey("ssid")) ssid = obj["ssid"].as(); - if (obj.containsKey("passphrase")) passphrase = obj["passphrase"].as(); - bool reboot; - if (ssid.compareTo(settings.WIFI.ssid) != 0) reboot = true; - if (passphrase.compareTo(settings.WIFI.passphrase) != 0) reboot = true; - if (!settings.WIFI.ssidExists(ssid.c_str()) && ssid.length() > 0) { - server.send(400, _encoding_json, "{\"status\":\"ERROR\",\"desc\":\"WiFi Network Does not exist\"}"); - } - else { - SETCHARPROP(settings.WIFI.ssid, ssid.c_str(), sizeof(settings.WIFI.ssid)); - SETCHARPROP(settings.WIFI.passphrase, passphrase.c_str(), sizeof(settings.WIFI.passphrase)); - settings.WIFI.save(); - settings.WIFI.print(); - server.send(201, _encoding_json, "{\"status\":\"OK\",\"desc\":\"Successfully set server connection\"}"); - if (reboot) { - Serial.println("Rebooting ESP for new WiFi settings..."); - rebootDelay.reboot = true; - rebootDelay.rebootTime = millis() + 1000; - } - } - } - else { - server.send(201, _encoding_json, "{\"status\":\"ERROR\",\"desc\":\"Invalid HTTP Method: \"}"); - } - } - }); - server.on("/modulesettings", []() { - webServer.sendCORSHeaders(server); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); + })); + + // modulesettings + asyncServer.on("/modulesettings", HTTP_GET, [](AsyncWebServerRequest *request) { + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); resp.beginObject(); resp.addElem("fwVersion", settings.fwVersion.name); - settings.toJSON(resp); - settings.NTP.toJSON(resp); + resp.addElem("ssdpBroadcast", settings.ssdpBroadcast); + resp.addElem("hostname", settings.hostname); + resp.addElem("connType", static_cast(settings.connType)); + resp.addElem("chipModel", settings.chipModel); + resp.addElem("checkForUpdate", settings.checkForUpdate); + resp.addElem("ntpServer", settings.NTP.ntpServer); + resp.addElem("posixZone", settings.NTP.posixZone); resp.endObject(); resp.endResponse(); - /* - DynamicJsonDocument doc(512); - JsonObject obj = doc.to(); - doc["fwVersion"] = settings.fwVersion.name; - settings.toJSON(obj); - //settings.Ethernet.toJSON(obj); - //settings.WIFI.toJSON(obj); - settings.NTP.toJSON(obj); - serializeJson(doc, g_content); - server.send(200, _encoding_json, g_content); - */ - }); - server.on("/networksettings", []() { - webServer.sendCORSHeaders(server); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginObject(); - settings.toJSON(resp); - resp.addElem("fwVersion", settings.fwVersion.name); - resp.beginObject("ethernet"); - settings.Ethernet.toJSON(resp); - resp.endObject(); - resp.beginObject("wifi"); - settings.WIFI.toJSON(resp); - resp.endObject(); - resp.beginObject("ip"); - settings.IP.toJSON(resp); - resp.endObject(); - resp.endObject(); - resp.endResponse(); - - /* + }); + + // networksettings + asyncServer.on("/networksettings", HTTP_GET, [](AsyncWebServerRequest *request) { DynamicJsonDocument doc(2048); JsonObject obj = doc.to(); - doc["fwVersion"] = settings.fwVersion.name; settings.toJSON(obj); + obj["fwVersion"] = settings.fwVersion.name; JsonObject eth = obj.createNestedObject("ethernet"); settings.Ethernet.toJSON(eth); JsonObject wifi = obj.createNestedObject("wifi"); settings.WIFI.toJSON(wifi); JsonObject ip = obj.createNestedObject("ip"); settings.IP.toJSON(ip); - serializeJson(doc, g_content); - server.send(200, _encoding_json, g_content); - */ - }); - server.on("/connectmqtt", []() { - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - DynamicJsonDocument doc(1024); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - webServer.handleDeserializationError(server, err); - return; - } - else { - JsonObject obj = doc.as(); - HTTPMethod method = server.method(); + serializeJson(doc, g_async_content, sizeof(g_async_content)); + request->send(200, _encoding_json, g_async_content); + }); + + // connectmqtt + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/connectmqtt", + [](AsyncWebServerRequest *request, JsonVariant &json) { + if(json.isNull()) { + request->send(500, "application/json", "{\"status\":\"ERROR\",\"desc\":\"JSON parse error\"}"); + return; + } + JsonObject obj = json.as(); Serial.print("Saving MQTT "); - Serial.print(F("HTTP Method: ")); - Serial.println(server.method()); - if (method == HTTP_POST || method == HTTP_PUT) { - mqtt.disconnect(); - settings.MQTT.fromJSON(obj); - settings.MQTT.save(); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginObject(); - settings.MQTT.toJSON(resp); - resp.endObject(); - resp.endResponse(); - /* - DynamicJsonDocument sdoc(1024); - JsonObject sobj = sdoc.to(); - settings.MQTT.toJSON(sobj); - serializeJson(sdoc, g_content); - server.send(200, _encoding_json, g_content); - */ - } - else { - server.send(201, "application/json", "{\"status\":\"ERROR\",\"desc\":\"Invalid HTTP Method: \"}"); - } - } - }); - server.on("/mqttsettings", []() { - webServer.sendCORSHeaders(server); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); - resp.beginObject(); - settings.MQTT.toJSON(resp); - resp.endObject(); - resp.endResponse(); - - /* + mqtt.disconnect(); + settings.MQTT.fromJSON(obj); + settings.MQTT.save(); + DynamicJsonDocument sdoc(1024); + JsonObject sobj = sdoc.to(); + settings.MQTT.toJSON(sobj); + serializeJson(sdoc, g_async_content, sizeof(g_async_content)); + request->send(200, _encoding_json, g_async_content); + })); + + // mqttsettings + asyncServer.on("/mqttsettings", HTTP_GET, [](AsyncWebServerRequest *request) { DynamicJsonDocument doc(1024); JsonObject obj = doc.to(); settings.MQTT.toJSON(obj); - serializeJson(doc, g_content); - server.send(200, _encoding_json, g_content); - */ - }); - server.on("/roomSortOrder", []() { - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - DynamicJsonDocument doc(512); - Serial.print("Plain: "); - Serial.print(server.method()); - Serial.println(server.arg("plain")); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - webServer.handleDeserializationError(server, err); - return; - } - else { - JsonArray arr = doc.as(); - HTTPMethod method = server.method(); - if (method == HTTP_POST || method == HTTP_PUT) { - // Parse out all the inputs. - uint8_t order = 0; - for(JsonVariant v : arr) { - uint8_t roomId = v.as(); - if (roomId != 0) { - SomfyRoom *room = somfy.getRoomById(roomId); - if(room) room->sortOrder = order++; - } - } - server.send(200, "application/json", "{\"status\":\"OK\",\"desc\":\"Successfully set room order\"}"); - } - else { - server.send(201, "application/json", "{\"status\":\"ERROR\",\"desc\":\"Invalid HTTP Method: \"}"); - } - } + serializeJson(doc, g_async_content, sizeof(g_async_content)); + request->send(200, _encoding_json, g_async_content); }); - server.on("/shadeSortOrder", []() { - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - DynamicJsonDocument doc(512); - Serial.print("Plain: "); - Serial.print(server.method()); - Serial.println(server.arg("plain")); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - webServer.handleDeserializationError(server, err); - return; - } - else { - JsonArray arr = doc.as(); - HTTPMethod method = server.method(); - if (method == HTTP_POST || method == HTTP_PUT) { - // Parse out all the inputs. - uint8_t order = 0; - for(JsonVariant v : arr) { - uint8_t shadeId = v.as(); - if (shadeId != 255) { - SomfyShade *shade = somfy.getShadeById(shadeId); - if(shade) shade->sortOrder = order++; - } + + // roomSortOrder + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/roomSortOrder", + [](AsyncWebServerRequest *request, JsonVariant &json) { + if(json.isNull()) { + request->send(500, "application/json", "{\"status\":\"ERROR\",\"desc\":\"JSON parse error\"}"); + return; + } + JsonArray arr = json.as(); + uint8_t order = 0; + for(JsonVariant v : arr) { + uint8_t roomId = v.as(); + if(roomId != 0) { + SomfyRoom *room = somfy.getRoomById(roomId); + if(room) room->sortOrder = order++; } - server.send(200, "application/json", "{\"status\":\"OK\",\"desc\":\"Successfully set shade order\"}"); } - else { - server.send(201, "application/json", "{\"status\":\"ERROR\",\"desc\":\"Invalid HTTP Method: \"}"); + request->send(200, "application/json", "{\"status\":\"OK\",\"desc\":\"Successfully set room order\"}"); + })); + + // shadeSortOrder + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/shadeSortOrder", + [](AsyncWebServerRequest *request, JsonVariant &json) { + if(json.isNull()) { + request->send(500, "application/json", "{\"status\":\"ERROR\",\"desc\":\"JSON parse error\"}"); + return; } - } - }); - server.on("/groupSortOrder", []() { - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - DynamicJsonDocument doc(512); - Serial.print("Plain: "); - Serial.print(server.method()); - Serial.println(server.arg("plain")); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - webServer.handleDeserializationError(server, err); - return; - } - else { - JsonArray arr = doc.as(); - HTTPMethod method = server.method(); - if (method == HTTP_POST || method == HTTP_PUT) { - // Parse out all the inputs. - uint8_t order = 0; - for(JsonVariant v : arr) { - uint8_t groupId = v.as(); - if (groupId != 255) { - SomfyGroup *group = somfy.getGroupById(groupId); - if(group) group->sortOrder = order++; - } + JsonArray arr = json.as(); + uint8_t order = 0; + for(JsonVariant v : arr) { + uint8_t shadeId = v.as(); + if(shadeId != 255) { + SomfyShade *shade = somfy.getShadeById(shadeId); + if(shade) shade->sortOrder = order++; } - server.send(200, "application/json", "{\"status\":\"OK\",\"desc\":\"Successfully set group order\"}"); } - else { - server.send(201, "application/json", "{\"status\":\"ERROR\",\"desc\":\"Invalid HTTP Method: \"}"); + request->send(200, "application/json", "{\"status\":\"OK\",\"desc\":\"Successfully set shade order\"}"); + })); + + // groupSortOrder + asyncServer.addHandler(new AsyncCallbackJsonWebHandler("/groupSortOrder", + [](AsyncWebServerRequest *request, JsonVariant &json) { + if(json.isNull()) { + request->send(500, "application/json", "{\"status\":\"ERROR\",\"desc\":\"JSON parse error\"}"); + return; } - } - }); - server.on("/beginFrequencyScan", []() { - webServer.sendCORSHeaders(server); + JsonArray arr = json.as(); + uint8_t order = 0; + for(JsonVariant v : arr) { + uint8_t groupId = v.as(); + if(groupId != 255) { + SomfyGroup *group = somfy.getGroupById(groupId); + if(group) group->sortOrder = order++; + } + } + request->send(200, "application/json", "{\"status\":\"OK\",\"desc\":\"Successfully set group order\"}"); + })); + + asyncServer.on("/beginFrequencyScan", HTTP_GET, [](AsyncWebServerRequest *request) { somfy.transceiver.beginFrequencyScan(); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); resp.beginObject(); - somfy.transceiver.toJSON(resp); + serializeTransceiverConfig(resp); resp.endObject(); resp.endResponse(); - /* - DynamicJsonDocument doc(1024); - JsonObject obj = doc.to(); - somfy.transceiver.toJSON(obj); - serializeJson(doc, g_content); - server.send(200, _encoding_json, g_content); - */ }); - server.on("/endFrequencyScan", []() { - webServer.sendCORSHeaders(server); + + asyncServer.on("/endFrequencyScan", HTTP_GET, [](AsyncWebServerRequest *request) { somfy.transceiver.endFrequencyScan(); - JsonResponse resp; - resp.beginResponse(&server, g_content, sizeof(g_content)); + AsyncJsonResp resp; + resp.beginResponse(request, g_async_content, sizeof(g_async_content)); resp.beginObject(); - somfy.transceiver.toJSON(resp); + serializeTransceiverConfig(resp); resp.endObject(); resp.endResponse(); - /* - DynamicJsonDocument doc(1024); - JsonObject obj = doc.to(); - somfy.transceiver.toJSON(obj); - serializeJson(doc, g_content); - server.send(200, _encoding_json, g_content); - */ }); - server.on("/recoverFilesystem", [] () { - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - webServer.sendCORSHeaders(server); + + asyncServer.on("/recoverFilesystem", WebRequestMethodComposite(HTTP_GET) | HTTP_POST, [](AsyncWebServerRequest *request) { if(git.status == GIT_UPDATING) - server.send(200, "application/json", "{\"status\":\"OK\",\"desc\":\"Filesystem is updating. Please wait!!!\"}"); + request->send(200, "application/json", "{\"status\":\"OK\",\"desc\":\"Filesystem is updating. Please wait!!!\"}"); else if(git.status != GIT_STATUS_READY) - server.send(200, "application/json", "{\"status\":\"ERROR\",\"desc\":\"Cannot recover file system at this time.\"}"); + request->send(200, "application/json", "{\"status\":\"ERROR\",\"desc\":\"Cannot recover file system at this time.\"}"); else { git.recoverFilesystem(); - server.send(200, "application/json", "{\"status\":\"OK\",\"desc\":\"Recovering filesystem from github please wait!!!\"}"); + request->send(200, "application/json", "{\"status\":\"OK\",\"desc\":\"Recovering filesystem from github please wait!!!\"}"); } }); - server.begin(); - apiServer.begin(); + + asyncServer.onNotFound([](AsyncWebServerRequest *r) { + if(r->method() == HTTP_OPTIONS) { r->send(200); return; } + webServer.handleNotFound(r); + }); + + // serveStatic MUST be registered AFTER all route handlers so it doesn't shadow them + asyncServer.serveStatic("/", LittleFS, "/").setDefaultFile("index.html"); + + asyncServer.begin(); } diff --git a/src/Web.h b/src/Web.h index 6c39102..042db62 100644 --- a/src/Web.h +++ b/src/Web.h @@ -1,4 +1,3 @@ -#include #include #include #include "Somfy.h" @@ -7,45 +6,18 @@ class Web { public: bool uploadSuccess = false; - void sendCORSHeaders(WebServer &server); - void sendCacheHeaders(uint32_t seconds=604800); void startup(); - void handleLogin(WebServer &server); - void handleLogout(WebServer &server); - void handleStreamFile(WebServer &server, const char *filename, const char *encoding); - void handleController(WebServer &server); - void handleLoginContext(WebServer &server); - void handleGetRepeaters(WebServer &server); - void handleGetRooms(WebServer &server); - void handleGetShades(WebServer &server); - void handleGetGroups(WebServer &server); - void handleShadeCommand(WebServer &server); - void handleRepeatCommand(WebServer &server); - void handleGroupCommand(WebServer &server); - void handleTiltCommand(WebServer &server); - void handleDiscovery(WebServer &server); - void handleNotFound(WebServer &server); - void handleRoom(WebServer &server); - void handleShade(WebServer &server); - void handleGroup(WebServer &server); - void handleSetPositions(WebServer &server); - void handleSetSensor(WebServer &server); - void handleDownloadFirmware(WebServer &server); - void handleBackup(WebServer &server, bool attach = false); - void handleReboot(WebServer &server); - void handleDeserializationError(WebServer &server, DeserializationError &err); void begin(); void loop(); void end(); - // Web Handlers + // Auth helpers bool createAPIToken(const IPAddress ipAddress, char *token); bool createAPIToken(const char *payload, char *token); bool createAPIPinToken(const IPAddress ipAddress, const char *pin, char *token); bool createAPIPasswordToken(const IPAddress ipAddress, const char *username, const char *password, char *token); - bool isAuthenticated(WebServer &server, bool cfg = false); bool isAuthenticated(AsyncWebServerRequest *request, bool cfg = false); - // Async API handler overloads (port 8081) + // Async API handlers void handleDiscovery(AsyncWebServerRequest *request); void handleGetRooms(AsyncWebServerRequest *request); void handleGetShades(AsyncWebServerRequest *request); From 9584f01a70fed6a9750a76d04d2faded6715fdd9 Mon Sep 17 00:00:00 2001 From: cjkas Date: Tue, 24 Mar 2026 08:58:15 +0100 Subject: [PATCH 13/16] Fix bug with my position --- .gitignore | 1 + src/Somfy.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index d11457f..5609aab 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ SomfyController.ino.esp32c3.bin SomfyController.ino.esp32s2.bin .vscode/ .pio/ +.claude/ data/ build/ coredump_report.txt diff --git a/src/Somfy.cpp b/src/Somfy.cpp index 30bf84a..5feb36d 100644 --- a/src/Somfy.cpp +++ b/src/Somfy.cpp @@ -156,9 +156,9 @@ void somfy_frame_t::decodeFrame(byte* frame) { this->proto = radio_proto::RTV; this->cmd = (somfy_commands)(this->encKey - 148); } - else if(this->encKey > 133) { + else if(this->encKey >= 133) { this->proto = radio_proto::RTW; - this->cmd = (somfy_commands)(this->encKey - 133); + this->cmd = this->encKey == 133 ? somfy_commands::My : (somfy_commands)(this->encKey - 133); } } else this->proto = radio_proto::RTS; From 8262f392f5fcb091401d45b6c66c29bf1aa6cf02 Mon Sep 17 00:00:00 2001 From: cjkas Date: Tue, 24 Mar 2026 14:36:57 +0100 Subject: [PATCH 14/16] remove sync server --- src/ConfigSettings.cpp | 67 ---------------- src/ConfigSettings.h | 18 ++--- src/GitOTA.cpp | 49 ------------ src/GitOTA.h | 6 +- src/Somfy.cpp | 177 ----------------------------------------- src/Somfy.h | 13 --- src/WResp.cpp | 37 --------- src/WResp.h | 11 --- src/Web.cpp | 2 - src/Web.h | 1 - 10 files changed, 12 insertions(+), 369 deletions(-) diff --git a/src/ConfigSettings.cpp b/src/ConfigSettings.cpp index 5deec5e..3de3c4c 100644 --- a/src/ConfigSettings.cpp +++ b/src/ConfigSettings.cpp @@ -85,13 +85,6 @@ bool appver_t::toJSON(JsonObject &obj) { obj["suffix"] = this->suffix; return true; } -void appver_t::toJSON(JsonResponse &json) { - json.addElem("name", this->name); - json.addElem("major", this->major); - json.addElem("minor", this->minor); - json.addElem("build", this->build); - json.addElem("suffix", this->suffix); -} void appver_t::toJSON(JsonSockEvent *json) { json->addElem("name", this->name); json->addElem("major", this->major); @@ -249,14 +242,6 @@ bool ConfigSettings::toJSON(JsonObject &obj) { obj["checkForUpdate"] = this->checkForUpdate; return true; } -void ConfigSettings::toJSON(JsonResponse &json) { - json.addElem("ssdpBroadcast", this->ssdpBroadcast); - json.addElem("hostname", this->hostname); - json.addElem("connType", static_cast(this->connType)); - json.addElem("chipModel", this->chipModel); - json.addElem("checkForUpdate", this->checkForUpdate); -} - bool ConfigSettings::requiresAuth() { return this->Security.type != security_types::None; } bool ConfigSettings::fromJSON(JsonObject &obj) { if(obj.containsKey("ssdpBroadcast")) this->ssdpBroadcast = obj["ssdpBroadcast"]; @@ -308,18 +293,6 @@ bool MQTTSettings::begin() { this->load(); return true; } -void MQTTSettings::toJSON(JsonResponse &json) { - json.addElem("enabled", this->enabled); - json.addElem("pubDisco", this->pubDisco); - json.addElem("protocol", this->protocol); - json.addElem("hostname", this->hostname); - json.addElem("port", (uint32_t)this->port); - json.addElem("username", this->username); - json.addElem("password", this->password); - json.addElem("rootTopic", this->rootTopic); - json.addElem("discoTopic", this->discoTopic); -} - bool MQTTSettings::toJSON(JsonObject &obj) { obj["enabled"] = this->enabled; obj["pubDisco"] = this->pubDisco; @@ -418,11 +391,6 @@ bool NTPSettings::fromJSON(JsonObject &obj) { this->parseValueString(obj, "posixZone", this->posixZone, sizeof(this->posixZone)); return true; } -void NTPSettings::toJSON(JsonResponse &json) { - json.addElem("ntpServer", this->ntpServer); - json.addElem("posixZone", this->posixZone); -} - bool NTPSettings::toJSON(JsonObject &obj) { obj["ntpServer"] = this->ntpServer; obj["posixZone"] = this->posixZone; @@ -459,16 +427,6 @@ bool IPSettings::toJSON(JsonObject &obj) { obj["dns2"] = this->dns2 == ipEmpty ? "" : this->dns2.toString(); return true; } -void IPSettings::toJSON(JsonResponse &json) { - IPAddress ipEmpty(0,0,0,0); - json.addElem("dhcp", this->dhcp); - json.addElem("ip", this->ip.toString().c_str()); - json.addElem("gateway", this->gateway.toString().c_str()); - json.addElem("subnet", this->subnet.toString().c_str()); - json.addElem("dns1", this->dns1.toString().c_str()); - json.addElem("dns2", this->dns2.toString().c_str()); -} - bool IPSettings::save() { pref.begin("IP"); pref.clear(); @@ -529,14 +487,6 @@ bool SecuritySettings::toJSON(JsonObject &obj) { obj["permissions"] = this->permissions; return true; } -void SecuritySettings::toJSON(JsonResponse &json) { - json.addElem("type", static_cast(this->type)); - json.addElem("username", this->username); - json.addElem("password", this->password); - json.addElem("pin", this->pin); - json.addElem("permissions", this->permissions); -} - bool SecuritySettings::save() { pref.begin("SEC"); pref.clear(); @@ -590,13 +540,6 @@ bool WifiSettings::toJSON(JsonObject &obj) { obj["hidden"] = this->hidden; return true; } -void WifiSettings::toJSON(JsonResponse &json) { - json.addElem("ssid", this->ssid); - json.addElem("passphrase", this->passphrase); - json.addElem("roaming", this->roaming); - json.addElem("hidden", this->hidden); -} - bool WifiSettings::save() { pref.begin("WIFI"); pref.clear(); @@ -697,16 +640,6 @@ bool EthernetSettings::toJSON(JsonObject &obj) { obj["MDIOPin"] = this->MDIOPin; return true; } -void EthernetSettings::toJSON(JsonResponse &json) { - json.addElem("boardType", this->boardType); - json.addElem("phyAddress", this->phyAddress); - json.addElem("CLKMode", static_cast(this->CLKMode)); - json.addElem("phyType", static_cast(this->phyType)); - json.addElem("PWRPin", this->PWRPin); - json.addElem("MDCPin", this->MDCPin); - json.addElem("MDIOPin", this->MDIOPin); -} - bool EthernetSettings::usesPin(uint8_t pin) { if((this->CLKMode == 0 || this->CLKMode == 1) && pin == 0) return true; else if(this->CLKMode == 2 && pin == 16) return true; diff --git a/src/ConfigSettings.h b/src/ConfigSettings.h index 50314db..2bee8e2 100644 --- a/src/ConfigSettings.h +++ b/src/ConfigSettings.h @@ -34,7 +34,7 @@ struct appver_t { char suffix[4] = ""; void parse(const char *ver); bool toJSON(JsonObject &obj); - void toJSON(JsonResponse &json); + void toJSON(JsonSockEvent *json); int8_t compare(appver_t &ver); void copy(appver_t &ver); @@ -46,7 +46,7 @@ class BaseSettings { bool loadFile(const char* filename); bool fromJSON(JsonObject &obj); bool toJSON(JsonObject &obj); - void toJSON(JsonResponse &json); + bool parseIPAddress(JsonObject &obj, const char *prop, IPAddress *); bool parseValueString(JsonObject &obj, const char *prop, char *dest, size_t size); int parseValueInt(JsonObject &obj, const char *prop, int defVal); @@ -61,7 +61,7 @@ class NTPSettings: BaseSettings { char posixZone[64] = ""; bool fromJSON(JsonObject &obj); bool toJSON(JsonObject &obj); - void toJSON(JsonResponse &json); + bool apply(); bool begin(); bool save(); @@ -79,7 +79,7 @@ class WifiSettings: BaseSettings { bool begin(); bool fromJSON(JsonObject &obj); bool toJSON(JsonObject &obj); - void toJSON(JsonResponse &json); + String mapEncryptionType(int type); bool ssidExists(const char *ssid); void printNetworks(); @@ -102,7 +102,7 @@ class EthernetSettings: BaseSettings { bool begin(); bool fromJSON(JsonObject &obj); bool toJSON(JsonObject &obj); - void toJSON(JsonResponse &json); + bool load(); bool save(); void print(); @@ -120,7 +120,7 @@ class IPSettings: BaseSettings { bool begin(); bool fromJSON(JsonObject &obj); bool toJSON(JsonObject &obj); - void toJSON(JsonResponse &json); + bool load(); bool save(); void print(); @@ -145,7 +145,7 @@ class SecuritySettings: BaseSettings { bool load(); void print(); bool toJSON(JsonObject &obj); - void toJSON(JsonResponse &json); + bool fromJSON(JsonObject &obj); }; class MQTTSettings: BaseSettings { @@ -163,7 +163,7 @@ class MQTTSettings: BaseSettings { bool save(); bool load(); bool toJSON(JsonObject &obj); - void toJSON(JsonResponse &json); + bool fromJSON(JsonObject &obj); }; class ConfigSettings: BaseSettings { @@ -187,7 +187,7 @@ class ConfigSettings: BaseSettings { bool requiresAuth(); bool fromJSON(JsonObject &obj); bool toJSON(JsonObject &obj); - void toJSON(JsonResponse &json); + bool begin(); bool save(); bool load(); diff --git a/src/GitOTA.cpp b/src/GitOTA.cpp index 49ea3c5..3b4e9a3 100644 --- a/src/GitOTA.cpp +++ b/src/GitOTA.cpp @@ -72,22 +72,6 @@ void GitRelease::setAssetProperty(const char *key, const char *val) { } } } -void GitRelease::toJSON(JsonResponse &json) { - Timestamp ts; - char buff[20]; - sprintf(buff, "%llu", this->id); - json.addElem("id", buff); - json.addElem("name", this->name); - json.addElem("date", ts.getISOTime(this->releaseDate)); - json.addElem("draft", this->draft); - json.addElem("preRelease", this->preRelease); - json.addElem("main", this->main); - json.addElem("hasFS", this->hasFS); - json.addElem("hwVersions", this->hwVersions); - json.beginObject("version"); - this->version.toJSON(json); - json.endObject(); -} #define ERR_CLIENT_OFFSET -50 int16_t GitRepo::getReleases(uint8_t num) { @@ -230,22 +214,6 @@ int16_t GitRepo::getReleases(uint8_t num) { settings.printAvailHeap(); return 0; } -void GitRepo::toJSON(JsonResponse &json) { - json.beginObject("fwVersion"); - settings.fwVersion.toJSON(json); - json.endObject(); - json.beginObject("appVersion"); - settings.appVersion.toJSON(json); - json.endObject(); - json.beginArray("releases"); - for(uint8_t i = 0; i < GIT_MAX_RELEASES + 1; i++) { - if(this->releases[i].id == 0) continue; - json.beginObject(); - this->releases[i].toJSON(json); - json.endObject(); - } - json.endArray(); -} #define UPDATE_ERR_OFFSET 20 #define ERR_DOWNLOAD_HTTP -40 #define ERR_DOWNLOAD_BUFFER -41 @@ -310,23 +278,6 @@ void GitUpdater::setCurrentRelease(GitRepo &repo) { } this->emitUpdateCheck(); } -void GitUpdater::toJSON(JsonResponse &json) { - json.addElem("available", this->updateAvailable); - json.addElem("status", this->status); - json.addElem("error", (int32_t)this->error); - json.addElem("cancelled", this->cancelled); - json.addElem("checkForUpdate", settings.checkForUpdate); - json.addElem("inetAvailable", this->inetAvailable); - json.beginObject("fwVersion"); - settings.fwVersion.toJSON(json); - json.endObject(); - json.beginObject("appVersion"); - settings.appVersion.toJSON(json); - json.endObject(); - json.beginObject("latest"); - this->latest.toJSON(json); - json.endObject(); -} void GitUpdater::emitUpdateCheck(uint8_t num) { JsonSockEvent *json = sockEmit.beginEmit("fwStatus"); json->beginObject(); diff --git a/src/GitOTA.h b/src/GitOTA.h index f79aaea..6996f83 100644 --- a/src/GitOTA.h +++ b/src/GitOTA.h @@ -28,13 +28,13 @@ class GitRelease { appver_t version; void setReleaseProperty(const char *key, const char *val); void setAssetProperty(const char *key, const char *val); - void toJSON(JsonResponse &json); + }; class GitRepo { public: int16_t getReleases(uint8_t num = GIT_MAX_RELEASES); GitRelease releases[GIT_MAX_RELEASES + 1]; - void toJSON(JsonResponse &json); + }; class GitUpdater { public: @@ -58,7 +58,7 @@ class GitUpdater { void setFirmwareFile(); void setCurrentRelease(GitRepo &repo); void loop(); - void toJSON(JsonResponse &json); + bool recoverFilesystem(); int checkInternet(); void emitUpdateCheck(uint8_t num=255); diff --git a/src/Somfy.cpp b/src/Somfy.cpp index 5feb36d..31b1aca 100644 --- a/src/Somfy.cpp +++ b/src/Somfy.cpp @@ -1,7 +1,6 @@ #include #include #include -#include #include #include "Utils.h" #include "ConfigSettings.h" @@ -3328,72 +3327,6 @@ int8_t SomfyShade::fromJSON(JsonObject &obj) { } return err; } -void SomfyShade::toJSONRef(JsonResponse &json) { - json.addElem("shadeId", this->getShadeId()); - json.addElem("roomId", this->roomId); - json.addElem("name", this->name); - json.addElem("remoteAddress", (uint32_t)this->m_remoteAddress); - json.addElem("paired", this->paired); - json.addElem("shadeType", static_cast(this->shadeType)); - json.addElem("flipCommands", this->flipCommands); - json.addElem("flipPosition", this->flipCommands); - json.addElem("bitLength", this->bitLength); - json.addElem("proto", static_cast(this->proto)); - json.addElem("flags", this->flags); - json.addElem("sunSensor", this->hasSunSensor()); - json.addElem("hasLight", this->hasLight()); - json.addElem("repeats", this->repeats); - //SomfyRemote::toJSON(json); -} - -void SomfyShade::toJSON(JsonResponse &json) { - json.addElem("shadeId", this->getShadeId()); - json.addElem("roomId", this->roomId); - json.addElem("name", this->name); - json.addElem("remoteAddress", (uint32_t)this->m_remoteAddress); - json.addElem("upTime", (uint32_t)this->upTime); - json.addElem("downTime", (uint32_t)this->downTime); - json.addElem("paired", this->paired); - json.addElem("lastRollingCode", (uint32_t)this->lastRollingCode); - json.addElem("position", this->transformPosition(this->currentPos)); - json.addElem("tiltType", static_cast(this->tiltType)); - json.addElem("tiltPosition", this->transformPosition(this->currentTiltPos)); - json.addElem("tiltDirection", this->tiltDirection); - json.addElem("tiltTime", (uint32_t)this->tiltTime); - json.addElem("stepSize", (uint32_t)this->stepSize); - json.addElem("tiltTarget", this->transformPosition(this->tiltTarget)); - json.addElem("target", this->transformPosition(this->target)); - json.addElem("myPos", this->transformPosition(this->myPos)); - json.addElem("myTiltPos", this->transformPosition(this->myTiltPos)); - json.addElem("direction", this->direction); - json.addElem("shadeType", static_cast(this->shadeType)); - json.addElem("bitLength", this->bitLength); - json.addElem("proto", static_cast(this->proto)); - json.addElem("flags", this->flags); - json.addElem("flipCommands", this->flipCommands); - json.addElem("flipPosition", this->flipPosition); - json.addElem("inGroup", this->isInGroup()); - json.addElem("sunSensor", this->hasSunSensor()); - json.addElem("light", this->hasLight()); - json.addElem("repeats", this->repeats); - json.addElem("sortOrder", this->sortOrder); - json.addElem("gpioUp", this->gpioUp); - json.addElem("gpioDown", this->gpioDown); - json.addElem("gpioMy", this->gpioMy); - json.addElem("gpioLLTrigger", ((this->gpioFlags & (uint8_t)gpio_flags_t::LowLevelTrigger) == 0) ? false : true); - json.addElem("simMy", this->simMy()); - json.beginArray("linkedRemotes"); - for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) { - SomfyLinkedRemote &lremote = this->linkedRemotes[i]; - if(lremote.getRemoteAddress() != 0) { - json.beginObject(); - lremote.toJSON(json); - json.endObject(); - } - } - json.endArray(); -} - /* bool SomfyShade::toJSON(JsonObject &obj) { //Serial.print("Serializing Shade:"); @@ -3461,12 +3394,6 @@ bool SomfyRoom::toJSON(JsonObject &obj) { return true; } */ -void SomfyRoom::toJSON(JsonResponse &json) { - json.addElem("roomId", this->roomId); - json.addElem("name", this->name); - json.addElem("sortOrder", this->sortOrder); -} - bool SomfyGroup::fromJSON(JsonObject &obj) { if(obj.containsKey("name")) strlcpy(this->name, obj["name"], sizeof(this->name)); if(obj.containsKey("roomId")) this->roomId = obj["roomId"]; @@ -3488,50 +3415,6 @@ bool SomfyGroup::fromJSON(JsonObject &obj) { } return true; } -void SomfyGroup::toJSON(JsonResponse &json) { - this->updateFlags(); - json.addElem("groupId", this->getGroupId()); - json.addElem("roomId", this->roomId); - json.addElem("name", this->name); - json.addElem("remoteAddress", (uint32_t)this->m_remoteAddress); - json.addElem("lastRollingCode", (uint32_t)this->lastRollingCode); - json.addElem("bitLength", this->bitLength); - json.addElem("proto", static_cast(this->proto)); - json.addElem("sunSensor", this->hasSunSensor()); - json.addElem("flipCommands", this->flipCommands); - json.addElem("flags", this->flags); - json.addElem("repeats", this->repeats); - json.addElem("sortOrder", this->sortOrder); - json.beginArray("linkedShades"); - for(uint8_t i = 0; i < SOMFY_MAX_GROUPED_SHADES; i++) { - uint8_t shadeId = this->linkedShades[i]; - if(shadeId > 0 && shadeId < 255) { - SomfyShade *shade = somfy.getShadeById(shadeId); - if(shade) { - json.beginObject(); - shade->toJSONRef(json); - json.endObject(); - } - } - } - json.endArray(); -} -void SomfyGroup::toJSONRef(JsonResponse &json) { - this->updateFlags(); - json.addElem("groupId", this->getGroupId()); - json.addElem("roomId", this->roomId); - json.addElem("name", this->name); - json.addElem("remoteAddress", (uint32_t)this->m_remoteAddress); - json.addElem("lastRollingCode", (uint32_t)this->lastRollingCode); - json.addElem("bitLength", this->bitLength); - json.addElem("proto", static_cast(this->proto)); - json.addElem("sunSensor", this->hasSunSensor()); - json.addElem("flipCommands", this->flipCommands); - json.addElem("flags", this->flags); - json.addElem("repeats", this->repeats); - json.addElem("sortOrder", this->sortOrder); -} - /* bool SomfyGroup::toJSON(JsonObject &obj) { this->updateFlags(); @@ -3563,10 +3446,6 @@ bool SomfyGroup::toJSON(JsonObject &obj) { } */ -void SomfyRemote::toJSON(JsonResponse &json) { - json.addElem("remoteAddress", (uint32_t)this->getRemoteAddress()); - json.addElem("lastRollingCode", (uint32_t)this->lastRollingCode); -} /* bool SomfyRemote::toJSON(JsonObject &obj) { //obj["remotePrefId"] = this->getRemotePrefId(); @@ -4131,26 +4010,6 @@ uint16_t SomfyRemote::setRollingCode(uint16_t code) { } return code; } -void SomfyShadeController::toJSONRooms(JsonResponse &json) { - for(uint8_t i = 0; i < SOMFY_MAX_ROOMS; i++) { - SomfyRoom *room = &this->rooms[i]; - if(room->roomId != 0) { - json.beginObject(); - room->toJSON(json); - json.endObject(); - } - } -} -void SomfyShadeController::toJSONShades(JsonResponse &json) { - for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) { - SomfyShade &shade = this->shades[i]; - if(shade.getShadeId() != 255) { - json.beginObject(); - shade.toJSON(json); - json.endObject(); - } - } -} /* bool SomfyShadeController::toJSON(DynamicJsonDocument &doc) { @@ -4205,21 +4064,6 @@ bool SomfyShadeController::toJSONGroups(JsonArray &arr) { return true; } */ -void SomfyShadeController::toJSONGroups(JsonResponse &json) { - for(uint8_t i = 0; i < SOMFY_MAX_GROUPS; i++) { - SomfyGroup &group = this->groups[i]; - if(group.getGroupId() != 255) { - json.beginObject(); - group.toJSON(json); - json.endObject(); - } - } -} -void SomfyShadeController::toJSONRepeaters(JsonResponse &json) { - for(uint8_t i = 0; i < SOMFY_MAX_REPEATERS; i++) { - if(somfy.repeaters[i] != 0) json.addElem((uint32_t)somfy.repeaters[i]); - } -} void SomfyShadeController::loop() { this->transceiver.loop(); for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) { @@ -4703,11 +4547,6 @@ void Transceiver::disableReceive(void) { interruptPin = 0; } -void Transceiver::toJSON(JsonResponse& json) { - json.beginObject("config"); - this->config.toJSON(json); - json.endObject(); -} /* bool Transceiver::toJSON(JsonObject& obj) { //Serial.println("Setting Transceiver Json"); @@ -4786,22 +4625,6 @@ void transceiver_config_t::fromJSON(JsonObject& obj) { */ Serial.printf("SCK:%u MISO:%u MOSI:%u CSN:%u RX:%u TX:%u\n", this->SCKPin, this->MISOPin, this->MOSIPin, this->CSNPin, this->RXPin, this->TXPin); } -void transceiver_config_t::toJSON(JsonResponse &json) { - json.addElem("type", this->type); - json.addElem("TXPin", this->TXPin); - json.addElem("RXPin", this->RXPin); - json.addElem("SCKPin", this->SCKPin); - json.addElem("MOSIPin", this->MOSIPin); - json.addElem("MISOPin", this->MISOPin); - json.addElem("CSNPin", this->CSNPin); - json.addElem("rxBandwidth", this->rxBandwidth); // float - json.addElem("frequency", this->frequency); // float - json.addElem("deviation", this->deviation); // float - json.addElem("txPower", this->txPower); - json.addElem("proto", static_cast(this->proto)); - json.addElem("enabled", this->enabled); - json.addElem("radioInit", this->radioInit); -} /* void transceiver_config_t::toJSON(JsonObject& obj) { obj["type"] = this->type; diff --git a/src/Somfy.h b/src/Somfy.h index 3c1aac5..33ac49d 100644 --- a/src/Somfy.h +++ b/src/Somfy.h @@ -209,7 +209,6 @@ class SomfyRoom { void clear(); bool save(); bool fromJSON(JsonObject &obj); - void toJSON(JsonResponse &json); void emitState(const char *evt = "roomState"); void emitState(uint8_t num, const char *evt = "roomState"); void publish(); @@ -239,7 +238,6 @@ class SomfyRemote { uint8_t repeats = 1; virtual bool isLastCommand(somfy_commands cmd); char *getRemotePrefId() {return m_remotePrefId;} - virtual void toJSON(JsonResponse &json); virtual void setRemoteAddress(uint32_t address); virtual uint32_t getRemoteAddress(); virtual uint16_t getNextRollingCode(); @@ -304,9 +302,7 @@ class SomfyShade : public SomfyRemote { SomfyLinkedRemote linkedRemotes[SOMFY_MAX_LINKED_REMOTES]; bool paired = false; int8_t validateJSON(JsonObject &obj); - void toJSONRef(JsonResponse &json); int8_t fromJSON(JsonObject &obj); - void toJSON(JsonResponse &json) override; char name[21] = ""; void setShadeId(uint8_t id) { shadeId = id; } @@ -393,9 +389,6 @@ class SomfyGroup : public SomfyRemote { void clear(); bool fromJSON(JsonObject &obj); //bool toJSON(JsonObject &obj); - void toJSON(JsonResponse &json); - void toJSONRef(JsonResponse &json); - bool linkShade(uint8_t shadeId); bool unlinkShade(uint8_t shadeId); bool hasShadeId(uint8_t shadeId); @@ -485,7 +478,6 @@ struct transceiver_config_t { */ void fromJSON(JsonObject& obj); //void toJSON(JsonObject& obj); - void toJSON(JsonResponse& json); void save(); void load(); void apply(); @@ -500,7 +492,6 @@ class Transceiver { transceiver_config_t config; bool printBuffer = false; //bool toJSON(JsonObject& obj); - void toJSON(JsonResponse& json); bool fromJSON(JsonObject& obj); bool save(); bool begin(); @@ -557,10 +548,6 @@ class SomfyShadeController { SomfyGroup groups[SOMFY_MAX_GROUPS]; bool linkRepeater(uint32_t address); bool unlinkRepeater(uint32_t address); - void toJSONShades(JsonResponse &json); - void toJSONRooms(JsonResponse &json); - void toJSONGroups(JsonResponse &json); - void toJSONRepeaters(JsonResponse &json); uint8_t repeaterCount(); uint8_t roomCount(); uint8_t shadeCount(); diff --git a/src/WResp.cpp b/src/WResp.cpp index 4c3d379..f64c0fa 100644 --- a/src/WResp.cpp +++ b/src/WResp.cpp @@ -1,6 +1,4 @@ #include "WResp.h" -#include -#include void JsonSockEvent::beginEvent(AsyncWebSocket *server, const char *evt, char *buff, size_t buffSize) { this->server = server; this->buff = buff; @@ -35,41 +33,6 @@ void JsonSockEvent::_safecat(const char *val, bool escape) { else strcat(this->buff, val); if(escape) strcat(this->buff, "\""); } -void JsonResponse::beginResponse(WebServer *server, char *buff, size_t buffSize) { - this->server = server; - this->buff = buff; - this->buffSize = buffSize; - this->buff[0] = 0x00; - this->_nocomma = true; - server->setContentLength(CONTENT_LENGTH_UNKNOWN); -} -void JsonResponse::endResponse() { - if(strlen(buff)) this->send(); - server->sendContent("", 0); -} -void JsonResponse::send() { - Serial.println("JsonResponse::send start "); - unsigned long ts = millis(); - esp_task_wdt_reset(); - if(!this->_headersSent) server->send_P(200, "application/json", this->buff); - else server->sendContent(this->buff); - //Serial.printf("Sent %d bytes %d\n", strlen(this->buff), this->buffSize); - this->buff[0] = 0x00; - this->_headersSent = true; - Serial.printf("JsonResponse::send end took %d ms\n", millis() - ts); -} -void JsonResponse::_safecat(const char *val, bool escape) { - size_t len = (escape ? this->calcEscapedLength(val) : strlen(val)) + strlen(this->buff); - if(escape) len += 2; - if(len >= this->buffSize) { - this->send(); - } - if(escape) strcat(this->buff, "\""); - if(escape) this->escapeString(val, &this->buff[strlen(this->buff)]); - else strcat(this->buff, val); - if(escape) strcat(this->buff, "\""); -} - void AsyncJsonResp::beginResponse(AsyncWebServerRequest *request, char *buff, size_t buffSize) { this->_request = request; this->buff = buff; diff --git a/src/WResp.h b/src/WResp.h index 6412c40..7356189 100644 --- a/src/WResp.h +++ b/src/WResp.h @@ -3,8 +3,6 @@ #ifndef wresp_h #define wresp_h -class WebServer; - class JsonFormatter { protected: char *buff; @@ -54,15 +52,6 @@ class JsonFormatter { void addElem(const char *name, const char *val); void addElem(const char* name, uint64_t lval); }; -class JsonResponse : public JsonFormatter { - protected: - void _safecat(const char *val, bool escape = false) override; - public: - WebServer *server; - void beginResponse(WebServer *server, char *buff, size_t buffSize); - void endResponse(); - void send(); -}; class AsyncJsonResp : public JsonFormatter { protected: void _safecat(const char *val, bool escape = false) override; diff --git a/src/Web.cpp b/src/Web.cpp index cc54e17..07994f7 100644 --- a/src/Web.cpp +++ b/src/Web.cpp @@ -63,8 +63,6 @@ void Web::startup() { void Web::loop() { delay(1); } -void Web::end() { -} bool Web::isAuthenticated(AsyncWebServerRequest *request, bool cfg) { Serial.println("Checking async authentication"); if(settings.Security.type == security_types::None) return true; diff --git a/src/Web.h b/src/Web.h index 042db62..7cf3150 100644 --- a/src/Web.h +++ b/src/Web.h @@ -9,7 +9,6 @@ class Web { void startup(); void begin(); void loop(); - void end(); // Auth helpers bool createAPIToken(const IPAddress ipAddress, char *token); bool createAPIToken(const char *payload, char *token); From d0d903d55c112e268f204e4cbf9f60077eee75df Mon Sep 17 00:00:00 2001 From: cjkas Date: Tue, 24 Mar 2026 14:37:08 +0100 Subject: [PATCH 15/16] more human friendly uptime --- data-src/index.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/data-src/index.js b/data-src/index.js index 7a689c6..d2400e8 100644 --- a/data-src/index.js +++ b/data-src/index.js @@ -4335,7 +4335,7 @@ class Firmware { if (typeof overlay !== 'undefined') overlay.remove(); reject({ htmlError: status, service: 'GET /backup' }); }; - xhr.open('GET', baseUrl.length > 0 ? `${baseUrl}/backup` : '/backup', true); + xhr.open('GET', baseUrl.length > 0 ? `${baseUrl}/backup?attach=true` : '/backup?attach=true', true); xhr.send(); }); } @@ -4386,7 +4386,19 @@ class Firmware { sp = document.getElementById('spanMinMemory'); if (sp) sp.innerHTML = mem.min.fmt('#,##0'); sp = document.getElementById('spanUptime'); - if (sp) sp.innerHTML = mem.uptime / 3600000; + if (sp) { + let t = Math.floor(mem.uptime / 1000); + let d = Math.floor(t / 86400); t %= 86400; + let h = Math.floor(t / 3600); t %= 3600; + let m = Math.floor(t / 60); + let s = t % 60; + let parts = []; + if (d > 0) parts.push(d + 'd'); + if (h > 0) parts.push(h + 'h'); + if (m > 0) parts.push(m + 'm'); + if (s > 0 || parts.length === 0) parts.push(s + 's'); + sp.innerHTML = parts.join(' '); + } } From 07e2e45333ae9b149c08eaf93a4a298e8f9ef1dc Mon Sep 17 00:00:00 2001 From: cjkas Date: Tue, 24 Mar 2026 19:35:45 +0100 Subject: [PATCH 16/16] update CI build scripts --- .github/workflows/ci.yaml | 144 ++++-------------------- .github/workflows/release.yaml | 194 +++++++-------------------------- platformio.ini | 51 +++------ 3 files changed, 72 insertions(+), 317 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6b4bec0..fc93223 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -2,150 +2,44 @@ name: ESPSomfy-RTS on: [push, pull_request] -env: - ARDUINO_BOARD_MANAGER_ADDITIONAL_URLS: "https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_dev_index.json" - ARDUINO_CLI_VERSION: "0.x" - ARDUINO_ESP32_VERSION: "2.0.10" - ARDUINO_JSON_VERSION: "6.21.3" - ESPTOOL_VERSION: "4.6" - LITTLEFS_VERSION: "v2.5.1" - MKLITTLEFS_VERSION: "3.1.0" - PUB_SUB_CLIENT_VERSION: "2.8.0" - PYTHON_VERSION: "3.10" - SMARTRC_CC1101_VERSION: "2.5.7" - WEB_SOCKET_VERSION: "2.4.0" - jobs: - littlefs: - name: LittleFS - runs-on: ubuntu-latest - - steps: - - name: Check out code - uses: actions/checkout@v3 - - - name: Checkout mklittlefs - uses: actions/checkout@v3 - with: - repository: earlephilhower/mklittlefs - path: mklittlefs - ref: ${{ env.MKLITTLEFS_VERSION }} - - - name: Checkout LittleFS - uses: actions/checkout@v3 - with: - repository: littlefs-project/littlefs - path: mklittlefs/littlefs - ref: ${{ env.LITTLEFS_VERSION }} - - - name: Build mklittlefs - run: | - make -C mklittlefs - - - name: Create LittleFS - run: | - ./mklittlefs/mklittlefs --create data --size 1441792 SomfyController.littlefs.bin - - - name: Upload binaries - uses: actions/upload-artifact@v3 - with: - name: LittleFS - path: SomfyController.littlefs.bin - retention-days: 5 - - arduino: + build: name: ${{ matrix.name }} - needs: [littlefs] runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - - board: esp32 - addr_bootloader: 0x1000 - chip: ESP32 - fqbn: esp32:esp32:esp32 + - env: esp32dev name: ESP32 - - board: lolin_c3_mini - addr_bootloader: 0x0 - chip: ESP32-C3 - fqbn: esp32:esp32:lolin_c3_mini - name: LOLIN-C3-mini - - board: lolin_s2_mini - addr_bootloader: 0x1000 - chip: ESP32-S2 - fqbn: esp32:esp32:lolin_s2_mini - name: LOLIN-S2-mini - - board: lolin_s3_mini - addr_bootloader: 0x0 - chip: ESP32-S3 - fqbn: esp32:esp32:lolin_s3_mini - name: LOLIN-S3-mini steps: - name: Check out code - uses: actions/checkout@v3 + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 with: - path: SomfyController + python-version: "3.12" - - name: Get LittleFS - uses: actions/download-artifact@v3 - with: - name: LittleFS - - - name: Install Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@v4 - with: - python-version: ${{ env.PYTHON_VERSION }} - - - name: Upgrade pip - run: | - python -m pip install --upgrade pip - pip --version - - - name: Install ESPTool - run: | - pip install esptool==${{ env.ESPTOOL_VERSION }} - - - name: Install Arduino CLI - uses: arduino/setup-arduino-cli@v1 - with: - version: ${{ env.ARDUINO_CLI_VERSION }} - - - name: Configure Arduino CLI - run: | - arduino-cli core update-index - arduino-cli core install esp32:esp32@${{ env.ARDUINO_ESP32_VERSION }} - - - name: Configure Arduino Libraries - run: | - arduino-cli lib install ArduinoJson@${{ env.ARDUINO_JSON_VERSION }} - arduino-cli lib install PubSubClient@${{ env.PUB_SUB_CLIENT_VERSION }} - arduino-cli lib install SmartRC-CC1101-Driver-Lib@${{ env.SMARTRC_CC1101_VERSION }} - arduino-cli lib install WebSockets@${{ env.WEB_SOCKET_VERSION }} + - name: Install PlatformIO + run: pip install platformio - name: Build ${{ matrix.name }} - run: | - mkdir -p build - arduino-cli compile --clean --output-dir build --fqbn ${{ matrix.fqbn }} --warnings default ./SomfyController + run: pio run -e ${{ matrix.env }} - - name: ${{ matrix.name }} Image - run: | - python -m esptool --chip ${{ matrix.chip }} \ - merge_bin -o build/SomfyController.onboard.bin \ - ${{ matrix.addr_bootloader }} build/SomfyController.ino.bootloader.bin \ - 0x8000 build/SomfyController.ino.partitions.bin \ - 0x10000 build/SomfyController.ino.bin \ - 0x290000 SomfyController.littlefs.bin + - name: Build LittleFS image + run: pio run -e ${{ matrix.env }} -t buildfs - - name: Upload ${{ matrix.name }} - uses: actions/upload-artifact@v3 + - name: Upload firmware + uses: actions/upload-artifact@v4 with: name: ${{ matrix.name }} path: | - build/SomfyController.ino.bin - build/SomfyController.ino.bootloader.bin - build/SomfyController.ino.partitions.bin - build/SomfyController.onboard.bin + .pio/build/${{ matrix.env }}/firmware.bin + .pio/build/${{ matrix.env }}/firmware.elf + .pio/build/${{ matrix.env }}/partitions.bin + .pio/build/${{ matrix.env }}/bootloader.bin + .pio/build/${{ matrix.env }}/littlefs.bin retention-days: 5 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index fe6096d..96b5d08 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,27 +1,26 @@ name: ESPSomfy-RTS Release -on: +on: release: types: [published] -env: - ARDUINO_BOARD_MANAGER_ADDITIONAL_URLS: "https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json" - ARDUINO_CLI_VERSION: "0.x" - ARDUINO_ESP32_VERSION: "2.0.17" - ARDUINO_JSON_VERSION: "6.21.5" - ESPTOOL_VERSION: "4.7" - LITTLEFS_VERSION: "v2.5.1" - MKLITTLEFS_VERSION: "3.1.0" - PUB_SUB_CLIENT_VERSION: "2.8.0" - PYTHON_VERSION: "3.10" - SMARTRC_CC1101_VERSION: "2.5.7" - WEB_SOCKET_VERSION: "2.4.0" - jobs: - littlefs: - name: LittleFS + build: + permissions: write-all + name: ${{ matrix.name }} runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - env: esp32dev + name: ESP32 + chip: ESP32 + addr_bootloader: "0x1000" + fwname: SomfyController.esp32.bin + obname: SomfyController.onboard.esp32.bin + steps: - name: Get Release id: get_release @@ -32,34 +31,31 @@ jobs: - name: Check out code uses: actions/checkout@v4 - - name: Checkout mklittlefs - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 with: - repository: earlephilhower/mklittlefs - path: mklittlefs - ref: ${{ env.MKLITTLEFS_VERSION }} + python-version: "3.12" - - name: Checkout LittleFS - uses: actions/checkout@v4 - with: - repository: littlefs-project/littlefs - path: mklittlefs/littlefs - ref: ${{ env.LITTLEFS_VERSION }} + - name: Install PlatformIO + run: pip install platformio - - name: Build mklittlefs + - name: Build firmware + run: pio run -e ${{ matrix.env }} + + - name: Build LittleFS image + run: pio run -e ${{ matrix.env }} -t buildfs + + - name: Create onboard image run: | - make -C mklittlefs + python -m esptool --chip ${{ matrix.chip }} \ + merge_bin -o ${{ matrix.obname }} \ + ${{ matrix.addr_bootloader }} .pio/build/${{ matrix.env }}/bootloader.bin \ + 0x8000 .pio/build/${{ matrix.env }}/partitions.bin \ + 0x10000 .pio/build/${{ matrix.env }}/firmware.bin \ + 0x310000 .pio/build/${{ matrix.env }}/littlefs.bin - - name: Create LittleFS - run: | - ./mklittlefs/mklittlefs --create data --size 1441792 SomfyController.littlefs.bin - - - name: Upload binaries - uses: actions/upload-artifact@v4 - with: - name: LittleFS - path: SomfyController.littlefs.bin - retention-days: 5 + - name: Compress onboard image + run: zip ${{ matrix.obname }}.zip ${{ matrix.obname }} - name: Upload LittleFS uses: shogo82148/actions-upload-release-asset@v1.7.5 @@ -67,129 +63,20 @@ jobs: github_token: ${{ github.token }} upload_url: ${{ steps.get_release.outputs.upload_url }} asset_name: SomfyController.littlefs.bin - asset_path: SomfyController.littlefs.bin + asset_path: .pio/build/${{ matrix.env }}/littlefs.bin overwrite: true - arduino: - permissions: write-all - name: ${{ matrix.name }} - needs: [littlefs] - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - include: - - board: esp32 - addr_bootloader: 0x1000 - chip: ESP32 - fqbn: esp32:esp32:esp32wrover:PartitionScheme=default,FlashMode=qio,FlashFreq=80,UploadSpeed=921600,DebugLevel=none,EraseFlash=none - # esp32:esp32:esp32wrover:PartitionScheme=default,FlashMode=qio,FlashFreq=80,UploadSpeed=921600,DebugLevel=none,EraseFlash=none - name: ESP32 - obname: SomfyController.onboard.esp32.bin - fwname: SomfyController.ino.esp32.bin - - board: esp32c3 - addr_bootloader: 0x0 - chip: ESP32-C3 - fqbn: esp32:esp32:esp32c3:JTAGAdapter=default,CDCOnBoot=cdc,PartitionScheme=default,CPUFreq=160,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,DebugLevel=none,EraseFlash=none - # esp32:esp32:esp32c3:JTAGAdapter=default,CDCOnBoot=default,PartitionScheme=default,CPUFreq=160,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,DebugLevel=none,EraseFlash=none - name: ESP32C3 - obname: SomfyController.onboard.esp32c3.bin - fwname: SomfyController.ino.esp32c3.bin - - board: esp32s2 - addr_bootloader: 0x1000 - chip: ESP32-S2 - fqbn: esp32:esp32:esp32s2:JTAGAdapter=default,CDCOnBoot=cdc,MSCOnBoot=default,DFUOnBoot=default,UploadMode=default,PSRAM=disabled,PartitionScheme=default,CPUFreq=240,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,DebugLevel=none,EraseFlash=none - # esp32:esp32:esp32s2:JTAGAdapter=default,CDCOnBoot=default,MSCOnBoot=default,DFUOnBoot=default,UploadMode=default,PSRAM=disabled,PartitionScheme=default,CPUFreq=240,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,DebugLevel=none,EraseFlash=none - name: ESP32S2 - obname: SomfyController.onboard.esp32s2.bin - fwname: SomfyController.ino.esp32s2.bin - - board: esp32s3 - addr_bootloader: 0x0 - chip: ESP32-S3 - fqbn: esp32:esp32:esp32s3:JTAGAdapter=default,PSRAM=disabled,FlashMode=qio,FlashSize=4M,LoopCore=1,EventsCore=1,USBMode=hwcdc,CDCOnBoot=cdc,MSCOnBoot=default,DFUOnBoot=default,UploadMode=default,PartitionScheme=default,CPUFreq=240,UploadSpeed=921600,DebugLevel=none,EraseFlash=none - # esp32:esp32:esp32s3:JTAGAdapter=default,PSRAM=disabled,FlashMode=qio,FlashSize=4M,LoopCore=1,EventsCore=1,USBMode=hwcdc,CDCOnBoot=cdc,MSCOnBoot=default,DFUOnBoot=default,UploadMode=default,PartitionScheme=default,CPUFreq=240,UploadSpeed=921600,DebugLevel=none,EraseFlash=none - name: ESP32S3 - fwname: SomfyController.ino.esp32s3.bin - obname: SomfyController.onboard.esp32s3.bin - steps: - - name: Get Release - id: get_release - uses: bruceadams/get-release@v1.3.2 - env: - GITHUB_TOKEN: ${{ github.token }} - - - name: Check out code - uses: actions/checkout@v4 - with: - path: SomfyController - - - name: Get LittleFS - uses: actions/download-artifact@v4 - with: - name: LittleFS - - - name: Install Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@v5 - with: - python-version: ${{ env.PYTHON_VERSION }} - - - name: Upgrade pip - run: | - python -m pip install --upgrade pip - pip --version - - - name: Install ESPTool - run: | - pip install esptool==${{ env.ESPTOOL_VERSION }} - - - name: Install Arduino CLI - uses: arduino/setup-arduino-cli@v1 - with: - version: ${{ env.ARDUINO_CLI_VERSION }} - - - name: Configure Arduino CLI - run: | - arduino-cli core update-index - arduino-cli core install esp32:esp32@${{ env.ARDUINO_ESP32_VERSION }} - - - name: Configure Arduino Libraries - run: | - arduino-cli lib install ArduinoJson@${{ env.ARDUINO_JSON_VERSION }} - arduino-cli lib install PubSubClient@${{ env.PUB_SUB_CLIENT_VERSION }} - arduino-cli lib install SmartRC-CC1101-Driver-Lib@${{ env.SMARTRC_CC1101_VERSION }} - arduino-cli lib install WebSockets@${{ env.WEB_SOCKET_VERSION }} - - - name: Build ${{ matrix.name }} - run: | - mkdir -p build${{ matrix.name }} - arduino-cli compile --clean --output-dir build${{ matrix.name }} --fqbn ${{ matrix.fqbn }} --warnings none ./SomfyController - - - name: ${{ matrix.name }} Image - run: | - python -m esptool --chip ${{ matrix.chip }} \ - merge_bin -o ${{ matrix.obname }} \ - ${{ matrix.addr_bootloader }} build${{ matrix.name }}/SomfyController.ino.bootloader.bin \ - 0x8000 build${{ matrix.name }}/SomfyController.ino.partitions.bin \ - 0x10000 build${{ matrix.name }}/SomfyController.ino.bin \ - 0x290000 SomfyController.littlefs.bin - - - name: Upload Firmware ${{ matrix.name }} + - name: Upload firmware uses: shogo82148/actions-upload-release-asset@v1.7.5 with: github_token: ${{ github.token }} upload_url: ${{ steps.get_release.outputs.upload_url }} asset_name: ${{ matrix.fwname }} - asset_path: build${{ matrix.name }}/SomfyController.ino.bin + asset_path: .pio/build/${{ matrix.env }}/firmware.bin + overwrite: true - - name: ${{ matrix.name }} Compress Onboard Image - run: | - zip ${{ matrix.obname }}.zip ./${{ matrix.obname }} - - - name: Upload Onboard ${{ matrix.name }} + - name: Upload onboard image uses: shogo82148/actions-upload-release-asset@v1.7.5 - # env: - # GITHUB_TOKEN: ${{ github.token }} with: github_token: ${{ github.token }} upload_url: ${{ steps.get_release.outputs.upload_url }} @@ -197,4 +84,3 @@ jobs: asset_path: ${{ matrix.obname }}.zip overwrite: true asset_content_type: application/zip - diff --git a/platformio.ini b/platformio.ini index b237059..2141691 100644 --- a/platformio.ini +++ b/platformio.ini @@ -1,64 +1,39 @@ ; PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples ; https://docs.platformio.org/page/projectconf.html [platformio] default_envs = esp32dev -[env:esp32dev] +; Shared settings for all environments +[env] platform = espressif32 -board = esp32dev framework = arduino -lib_deps = +lib_deps = bblanchon/ArduinoJson@^7.2.2 lsatan/SmartRC-CC1101-Driver-Lib@^2.5.7 knolleary/PubSubClient@^2.8 - esp32async/ESPAsyncWebServer @ ^3.10.3 - esp32async/AsyncTCP @ ^3.4.10 + esp32async/ESPAsyncWebServer@^3.10.3 + esp32async/AsyncTCP@^3.4.10 extra_scripts = pre:minify.py board_build.partitions = huge_app.csv board_build.filesystem = littlefs -build_flags = +build_flags = -DCONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=1 -DCONFIG_ESP_COREDUMP_DATA_FORMAT_ELF=1 -DCONFIG_ESP_COREDUMP_CHECKSUM_CRC32=1 -DCONFIG_ESP_TASK_WDT_PANIC=1 -DCONFIG_ESP_COREDUMP_DECODE_INFO=1 monitor_speed = 115200 -monitor_filters = +monitor_filters = time esp32_exception_decoder log2file -[env:esp32devdbg] -build_type = debug -platform = espressif32 +[env:esp32dev] board = esp32dev -framework = arduino -lib_deps = - bblanchon/ArduinoJson@^7.2.2 - links2004/WebSockets@^2.7.3 - lsatan/SmartRC-CC1101-Driver-Lib@^2.5.7 - knolleary/PubSubClient@^2.8 -extra_scripts = pre:minify.py -board_build.partitions = huge_app.csv -board_build.filesystem = littlefs -[env:esp32c3dev] -platform = espressif32 -board = esp32-c3-devkitm-1 -framework = arduino -lib_deps = - bblanchon/ArduinoJson@^7.2.2 - links2004/WebSockets@^2.7.3 - lsatan/SmartRC-CC1101-Driver-Lib@^2.5.7 - knolleary/PubSubClient@^2.8 -extra_scripts = pre:minify.py -board_build.partitions = min_spiffs.csv -board_build.filesystem = littlefs +[env:esp32devdbg] +board = esp32dev +build_type = debug + +