diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f2884eac2..6012bb07f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,10 +23,10 @@ jobs: documentation: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: - python-version: '3.x' + python-version: '3.13' - name: Build documentation run: | cd docs @@ -37,7 +37,7 @@ jobs: make html cd .. zip -r -qq ESPEasy_docs.zip docs/build/* - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v6 with: name: Documentation path: ESPEasy_docs.zip @@ -46,10 +46,10 @@ jobs: outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: - python-version: '3.x' + python-version: '3.13' - id: set-matrix run: | pip install uv @@ -62,10 +62,10 @@ jobs: strategy: matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: - python-version: '3.x' + python-version: '3.13' - name: Dependencies run: | sudo apt-get update @@ -88,7 +88,7 @@ jobs: uses: Entepotenz/change-string-case-action-min-dependencies@v1 with: string: ${{ matrix.chip }} - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v6 with: # FIXME Workaround to (temporarily) not use # in the artifact name, see https://github.com/actions/upload-artifact/issues/473 name: Bin-${{ steps.string.outputs.uppercase }}-${{ matrix.env }}-${{ steps.date.outputs.builddate }}_PR_${{ github.event.number }}_${{ github.run_id }} # Sort by ESP type @@ -101,16 +101,16 @@ jobs: needs: build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: - python-version: '3.x' + python-version: '3.13' - name: Get current date id: date run: | echo "builddate=$(date +'%Y%m%d')" >> $GITHUB_OUTPUT - name: Download all artifacts - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v7 with: path: artifacts/Binaries/bin/ pattern: Bin-* @@ -119,7 +119,7 @@ jobs: run: | cd artifacts/Binaries/ ls -R - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v6 with: # FIXME Workaround to (temporarily) not use # in the artifact name, see https://github.com/actions/upload-artifact/issues/473 name: ESPEasy-all-Binaries-${{ steps.date.outputs.builddate }}_PR_${{ github.event.number }}_${{ github.run_id }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3093a4dff..e8a70cdf9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,10 +21,10 @@ jobs: outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: - python-version: '3.x' + python-version: '3.13' - id: set-matrix run: | pip install uv @@ -37,8 +37,10 @@ jobs: strategy: matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-python@v6 + with: + python-version: '3.13' - name: Dependencies run: | sudo apt install binutils build-essential libffi-dev libgit2-dev @@ -56,7 +58,7 @@ jobs: uses: Entepotenz/change-string-case-action-min-dependencies@v1 with: string: ${{ matrix.chip }} - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v6 with: name: Bin-${{ steps.string.outputs.uppercase }}-${{ matrix.env }} path: | @@ -67,8 +69,10 @@ jobs: needs: build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-python@v6 + with: + python-version: '3.13' - name: Build documentation run: | cd docs @@ -83,7 +87,7 @@ jobs: cd dist zip -r -qq ../ESPEasy_dist.zip * cd .. - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v6 with: name: Distribution path: | @@ -121,19 +125,19 @@ jobs: steps: - uses: actions/setup-python@v6 with: - python-version: '3.x' + python-version: '3.13' - name: Get current date id: date run: | echo "builddate=$(date +'%Y%m%d')" >> $GITHUB_OUTPUT - name: Download all successfully compiled artifacts - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v7 with: path: artifacts/bin/ pattern: Bin-* merge-multiple: true - name: Download dist and docs zip files - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v7 with: path: distribution/ name: Distribution @@ -150,6 +154,7 @@ jobs: find . -name '*ESP32c3*' -print | zip -@ ../ESPEasy_ESP32c3.zip find . -name '*ESP32s3*' -print | zip -@ ../ESPEasy_ESP32s3.zip find . -name '*ESP32c2*' -print | zip -@ ../ESPEasy_ESP32c2.zip + find . -name '*ESP32c5*' -print | zip -@ ../ESPEasy_ESP32c5.zip find . -name '*ESP32c6*' -print | zip -@ ../ESPEasy_ESP32c6.zip find . -name '*ESP32solo1*' -print | zip -@ ../ESPEasy_ESP32solo1.zip find . -name '*ESP32_*' -print | zip -@ ../ESPEasy_ESP32.zip @@ -167,6 +172,7 @@ jobs: zipmerge ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32c3_binaries.zip ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32_binaries.zip ESPEasy_ESP32c3.zip zipmerge ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32s3_binaries.zip ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32_binaries.zip ESPEasy_ESP32s3.zip zipmerge ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32c2_binaries.zip ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32_binaries.zip ESPEasy_ESP32c2.zip + zipmerge ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32c5_binaries.zip ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32_binaries.zip ESPEasy_ESP32c5.zip zipmerge ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32c6_binaries.zip ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32_binaries.zip ESPEasy_ESP32c6.zip zipmerge ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32solo1_binaries.zip ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32_binaries.zip ESPEasy_ESP32solo1.zip zipmerge ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32_binaries.zip ESPEasy_ESP32.zip @@ -174,6 +180,6 @@ jobs: with: # Upload all separately supported CPU models and the docs zip # TODO if/when available: ESP32h2 - artifacts: "ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP82xx_binaries.zip,ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32_binaries.zip,ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32solo1_binaries.zip,ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32s2_binaries.zip,ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32c3_binaries.zip,ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32s3_binaries.zip,ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32c2_binaries.zip,ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32c6_binaries.zip,distribution/*.zip" + artifacts: "ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP82xx_binaries.zip,ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32_binaries.zip,ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32solo1_binaries.zip,ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32s2_binaries.zip,ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32c3_binaries.zip,ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32s3_binaries.zip,ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32c2_binaries.zip,ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32c5_binaries.zip,ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32c6_binaries.zip,distribution/*.zip" body: ${{ needs.prepare-notes.outputs.notes }} token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 71a3dc59c..08840c545 100644 --- a/.gitignore +++ b/.gitignore @@ -72,19 +72,22 @@ docs/source/Plugin/_plugin_sets_overview.repl .platformio/ .pio/ - -managed_components/ + +managed_components/ CMakeLists.txt -sdkconfig.max_ESP32s3_16M8M_LittleFS_OPI_PSRAM_ETH +sdkconfig.max_ESP32s3_16M8M_OPI_PSRAM sdkconfig.defaults -dependencies.lock +dependencies.lock + +.dummy/ + +.cache/ + +src/src/CustomBuild/CompiletimeDefines_generated.h + -.dummy/ - -.cache/ - -src/src/CustomBuild/CompiletimeDefines_generated.h +sdkconfig.normal_ESP32solo1_4M316k diff --git a/README.md b/README.md index 7e170d399..15dca94db 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,8 @@ ESPEasy_mega\_*[releasedate]*\_*[build-type]*\_*[opt-arduino-library]*\_*[hardwa *[build-type]* can be any of: Build type | Description | included plugins | -------------|-------------------------------------------|----------------------------------| -climate | All plugins related to climate measurement| Stable + Climate | +climate A | Plugins related to climate measurement A | Stable + Climate A | +climate B | Plugins related to climate measurement B | Stable + Climate B | custom | Custom predefined set/Defined in Custom.h | Specific | normal | Standard plugins | Stable | collection_A | Normal + plugin collection A | Stable + Collection base + set A | @@ -50,9 +51,11 @@ collection_D | Normal + plugin collection D | Stable + Collection b collection_E | Normal + plugin collection E | Stable + Collection base + set E | collection_F | Normal + plugin collection F | Stable + Collection base + set F | collection_G | Normal + plugin collection G | Stable + Collection base + set G | +collection_H | Normal + plugin collection H | Stable + Collection base + set H | max | All available plugins | All available | energy | All plugins related to energy measurement | Stable + Energy measurement | -display | All plugins related to displays | Stable + Displays | +display A | All plugins related to displays A | Stable + Displays A | +display B | All plugins related to displays B | Stable + Displays B | neopixel | All plugins related to neopixel | Stable + Neopixel | hard | hardware specific builds | Minimal | minimal | minimal plugins for specific use-cases | Switch and Controller | @@ -112,6 +115,8 @@ Flash size | Description | N.B. Starting with release 2023/12/25, All ESP32 LittleFS builds use IDF 5.3, to support newer ESP32 chips like ESP32-C2 and ESP32-C6, and SPI Ethernet. Other SPIFFS based ESP32 builds will be migrated to LittleFS as SPIFFS is no longer officially available in IDF 5 and later. As a temporary solution, a specially crafted IDF 5.1 build that still includes SPIFFS, is used for the SPIFFS builds. A migration plan will be made available in 2025. +N.B.2 Starting with builds made after 2025/11/04, ESP32 builds will no longer have ``_LittleFS`` in the name as all ESP32 builds use LittleFS. Also the suffix ``_ETH`` has been removed since all builds will have Ethernet support, except for ESP32C2 builds. + *[opt-build-features]* can be any of: Build features | Description | ----------------|-----------------------------------------------------------------------------------------------------------| @@ -129,18 +134,18 @@ noOTA/NO_OTA | Does not support OTA (Over The Air-updating of the firmware) U N.B. Starting ca. 2025/02/27, many ESP32 builds are *only* available with _ETH suffix, indicating that Ethernet support is enabled, to reduce the (rather high) number of builds. Some example firmware names: -Firmware name | Hardware | Included plugins | -----------------------------------------------------------------------|-------------------------------------------------|----------------------------------| -ESPEasy_mega-20230822_normal_ESP8266_1M.bin | ESP8266/ESP8285 with 1MB flash | Stable | -ESPEasy_mega-20230822_normal_ESP8266_4M1M.bin | ESP8266 with 4MB flash | Stable | -ESPEasy_mega-20230822_collection_A_ESP8266_4M1M.bin | ESP8266 with 4MB flash | Stable + Collection base + set A | -ESPEasy_mega-20230822_normal_ESP32_4M316k_ETH.bin | ESP32 with 4MB flash | Stable | -ESPEasy_mega-20230822_collection_A_ESP32_4M316k_ETH.bin | ESP32 with 4MB flash | Stable + Collection base + set A | -ESPEasy_mega-20230822_collection_B_ESP32_4M316k_ETH.bin | ESP32 with 4MB flash | Stable + Collection base + set B | -ESPEasy_mega-20230822_max_ESP32s3_8M1M_LittleFS_ETH.bin | ESP32-S3 with 8MB flash, CDC-serial, Ethernet | All available plugins | -ESPEasy_mega-20230822_max_ESP32s3_8M1M_LittleFS_OPI_PSRAM_ETH.bin | ESP32-S3 8MB flash, PSRAM, CDC-serial, Ethernet | All available plugins | -ESPEasy_mega-20230822_max_ESP32_16M1M_ETH.bin | ESP32 with 16MB flash, SPIFFS, Ethernet | All available plugins | -ESPEasy_mega-20230822_max_ESP32_16M8M_LittleFS_ETH.bin | ESP32 with 16MB flash, LittleFS, Ethernet | All available plugins | +Firmware name | Hardware | Included plugins | +--------------------------------------------------------|-------------------------------------------------|----------------------------------| +ESPEasy_mega-20230822_normal_ESP8266_1M.bin | ESP8266/ESP8285 with 1MB flash | Stable | +ESPEasy_mega-20230822_normal_ESP8266_4M1M.bin | ESP8266 with 4MB flash | Stable | +ESPEasy_mega-20230822_collection_A_ESP8266_4M1M.bin | ESP8266 with 4MB flash | Stable + Collection base + set A | +ESPEasy_mega-20230822_normal_ESP32_4M316k_ETH.bin | ESP32 with 4MB flash | Stable | +ESPEasy_mega-20230822_collection_A_ESP32_4M316k_ETH.bin | ESP32 with 4MB flash | Stable + Collection base + set A | +ESPEasy_mega-20230822_collection_B_ESP32_4M316k_ETH.bin | ESP32 with 4MB flash | Stable + Collection base + set B | +ESPEasy_mega-20230822_max_ESP32s3_8M1M.bin | ESP32-S3 with 8MB flash, CDC-serial, Ethernet | All available plugins | +ESPEasy_mega-20230822_max_ESP32s3_8M1M_OPI_PSRAM.bin | ESP32-S3 8MB flash, PSRAM, CDC-serial, Ethernet | All available plugins | +ESPEasy_mega-20230822_max_ESP32_16M1M_ETH.bin | ESP32 with 16MB flash, SPIFFS, Ethernet | All available plugins | +ESPEasy_mega-20230822_max_ESP32_16M8M.bin | ESP32 with 16MB flash, LittleFS, Ethernet | All available plugins | The binary files for the different ESP32 variants (S2, C3, S3, C2, C6, Solo1, 'Classic') are available in separate archives. diff --git a/boards/esp32_solo1_4M.json b/boards/esp32_solo1_4M.json index 515e06d89..c8b353ab5 100644 --- a/boards/esp32_solo1_4M.json +++ b/boards/esp32_solo1_4M.json @@ -32,7 +32,9 @@ }, "espidf": { "custom_sdkconfig": [ - "CONFIG_FREERTOS_UNICORE=y" + "CONFIG_FREERTOS_UNICORE=y", + "# CONFIG_BOOTLOADER_SPI_CUSTOM_WP_PIN is not set", + "CONFIG_BOOTLOADER_SPI_WP_PIN=7" ] }, "url": "https://en.wikipedia.org/wiki/ESP32", diff --git a/boards/esp32c5cdc-8M.json b/boards/esp32c5cdc-8M.json new file mode 100644 index 000000000..42ae430a3 --- /dev/null +++ b/boards/esp32c5cdc-8M.json @@ -0,0 +1,40 @@ +{ + "build": { + "arduino":{ + "ldscript": "esp32c5_out.ld" + }, + "core": "esp32", + "extra_flags": "-DARDUINO_TASMOTA -DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=1 -DUSE_USB_CDC_CONSOLE -DESP32_8M -DESP32C5 -DARDUINO_USB_CDC_ON_BOOT=1", + "f_cpu": "160000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "mcu": "esp32c5", + "variant": "esp32c5", + "partitions": "boards/partitions/esp32_partition_app3520k_spiffs1088k.csv" + }, + "connectivity": [ + "wifi", + "bluetooth" + ], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": [ + "esp-builtin" + ], + "openocd_target": "esp32c5.cfg" + }, + "frameworks": [ + "arduino", + "espidf" + ], + "name": "Espressif Generic ESP32-C5 >= 8M Flash, ESPEasy 3520k Code/OTA 1088k FS", + "upload": { + "flash_size": "8MB", + "maximum_ram_size": 327680, + "maximum_size": 8388608, + "require_upload_port": true, + "speed": 460800 + }, + "url": "https://docs.espressif.com/projects/espressif-esp-dev-kits/en/latest/esp32c5/esp32-c5-devkitc-1/index.html", + "vendor": "Espressif" + } diff --git a/boards/esp32c5cdc.json b/boards/esp32c5cdc.json new file mode 100644 index 000000000..312d32556 --- /dev/null +++ b/boards/esp32c5cdc.json @@ -0,0 +1,40 @@ +{ + "build": { + "arduino":{ + "ldscript": "esp32c5_out.ld" + }, + "core": "esp32", + "extra_flags": "-DARDUINO_TASMOTA -DARDUINO_USB_MODE=1 -DUSE_USB_CDC_CONSOLE -DESP32_4M -DESP32C5 -DARDUINO_USB_CDC_ON_BOOT=1", + "f_cpu": "160000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "mcu": "esp32c5", + "variant": "esp32c5", + "partitions": "boards/partitions/esp32_partition_app1810k_spiffs316k.csv" + }, + "connectivity": [ + "wifi", + "bluetooth" + ], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": [ + "esp-builtin" + ], + "openocd_target": "esp32c5.cfg" + }, + "frameworks": [ + "arduino", + "espidf" + ], + "name": "Espressif Generic ESP32-C5 >= 4M Flash, ESPEasy 1810k Code/OTA 316k FS", + "upload": { + "flash_size": "4MB", + "maximum_ram_size": 327680, + "maximum_size": 4194304, + "require_upload_port": true, + "speed": 460800 + }, + "url": "https://docs.espressif.com/projects/espressif-esp-dev-kits/en/latest/esp32c5/esp32-c5-devkitc-1/index.html", + "vendor": "Espressif" + } diff --git a/boards/esp32c61cdc-8M.json b/boards/esp32c61cdc-8M.json new file mode 100644 index 000000000..be0ed214d --- /dev/null +++ b/boards/esp32c61cdc-8M.json @@ -0,0 +1,40 @@ +{ + "build": { + "arduino":{ + "ldscript": "esp32c61_out.ld" + }, + "core": "esp32", + "extra_flags": "-DARDUINO_TASMOTA -DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=1 -DUSE_USB_CDC_CONSOLE -DESP32_8M -DESP32C61 -DARDUINO_USB_CDC_ON_BOOT=1", + "f_cpu": "160000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "mcu": "esp32c61", + "variant": "esp32c61", + "partitions": "boards/partitions/esp32_partition_app3520k_spiffs1088k.csv" + }, + "connectivity": [ + "wifi", + "bluetooth" + ], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": [ + "esp-builtin" + ], + "openocd_target": "esp32c61.cfg" + }, + "frameworks": [ + "arduino", + "espidf" + ], + "name": "Espressif Generic ESP32-C61 >= 8M Flash, ESPEasy 3520k Code/OTA 1088k FS", + "upload": { + "flash_size": "8MB", + "maximum_ram_size": 327680, + "maximum_size": 8388608, + "require_upload_port": true, + "speed": 460800 + }, + "url": "https://docs.espressif.com/projects/espressif-esp-dev-kits/en/latest/esp32c6/esp32-c6-devkitc-1/index.html", + "vendor": "Espressif" + } diff --git a/boards/esp32p4_ev.json b/boards/esp32p4_ev.json new file mode 100644 index 000000000..cc842a28b --- /dev/null +++ b/boards/esp32p4_ev.json @@ -0,0 +1,39 @@ +{ + "build": { + "core": "esp32", + "extra_flags": [ + "-DARDUINO_TASMOTA -DESP32P4 -DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=1 -DUSE_USB_CDC_CONSOLE" + ], + "f_cpu": "360000000L", + "f_flash": "80000000L", + "f_psram": "200000000L", + "flash_mode": "qio", + "mcu": "esp32p4", + "chip_variant": "esp32p4_es", + "variant": "esp32p4", + "partitions": "boards/partitions/esp32_partition_app4096k_spiffs8124k.csv" + }, + "connectivity": [ + "wifi", + "bluetooth", + "openthread", + "ethernet" + ], + "debug": { + "openocd_target": "esp32p4.cfg" + }, + "frameworks": [ + "arduino", + "espidf" + ], + "name": "Espressif ESP32-P4 Function EV Board 16M Flash, ESPEasy 4096k Code/OTA 8M FS", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 768000, + "maximum_size": 16777216, + "require_upload_port": true, + "speed": 1500000 + }, + "url": "https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32p4/esp32-p4-function-ev-board/index.html", + "vendor": "Espressif" +} \ No newline at end of file diff --git a/boards/esp32s2cdc.json b/boards/esp32s2cdc.json index 1f71388af..b53dd82e4 100644 --- a/boards/esp32s2cdc.json +++ b/boards/esp32s2cdc.json @@ -5,7 +5,7 @@ "memory_type": "dio_qspi" }, "core": "esp32", - "extra_flags": "-DARDUINO_TASMOTA -DBOARD_HAS_PSRAM -DESP32_4M -DESP32S2 -DCONFIG_IDF_TARGET_ESP32S2=1 -DUSE_USB_CDC_CONSOLE -DARDUINO_USB_CDC_ON_BOOT=1", + "extra_flags": "-DARDUINO_TASMOTA -DBOARD_HAS_PSRAM -DESP32_4M -DESP32S2 -DCONFIG_IDF_TARGET_ESP32S2=1 -DUSE_USB_CDC_CONSOLE -DARDUINO_USB_CDC_ON_BOOT=1 -DCONFIG_TINYUSB_CDC_ENABLED=1", "f_cpu": "240000000L", "f_flash": "80000000L", "flash_mode": "dio", diff --git a/boards/esp32s3cdc-qio_opi-16M.json b/boards/esp32s3cdc-qio_opi-16M.json index 3693d158e..22afb4bb5 100644 --- a/boards/esp32s3cdc-qio_opi-16M.json +++ b/boards/esp32s3cdc-qio_opi-16M.json @@ -5,10 +5,11 @@ "memory_type": "qio_opi" }, "core": "esp32", - "extra_flags": "-DARDUINO_TASMOTA -DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=1 -DUSE_USB_CDC_CONSOLE -DESP32_16M -DESP32S3 -DARDUINO_USB_CDC_ON_BOOT=1", "f_cpu": "240000000L", "f_flash": "80000000L", + "boot": "opi", "flash_mode": "qio", + "extra_flags": "-DARDUINO_TASMOTA -DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=1 -DUSE_USB_CDC_CONSOLE -DESP32_16M -DESP32S3 -DARDUINO_USB_CDC_ON_BOOT=1", "hwids": [ [ "0x303A", diff --git a/boards/esp8266_160MHz_4M1M_board.json b/boards/esp8266_160MHz_4M1M_board.json new file mode 100644 index 000000000..01bddc562 --- /dev/null +++ b/boards/esp8266_160MHz_4M1M_board.json @@ -0,0 +1,31 @@ +{ + "build": { + "arduino": { + "ldscript": "eagle.flash.4m1m.ld" + }, + "core": "esp8266", + "extra_flags": "-DARDUINO_TASMOTA -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_ESP01 -DESP8266_4M -DESP8266_4M1M", + "f_cpu": "120000000L", + "f_flash": "40000000L", + "flash_mode": "dout", + "mcu": "esp8266", + "variant": "generic" + }, + "connectivity": [ + "wifi" + ], + "frameworks": [ + "arduino" + ], + "name": "Espressif Generic ESP8266 ESPEasy 4M Flash 1M FS", + "upload": { + "maximum_ram_size": 81920, + "maximum_size": 1040316, + "require_upload_port": true, + "resetmethod": "nodemcu", + "speed": 115200 + }, + "url": "http://www.esp8266.com/wiki/doku.php?id=esp8266-module-family", + "vendor": "Espressif" + } + \ No newline at end of file diff --git a/dist/Release_notes.txt b/dist/Release_notes.txt index e68f18a93..950d93f86 100644 --- a/dist/Release_notes.txt +++ b/dist/Release_notes.txt @@ -1,3 +1,557 @@ +------------------------------------------------- +Changes in release mega-20260108 (since mega-20260107) +------------------------------------------------- + +Release date: Thu Jan 8 08:33:12 AM CET 2026 + +TD-er (1): + Add Python version for prepare-dist in release.yml + + +------------------------------------------------- +Changes in release mega-20260107 (since mega-20250430) +------------------------------------------------- + +Release date: Wed Jan 7 11:00:02 PM CET 2026 + +Bartłomiej Zimoń (4): + Update Flashing.rst + Update Flashing.rst + Update Flashing.rst + Update Flashing.rst + +Fabio Ancona (2): + Update LD2410 doc adding info of false positive + Update P159.rst + +Jason2866 (8): + remove cache use uv + Update build.yml + Update post_esp32.py + Update post_esp32.py + add forgotten function + missing import os + Update post_esp32.py + remove cache use uv + +TD-er (55): + [Favicon] Serve favicon.ico as inline SVG + [settings] Reduce initial default config.dat to 96k on ESP32 + [JSON] Tweak generating JSON via web requests + [Favicon] Tweak blue color in SVG ESPEasy favicon + [Time] Speedup printing date/time string + [Build] Remove TAR support from LIMIT_BUILD_SIZE builds (saves 5+ kB) + [OLED display] Reduce build size for P023 + [SysVarPage] Fix table layout on builds with excludes features + [SysVarPage] Make more clear separation for "Standard Conversions" + [Sysvar] Enable %c_random% for all builds + [ESP32-S2] Change ADC for ESP32-S2 back to 13 bit + [ESPtool] Prepare use of esptool v5.0.0 + [Build] Get rid of all ESP32-SPIFFS builds + [ESPtool] Prepare use of esptool v5.0.0 + [ESPEasy p2p] Add node-IDs for new Espressif SoC models + [ADC] Fix not setting attenuation on internal ADC + [Build] Update to latest IDF5.4 build + add C6 builds + [ESP32-S3] Revert changes in board defines for ESP32-S3 with OPI PSRAM + [Build] Speed up ESP32 builds via CompiletimeDefines_generated.h + [WebFlasher] Add missing ESP32-xx MCUs to manifest script + [Build] Speed up ESP32 builds via CompiletimeDefines_generated.h + [WebFlasher] Add missing ESP32-xx MCUs to manifest script + [Rules] Fix processing variables with index > 2^31 + [MFRC522] Speed-up detecting cards + make error recovery more stable + [MFRC522] Improve range of reader + [MFRC522] Simplify board reset during init + [MFRC522] Allow to dump card info to debug logs + [MFRC522] Do proper timing for communicating with card + [MFRC522] Speedup reading by acting on interrupt pin + Add HTML_Print and PrintToString helpers + [Log] Add PrintToLog helper + [PWM motor] Speedup acting on limit switch interrupt + [ESP-IDF] Update to latest SDK version + [WebFlasher] Add more info on migrate to LittleFS on web flasher page + [WebFlasher] Update LittleFS migration guide in webflasher page + [PWM motor] Fix limit switch invert (again) + [PubSubClient] Update to new fork PubSubClient3 + [PubSubClient3] Speedup publish large messages using buffer + [PubSubClient3] Add check for nullptr in CHECK_STRING_LENGTH + [Controller] Fix crashes on MQTT callback calls + [WebServer] Properly close chunked transfer + [Cleanup] Fix JS issues + correctly end chunked transfer + [Build] Try fixing failing solo1 build + [ESP32-U4WDH] Fix bootloop on non-Solo1 builds + Fix missing #ifdef USES_P056 + [Build] Fix missing #ifndef LIMIT_BUILD_SIZE + [SysVars] Fix showing variables with hex number as index + [MQTTDiscovery] Reduce mem alloc MQTT_DiscoveryPublishWithStatusAndSet + Reduce ESP32 Display build size (BUILD_NO_DEBUG) + [CacheReader] Fix continue reading after crash/reboot + [CacheReader] Fix reading cache files from CacheController + [CacheReader] Add support for 64bit ints + output as events + [CacheReader] Fix incorrect settings array index + [Build] Limit package building to use Python 3.13 + [Collection] Remove P038 and P095 from ESP32 Collection builds + +Ton Huisman (369): + [P073] Add support for 74HC595 displays + [P073] Disable debug log + [P073] Make build fit again (part 1) + [P073] Make build fit again (part 2) + [P094] Remove from Collection D for ESP8266 buildsize + [P073] Move most code to PluginStruct files, explicit compile-time defines + [P073] Make 7-segment font functions publicly available + [P073] Separate 74HC595 multiplexed displays, code improvements + [P073] Update documentation + [P073] Disable 74HC595 displays for now + [P073] Add double-buffering for 74HC595 displays + [P073] Make some functions available externally, fix 74HC595 matrix display (sort-of, still some flickering) + [P073] Fine-tuning and allow to include 74HC595 in ESP8266 Custom builds + [P073] Make P073_revert7bits() available for global use + [P073] Fix copy/paste error + [P073] Use more efficient bit-revert algorithm, as suggested + [P073] Add option for time-blinking the second dot instead of the colon + [P073] Use DIRECT_PIN_IO for TM1637 displays for better timing accuracy + [P073] Update documentation + [P073] Should use the public function + [P165] Fix compiler warning + [MQTT] (Auto)Discovery implementation + [P052] Add MQTT Discovery data for supported Values + [Source] Fix CR/LF vs LF + [MQTT] Add support for more VTypes, implement more VTypes, small improvements + [MQTT] Move TaskValue VType Discovery to each plugin where needed. Some other code improvements + [P105] Fix Uncrustify formatting + [P164] Add fixme for currently unsupported AQI Discovery value + [Discovery] Add VType for Reactive Power + [Discovery] Implement VType for Reactive Power + [P076] Implement Discovery support (for user-selected values) + [P077] Implement Discovery support (for user-selected values) + [P078] Implement Discovery support (for user-selected values) + [P085] Implement Discovery support (for user-selected values) + [P102] Implement Discovery support (for user-selected values) + [P108] Implement Discovery support (for user-selected values) + [P115] Implement Discovery support (for user-selected values) + [P132] Implement Discovery support (for user-selected values) + [P142] Implement Discovery support (for user-selected values) + [Discovery] Refactor getting VType per plugin, add some more plugins for AutoDiscovery + [Discovery] Add support for some more VTypes, add AQI and NOx + [P147] Implement MQTT Discovery support + [P164] Update MQTT Discovery support + [EasyColorCode] Updates April 2025 + [P020] Update documentation + [P116] Fix typo in docs + [P180] Add plugin Generic I2C + [P180] Add also to Display, Energy and Neopixel collection builds (ESP32 only) + [P180] No defaults for I2C address or number of values + [P180] Cleanup memory after use, fix ESP8266 compilation + [P180] Cleanup memory after use, fix ESP8266 compilation + [Rules] Fix parsing bracketed expressions from position 0 + [P180] Add conditionally cancelling a command sequence (if) + [Rules] Add support for crc8 and crc32 functions + [P180] Optimize hex formatting, add preprocessed calculation to logging + [Rules] Add optional validation of crc to crc8 and crc32 functions + [EasyColorCode] Add more plugin commands + [P180] Correct regular value handling, improved hex formatting + [P180] Add documentation and usage examples + [P180] Add exec subcommand, add valuename support, fix issue updating command-cache + [P180] Add handling 1/sec, 10/sec and 50/sec event processing, add log subcommand + [P180] Update documentation and usage examples + [P180] Update documentation and usage examples + [EasyColorCode] Update commands for P180 + [Docs] Add examples for substring for translation of day/month names + [P180] Add let I2C Command, limit delay to max 10ms for 1ps/10ps/50ps + [P180] Add to Custom-sample.h + Bugfix [P075] Typo in check for RxD GPIO pin + [P180] Refactor into BusCmd, phase 1 + [P021] Bugfix: Make Invert work without a GPIO pin selected + [P180] Refactor into BusCmd_Handler interface, phase 2, Add String support + [P180] Remove previously added but unused I2C functions + [P061] Resolve I2C related FIXMEs, gaining ~120 bytes + [P180] Update documentation + [P011] Optimize I2C calls, gaining ~110 bytes + [Discovery] Add /config and Retain Discovery settings to MQTT Controller with AutoDiscovery + [Discovery] Fix Switch auto-discovery [Discovery] Add support for Inverted Switch [Discovery] Add support for Switch Device Class configuration + [P001] Implement Switch MQTT Device Class setting and Inverted Switch discovery + [P009] Implement Switch MQTT Device Class setting and Inverted Switch discovery + [P001][P009] Correct typo + [P019] Implement Switch MQTT Device Class setting and Inverted Switch discovery + [P021] Implement Switch MQTT Device Class setting and Inverted Switch discovery + [Discovery] Add support for different Switch device class per value + [Rules] Add support for String type variables + [StringVars] Fix variable typo + [StringVars] Remove unneeded UnaryOperator::IsNum + [StringVars] Fix new introduced template parser bug + [StringVars] Make string comparisons work with `if` + Add new commands and functions for String variables support + [SysVars] Make second argument for 2-argument conversions optional + [StringVars] Add documentation + [StringVars] Add documentation for [length#var] + [Bugfix] String commands parser fix off-by-one issue + [Rules] Add string function Lookup with docs + [Rules] Add conversion function %c_strf%() with docs + [Rules] Fix typo in docs + [StringVars] Add TaskValueSetDerived, TaskValueSetPresentation commands and handling + [StringVars] Add TaskValueSetDerived, TaskValueSetPresentation docs + [Docs] Move Data Acquisition to separate include file + [StringVars] Avoid calculation of formula with presentation prefix ($) + [StringVars] Improved handling of calculated values for Derived values + [SysVars, Conversions] Add %localunixtime%, %systzoffset_s% sysvars and %c_ts2date%() conversion + docs + [SysVars] Exclude %localunixtime% from Limited builds + [SysVars] Rename %localunixtime% to %unixtime_lcl% + [DerivedValues] Add Event & Log option and generate events and logging for Derived values, update docs + [SysVars] Add %c_ts2wday%(timestamp) (dow) conversion and docs + [EasyColorCode] Add more new commands and functions for String variables support + [P180] Add GET_CONFIG_VAR support + [Discovery] Fix typo in BinarySensorInverted helper + [Docs] Update WSL2 USB Serial configuration since Linux 6.x core update + [Feature] Add Unit of Measure for all taskvalues and present on Devices overview page + [UoM] Add Unit of measure value to /json endpoint + [UoM] Add Unit of measure value Devices overview page update script (also uses Presentation if available) + [Build] Reduce size of included Github clipboard script + [Build] Include reboot.js script in readable form + [Build] Reduce size of included Fetch and parse logs script + [UoM] Add Unit of measure value Devices overview page update script (also uses Presentation if available) + [JSON] Add Presentation as separate value instead of replacing Value (using updated .js scrip) + [UoM] Make .js script more fault tolerant when no global UoM enable setting is available + [DerivedValues] Don't determine max decimals for display but use default + [DerivedValues] Add optional Unit of Measure and include in /json output + [UoM] Make .js script more fault tolerant when no global UoM enable setting is available + [UoM] Optional default when fetching Unit of Measure + [Discovery] Implement UoM per taskValue when available + [I2C] Show I2C Bus nr in Not enabled message of i2cscanner + [I2C] Add a few missing I2C device addresses for INA219 + [UoM] Add Feature TaskValue-attributes with support for .uom, .decimals, .hasformula and .statsenabled + [DerivedValues] Move parsing derived variables to correct code-location + [DerivedValues] Add TaskValue attribute support for DerivedValues (empty is not valid) + [Discovery] Add default empty option for Device Class selector (all values shift 1 index up!) + [P116] Add support for ST7735/ST7789 170x320 displays + [SysVarPage] Exclude c_random from list for limited builds + [UoM] Add documentation + [UoM] Update documentation + [UoM] Update documentation + [VTypes] Refactor SensorValueType selector so it can be used separately + [VTypes] Refactor SensorValueType selector so it can be used separately (cherry picked) + [ValueTypes] Add Custom VType selection + [Plugins] Add CustomVTypeVar option for plugins that need custom Value Type selection + [DerivedValues] Add support for Value Type argument + [AutoDiscovery] Add support for Derived values, UoM and Value Type, some code optimizations + [C006] Add support for sending derived values + [C014] Add Unit of Measure to AutoDiscovery, add Send Derived values support + [Docs] Updates and improvements, Summer '25 + [Docs] Update docs for Value Type setting for selected plugins + [Docs] Move Send derived values as a per controller setting for tasks + [Derived Values] Move enable sending per task-controller, Code optimization + [C014] Docs: Update UoM feature + [P027] Docs: Fix a few typos + [P180] Improvements and fixes in handling of signed values + [Docs] Update/extend the SPIFFS to LittleFS migration procedure + [Docs] Update/extend the SPIFFS to LittleFS migration procedure + [Docs] Extract SPIFFS to LittleFS migration guide for accessability + [Docs] Expand SPIFFS to LittleFS migration guide, add troubleshooting section + [AutoDiscovery] Add support for Wind direction and Wind speed + [Plugins] Minor code improvement + [UoM] Restructure UoM selector into categories, add more uom options + [UoM] Fix UoM selector empty value + [Docs] Update Unit of Measure documentation and screenshots + [AutoDiscovery] Embed setting for `online` message in `/config` setting + [Docs] Update documentation for C005 + [AutoDiscovery] Update PluginTemplate + [AutoDiscovery] Embed setting for `online` message in `/config` setting + [Docs] P165 Fix a few typos + [AutoDiscovery] Remove unused code + [AutoDiscovery] Make start delay compiletime configurable, and extend max delay from 10 to 30 sec. + [Docs] Update documentation for C005 + [AutoDiscover] Add Duration, Date and Timestamp device classes, fix some irregularities + [AutoDiscover] Add Data rate, Data size, Sound pressure and Signal strength + [AutoDiscover] Refactor discovery message composition to reduce binary size by nearly 4k + [AutoDiscover] Set Unit of Measure and Value Type when adding a device (when possible/available) + [P095] Add ILI9342 display variants + [P095] ILI9342 added configurations, set resolution correctly + [P095] Update documentation + [UoM] Correct a few Value Type UoM defaults + [P026] Set default Value Type and UoM when changing a task value setting + [Converters] Add `%c_ts2isodate%()` Timestamp to ISO date conversion + [Converters] Add `%c_ts2isodate%()` Timestamp to ISO date conversion + [AutoDiscover] Clean names sent to HA by replacing unsupported characters by `_` + [Converters] Include system time-zone in `%c_ts2isodate%()` to have HA accept the `Timestamp` value type + [Converters] Make system time-zone optional in `%c_ts2isodate%()` (second argument != 0) + [Converters] Make system time-zone optional in `%c_ts2isodate%()` (second argument != 0) + [AutoDiscover] Add `Date/Time` (`datetime`) Device class + [AutoDiscover] Fix handling of BARO_ONLY values, and allow multiple BARO_ONLY values + [AutoDiscover] Remove non-working `Date/Time` (`datetime`) Device class :-( + [ValueTypes] Add categorized selector for Value Type per Value + [P176] Set default Value Types when adding the plugin + [ValueTypes] Fix optional None value + [P180] Remove unneeded code + [AutoDiscover] Try to get switch trigger working + [ValueTypes] Reorder value types somewhat, use categorized selector where applicable + [ValueTypes] Update documentation + [P014] Fix MQTT Discovery value types + [Build] Remove "_CDC_" from all env names due to linker issues + [MQTT] Fix command handling for /set and /cmd topic endpoints, add support for P001 in /set + [AutoDiscover] Mark some MQTT Device classes as 2-way to have on/off devices (Light) + [CustomVtype, UoM] Fix compilation for ESP8266 when not all enabled + [AutoDiscover] Better array index checks + [Backup] Add backup without credentials included (security.dat) + [Metrics] Add internal CPU temperature + [Backup] Use term credentials instead of username/password + [Backup] Update documentation + [Metrics] Update documentation + [AutoDiscover] Add support for P009 and P019, only send discovery for plugins that actually send data + [AutoDiscover] Fix flippin bit-flip issue + [AutoDiscover] Fix P021 not to be handled as a Switch + [Backup] Fix typo in variable name for ESP8266 + [AutoDiscover] Fix Group configuration + [Backup] Compile error when TAR support disabled + [Libs] MFRC522 remove unused variable + [Libs] PubSubClient signed/unsigned comnparison warning + [AutoDiscover] Set default Auto Discovery topics when adding HA Controller + [P009][P019] Fix logging alignment + [AutoDiscover] Fix a few comments + [AutoDiscover] Docs improvement for Group feature + [TaskValueSetDerived] Improved parsing of Unit of Measure value + [AutoDiscover] Fix conditional compilation issue + [Rules] Enable rules by default on a clean install + [AutoDiscover] Add state_class support and some Value Types + [AutoDiscover] Enable state_class support for plugins and correct a few Value Types + [AutoDiscover] Update documentation + [AutoDiscover] Fix compilation when state_class support not enabled + [AutoDiscover] Small code improvement + [P078] Improved value descriptions and register appended, small code improvements + [Caches] Formatting (minor) + [P056] Add multi-instance use + [P078] Fix docs parser warning + [P056] Update documentation + [P102] Add support for PZEM-017v1 + [P102] UI updates and corrections + [P102] Add documentation + [Build] Fix compiler warning for unused variable + [BusCmd I2C] Add support for handling > 128 bytes on I2C read/write (ESP32) + [BusCmd Helper] Add support for variable length string and buffers (Byte/Word), and some bugfixes + [P180] Update documentation + [BusCmd Helper] Send data & event when updating value.N from other than PLUGIN_READ action + [BusCmd Helper] Add LetStr I2C command, analogue to the `LetStr` Rules command, updated documentation + [P102] Small typo in documentation + [AutoDiscover] Use %sysname% instead of group_nr if Controller Group = UnitNr + [AutoDiscover] Update documentation for Group setting + [AutoDiscover] Add 15 more Value types and 7 UoM values, fix some default UoM + [AutoDiscover] Update Value type and UoM documentation + [MQTT] Add Retain option per task + [MQTT] Update documentation for Retained setting per task + [MQTT] Some documentation improvements + [Build] Add [P105] AHT2x to Collection G so it can be used with [P164] ENS160 + [P089] Add support for ESP32 + [P089] Remove unneeded attribute + [P089] Create separate Ping Service to handle single-instance Ping function sequentially + [P180] Add Tiny Code Reader example to documentation + [P180] Add optional skip argument to IF I2C command, update docs + [P021] Emphasize warning to prevent saving settings too often + [P039] Update SPI and GPIO documentation + [P037] Use configured decimals when generating events + [P037] Update documentation for: use configured decimals when generating events + [P168] Disable blocking Lux Read-Method options + [P168] Update documentation + [P102] Don't check non-available values for NaN + [P102] Correct documentation small typos + [MQTT] Handle connecting to broker as a background process + [Tools] Update documentation for MQTT Connect in background + [MQTT] Move Settings attribute to next available bit + [MQTT] Reconnect on unexpected network interruptions + [P089] Update documentation + [MQTT] Cancel background connection request after 2 minutes when still not connected + [MQTT] Fix reconnect on unexpected network interruptions + [Discovery] Avoid setting the timeout more than once + [MQTT] Use client timeout for delay between connection attempts + [Build] Fix missing include + [MQTT] Refactor background connection process to avoid logging from background task + [AutoDiscover] Fix small typos in default UoM list + [AutoDiscover] Fix spelling error in precipitation + [AutoDiscover] Fix spelling error in precipitation + [MQTT] Fix compilation on ESP8266 (Retain per task) + [P089] Bugfix: Putt include in scope + [P102] Use added setModel() method when needed + [P033] Enable formula field + [P033] Update documentation + [P102] Use defined constants + [P102] Add Submit instruction after use of 'Reset energy' mode + [P102] Update documentation + [Info] Emphasize the number of flash writes count since cold boot #3116 + [C011] HTTP Advanced - Don't URL-encode header + [HTTP] Add support for TLS + [HTTP] Enable TLS support for Controllers using HTTP + [P002] Fix docs layout issue + [HTTP] Defensive programming in test for https url + [HTTP] Enable TLS support also for Controller C007 Emoncms + [Devices] Limit displaying of (old) Wiki Help button to P056 and below + [Devices] Limit displaying of (old) Wiki Help button to P079 and below + [MQTT] Reduce work/memory in background task, use xTaskDelay as required + [Docs] Correct some layout inconsistencies + [Docs] Update overview generator, include Notify and Network plugins + [Build] Fix compiler warning + [Tools] Add Plugin list overview page + [Tools] Update documentation for Plugin list + [Build] Split Display into Display A and Display B + [Build] Display A/B split: Update documentation + [Tools] Add documentation-button to Plugin list overview page + [Tools] Add preliminary support for Network plugins + [Docs] Add MQTT Device class documentation for plugins where available + [MQTT] Minor corrections + [TLS] Update TLS to be able to use ECDHE_ECDSA_AES_128_GCM_SHA256 cipher + [Docs] ESP8266 is Feature Complete! + [TLS-MQTT] Automatic switch to ECDSA when error is 296 (TLS handshake) + [MQTT AutoDiscovery] Add support for Switch Device Class + [MQTT AutoDiscovery] Update documentation for Device Class + [P026] Fix Value Type for TXpower to use dBm + [HTTP] Fix build and feature-enable for non-MAX builds + [HTTP] Update documentation for TLS options + [UoM] Refactoring to reduce page-load time and size + [Build] Add Collection H and split Climate into Climate A and Climate B + [Build] Fix build errors for Collection H and Climate B + [Build] Remove obsolete comments + [Build] Move more plugins from Collection G to H to make Collection G fit + [Build] Reduce build-size for all Collection builds using BUILD_NO_DEBUG + [Build] Reduce build-size for Collection C build + [P169] Fix DEBUG log + [Build] Avoid build-reduction for MAX builds + [Build] Avoid build-reduction for MAX builds + [P021] Code optimizations + [Build] Remove _LittleFS and _ETH from build names due to linker issues + [Build] Enable `custom_remove_include` to not build ignored libs + [Build] Limit package building to use Python 3.13 + [UI] Update addTaskSelector() to use FormSelectorOptions + [Docs] P180 Add example for reading Sensirion SDP-8xx + [Docs] P180 Small corrections + [Docs] P180 Sensirion SDP-8xx add description for Single-shot measurement + [Bugfix] Don't use Flash-string parameter default as ESP8266 compiler doesn't support that + [Bugfix] PluginList add missing includes needed in some builds + [PluginList] Update for HTTPS TLS support + [Build] Fix SafeBoot build + [Bugfix] Derived values avoid parsing invalid values causing crashes + [Bugfix] Derived values avoid parsing invalid values causing crashes, more global fix + [Bugfix] Revert previous change as that didn't really fix the issue + [AutoDiscovery] Show Auto Discovery for Controllers that have this enabled (Task configuration) + [Bugfix] Calculation missed value when returning from multiple brackets + [Bugfix] MQTT StateClass selector stored wrong index + [P003] Enable MQTT State Class option so pulse-counter can be used for energy measurement (in HA) + [P180] BusCmd_Helper processing I2C commands via cmd subcommand was locked out + [SysVars] Add %latitude% and %longitude% vars and Latitude and Longitude commands + [SysVars] Documentation for %latitude% and %longitude% vars and Latitude and Longitude commands + [P180] Enable CustomVTypeVar and MQTTStateClass device flags + [P180] Fix typo in parameter for log subcommand + [P180] Add documentation for TF-Luna laser distance sensor + [P180] Small updates in documentation of TF-Luna laser distance sensor + [P180] Add documentation for QMC6310 3-axis magnetic sensor, add example-index to docs page + [Bugfix] Calculation of functions discarded the next operator + [Bugfix] Calculation of functions remove unneeded value duplication, small debug-code optimization + [Rules] Add some functions, conversions and constants + [Rules] Add some functions, conversions and constants, fix docs typo + [Docs] Updates and improvements (2025 Q4/2026-01) + [Stats] Add minp/maxp stats values and change min/max stats values for running min/max value, update docs + [Bugfix] Compilation error fix for custom IR build + [Bugfix] Metrix: Export 0 for empty values + [Bugfix] Metrix: Export 0 for empty values, fix for ESP8266 + [AdaGFX] Fix `tpm` subcommand argument check + [Docs] Update copyright notice to show 2026 + [AdaGFX] Fix space-height issue for some fonts + [P116] Add ST77xx resolutions 240x320 and 240x280 + [P116] Allow for > 16 hardware types in selection + [P116] Update documentation with new resolutions + [P087] Add support for receiving binary data, fixed length data, and event as hex string + [P087] Update documentation + [P087] Add to `ENERGY` build (ESP32 only) + [Converters] Bugfix formatToHex_wordarray() + [P087] Use PCONFIG_ULONG to store new config flags + [P004] Change name to include DS18xxx/MAX31xxx for better recognizability + [P057] Add documentation as it wasn't included yet + +chromoxdor (63): + Update Networking.cpp + a tiny bit better formatting + changes for OPENMETEO_EVENT and THINGSPEAK_EVENT + Update Networking.cpp + combining two functions + wrong use of concat times two :) + update + Update Networking.cpp + put thingspeak and openmeteo events into extra file + added inverter event + fix for not building on minimal_core_274_esp8266... + #ifdef to #if. m( + more universal json event approach + Update HTTPResponseParser.h + added variable decimals for floating point numbers + some changes to the json-event + made the eventQueue even more complex :P + minor changes + moved variable to cpp file + updates + Update HTTPResponseParser.cpp + Update define_plugin_sets.h + Update HTTPResponseParser.cpp + Update HTTPResponseParser.cpp + documentation and slight reduction of code + Update define_plugin_sets.h + corrected json examples + Docs: Formatting and linking + fixed typo + [P164 ] Adding a sleep mode + Update P164_data_struct.cpp + Update define_plugin_sets.h + Changes as requested + Update HTTPResponseParser.rst + Update HTTPResponseParser.cpp + ColorCode Additions + Update espeasy.js + forgot the "f" and the minified version + some unessecary commas removed + no indentation for comments.... + added a format button if on mobile device + format even without colorcode enabled + more robust block checks + jump to line of first error and keep history + make it work for android non color rules again + Readded search bar and enhanced it. + fixed broken replace function + forgot someting to change + find while typing + fix replace + more robust live search handling + removed unnecessary function + scroll to top when found + Update codemirror.min.css + replace: scroll to top too + as good as it gets :) + minor corrections help text + rephrasing of the confirm dialog + fix: jump back to cursor position after formatting + ..and remove an old dialog before opening a new one + disable shortcuts + disable shortcut: forgot to add the corresponding function + Update espeasy.min.js + +dependabot[bot] (10): + Bump cryptography from 43.0.1 to 44.0.1 + Bump actions/download-artifact from 4 to 5 + Bump actions/checkout from 4 to 5 + Bump actions/setup-python from 5 to 6 + Bump actions/github-script from 7 to 8 + Bump actions/download-artifact from 5 to 6 + Bump actions/upload-artifact from 4 to 5 + Bump actions/checkout from 5 to 6 + Bump actions/upload-artifact from 5 to 6 + Bump actions/download-artifact from 6 to 7 + +jfmennedy (1): + Fix horizantal vane not working + +repa6 (7): + added TAC2100 support to _P078_Eastron plugin + Update _P078_Eastron.ino with changelog info + Update P078_data_struct.h: added comma after TAC2100=7 + extended TAC2100 registers list and added TAC2100 to docimentation + fixed redister nature of load + Update P078_data_struct.cpp - added nat:load to SDM_UOMtoString strings + Update P078_data_struct.cpp - updated register addresses for the last 5 registers of TAC2100 + + ------------------------------------------------- Changes in release mega-20250430 (since mega-20241222) ------------------------------------------------- diff --git a/docs/builds_overview.py b/docs/builds_overview.py index 204cb4a03..a0898d8ac 100644 --- a/docs/builds_overview.py +++ b/docs/builds_overview.py @@ -5,13 +5,15 @@ ############################################################################################################# # This script parses all documentation substitution files to determine in what builds a plugin is available # Collection A..G, Display, Energy and Neopixel, IR and IRext get Normal plugins injected -# Collection plugins are also injected into Collection A..G +# Collection, Notify and Network plugins are also injected into Collection A..G # All plugins get injected into MAX build set # Some build sets have exceptions for plugins not available # The output generation order is determined by how they are ordered in list 'buildColors' # When adding or removing a build set, this script may need adjustments! # Changelog: +# 2025-10-05 tonhuisman: Adjustments for Display A and Display B split +# 2025-10-01 tonhuisman: Include Notify and Network plugins, ignore not available files, parse NWxxx also # 2024-05-04 tonhuisman: Working and documented # 2024-04-28 tonhuisman: Initial script @@ -30,8 +32,10 @@ appendBuilds = {'MAX'} # What build set to add plugins also appendAlso = { - 'NORMAL': {'CLIMATE', 'COLLECTION A', 'COLLECTION B', 'COLLECTION C', 'COLLECTION D', 'COLLECTION E', 'COLLECTION F', 'COLLECTION G', 'DISPLAY', 'ENERGY', 'IR', 'IRext', 'NEOPIXEL'}, - 'COLLECTION': {'COLLECTION A', 'COLLECTION B', 'COLLECTION C', 'COLLECTION D', 'COLLECTION E', 'COLLECTION F', 'COLLECTION G'} + 'NORMAL': {'CLIMATE', 'COLLECTION A', 'COLLECTION B', 'COLLECTION C', 'COLLECTION D', 'COLLECTION E', 'COLLECTION F', 'COLLECTION G', 'COLLECTION H', 'DISPLAY A', 'DISPLAY B', 'ENERGY', 'IR', 'IRext', 'NEOPIXEL'}, + 'COLLECTION': {'COLLECTION A', 'COLLECTION B', 'COLLECTION C', 'COLLECTION D', 'COLLECTION E', 'COLLECTION F', 'COLLECTION G', 'COLLECTION H'}, + 'DISPLAY': {'DISPLAY A', 'DISPLAY B'}, + 'CLIMATE': {'CLIMATE A', 'CLIMATE B'} } # Ignore these, not real build sets @@ -39,9 +43,11 @@ excludeBuilds = {'DEVELOPMENT', 'RETIRED'} # Plugins not included excludePlugins = { - 'CLIMATE': {'P007', 'P008', 'P009', 'P017', 'P022', 'P027', 'P030', 'P035', 'P040', 'P041', 'P042', 'P045'}, - 'DISPLAY': {'P070'}, - 'MAX': {'P089'}, + 'CLIMATE A': {'P007', 'P008', 'P009', 'P017', 'P022', 'P027', 'P030', 'P035', 'P040', 'P041', 'P042', 'P045'}, + 'CLIMATE B': {'P007', 'P008', 'P009', 'P017', 'P022', 'P027', 'P030', 'P035', 'P040', 'P041', 'P042', 'P045'}, + 'DISPLAY A': {'P038', 'P041', 'P042', 'P070'}, + 'DISPLAY B': {'P038', 'P041', 'P042', 'P057', 'P070', 'P075', 'P104'}, + # 'MAX': {''}, # 'NEOPIXEL': {''}, 'NORMAL': {'P016', 'P035'}, } @@ -56,8 +62,11 @@ buildColors = { 'COLLECTION E': 'yellow', 'COLLECTION F': 'yellow', 'COLLECTION G': 'yellow', - 'CLIMATE': 'yellow', - 'DISPLAY': 'yellow', + 'COLLECTION H': 'yellow', + 'CLIMATE A': 'yellow', + 'CLIMATE B': 'yellow', + 'DISPLAY A': 'yellow', + 'DISPLAY B': 'yellow', 'ENERGY': 'yellow', 'IR': 'yellow', 'IRext': 'yellow', @@ -101,6 +110,8 @@ def addToAllBuilds(plugin, pluginName, builds:dict): # Parse a single substitution file def parseSingleSubstitutionFile(fileName): filepath = os.path.relpath(os.path.join(basePath, fileName), '.') + if not os.path.isfile(filepath): + return # print(filepath) # For debugging pfile = open(filepath, "r") # Start empty @@ -113,7 +124,7 @@ def parseSingleSubstitutionFile(fileName): break # Parse into label, plugin ID, description and up to 4 separate builds (current max.), # append "(?:[^`]+`([^`]+)`)?" to regex for an extra build, if needed - m = re.search(r"[^|]\|([PCN](\d{3}))([^\|]+)\|[^`]+`([^`]+)`(?:[^`]+`([^`]+)`)?(?:[^`]+`([^`]+)`)?(?:[^`]+`([^`]+)`)?", line) + m = re.search(r"[^|]\|((?:NW|[PCN])(\d{3}))([^\|]+)\|[^`]+`([^`]+)`(?:[^`]+`([^`]+)`)?(?:[^`]+`([^`]+)`)?(?:[^`]+`([^`]+)`)?", line) if m: if m.group(3) == "_typename": # the typename substitution should be before _status... if plugin != "" and plugin != m.group(1): # Changed plugin ID, store current @@ -178,6 +189,10 @@ print('Parsing substitutions for build sets...') parseSubstitutionFiles('../Plugin/_plugin_substitutions.repl') # Parse all Controller substitutions parseSingleSubstitutionFile('../Controller/_controller_substitutions.repl') +# Parse all Notify substitutions +parseSingleSubstitutionFile('../Notify/_notify_substitutions_n00x.repl') +# Parse all Network substitutions +parseSingleSubstitutionFile('../Network/_network_substitutions.repl') # Generate output generateBuildOverview('../Plugin/_plugin_sets_overview.repl') diff --git a/docs/source/Controller/_Controller.rst b/docs/source/Controller/_Controller.rst index 801956148..05f1d5251 100644 --- a/docs/source/Controller/_Controller.rst +++ b/docs/source/Controller/_Controller.rst @@ -52,6 +52,10 @@ Generic fields - **Controller Port** - TCP/UDP Port number (0...65536) - **Enabled** - Whether or not the controller is active. +When **TLS** is included in the build, some of the Controllers have a setting to enable a TLS option. For HTTP-based controller protocols, this usually expects the port to adjusted from 80 to 443, but of course any custom port can be used. For MQTT controllers, the port is automatically updated from 1883 (No TLS) to 8883 when a TLS-enabled setting is selected. + +- **Use TLS** - Select the desired TLS type. The available options depend on what's implemented, and will change when development progresses. The default setting is **No TLS**. + Send queue parameters --------------------- diff --git a/docs/source/ESPEasy/ESPchips.rst b/docs/source/ESPEasy/ESPchips.rst index f5cf06b70..044744085 100644 --- a/docs/source/ESPEasy/ESPchips.rst +++ b/docs/source/ESPEasy/ESPchips.rst @@ -1,6 +1,29 @@ Supported ESP Chips ******************* +ESP8266 is Feature Complete! +============================ + +(Since 2025-04-30) + +The core team has decided to declare the ESP8266 builds (that also support ESP8285) Feature Complete, for multiple reasons: + +* **Binary size**: Making (new or enhanced) plugins and growing feature set fit in the limited available binary space of an ESP8266 (1020kB) is getting harder and harder. Many hundreds of hours have been spent already to reduce size as much as possble, and there isn't much room for improvement anymore. +* **Limited RAM available**: The ESP8266 has only 80 kB of RAM available, and with the core of ESPEasy loaded, that leaves only between 15 and 25 kB of free memory for plugins to work with. This is a major cause for crashes when some complicated tasks are handling data of some size. +* **Chip development progress**: New features for ESP chips are being developed, but the ESP8266 platform, while still being manufactured, does not get technical improvements anymore. +* **Software support by Espressif**: New software development in the framework for supporting the ESP platform is only directed at the ESP32 line of MCUs. No new development for ESP8266 is being done. + +The consequences of this decision are that: + +* New plugins and features are **not** included in the regularly made available ESP8266 builds. +* To enable some of the new features (many are still useable) for ESP8266, a Custom build can be configured and built by the user, as documented in :ref:`PlatformIO_page`. +* A notable exception is support for TLS: That's *not* possible to enable on ESPEasy in ESP8266 builds as it doesn't fit in the limited available RAM memory. + +For new projects, it is strongly advised to select one of the many available ESP32 boards, or chips when designing a custom board. Some of the ESP32 variants are available with 16 MB Flash (ESP32 Classic, ESP32-C3, ESP32-C6 and ESP32-S3), and have 1 or more MAX builds available in ESPEasy, that include all plugins and features available, and also a larger selection of fonts for displays (TFT/LCD, OLed and 7-segment). + +Overview of supported chips +=========================== + ESPEasy does support a number of variants of the processors manufactured by Espressif. * **ESP8266** The original ESP processor, with external flash. @@ -593,8 +616,10 @@ Datasheets * `DS:ESP32-S3 `_ * `DS:ESP32-C3 `_ / `DS:ESP8685 `_ * `DS:ESP32-C2 `_ / `DS:ESP8684 `_ +* `DS:ESP32-C5 `_ * `DS:ESP32-C6 `_ * `DS:ESP32-H2 `_ +* `DS:ESP32-P4 `_ Technical Reference Manuals @@ -606,8 +631,10 @@ Technical Reference Manuals * `TR:ESP32-S3 `_ * `TR:ESP32-C3 `_ * `TR:ESP32-C2 `_ +* `TR:ESP32-C5 `_ * `TR:ESP32-C6 `_ * `TR:ESP32-H2 `_ +* `TR:ESP32-P4 `_ ESP8266/ESP8285 @@ -854,7 +881,7 @@ ESP32-C5 This will be the first Espressif SoC supporting 5 GHz WiFi. -.. note:: Not yet available (as of Dec 2024) +.. note:: Beta silicon is available (as of June 2025), which may change in Q3 or Q4 of 2025. So not yet adviced to be used in real products ESP32-C6 ======== @@ -897,4 +924,5 @@ The CPU is rather powerful and there are versions with quite a large amount of P It does have a RMII interface for Ethernet, like the ESP32-classic does. -.. note:: Not yet supported (as of Dec 2024) +.. note:: Not yet supported. Beta silicon is available (as of June 2025), which may change in Q3 or Q4 of 2025. So not yet adviced to be used in real products + diff --git a/docs/source/Network/NW001.rst b/docs/source/Network/NW001.rst new file mode 100644 index 000000000..0d0f6cd1b --- /dev/null +++ b/docs/source/Network/NW001.rst @@ -0,0 +1,39 @@ +.. include:: _network_substitutions.repl + +.. _NW001_page: + +|NW001_typename| +================================================== + +|NW001_shortinfo| + +Network details +--------------- + +Type: |NW001_type| + +Name: |NW001_name| + +Status: |NW001_status| + +GitHub: |NW001_github|_ + +Maintainer: |NW001_maintainer| + +Change log +---------- + +.. versionchanged:: 2.0 + ... + + |added| + Major overhaul for 2.0 release. + +.. versionadded:: 1.0 + ... + + |added| + Initial release version. + +Description +----------- diff --git a/docs/source/Network/NW002.rst b/docs/source/Network/NW002.rst new file mode 100644 index 000000000..ca451d1c3 --- /dev/null +++ b/docs/source/Network/NW002.rst @@ -0,0 +1,39 @@ +.. include:: _network_substitutions.repl + +.. _NW002_page: + +|NW002_typename| +================================================== + +|NW002_shortinfo| + +Network details +--------------- + +Type: |NW002_type| + +Name: |NW002_name| + +Status: |NW002_status| + +GitHub: |NW002_github|_ + +Maintainer: |NW002_maintainer| + +Change log +---------- + +.. versionchanged:: 2.0 + ... + + |added| + Major overhaul for 2.0 release. + +.. versionadded:: 1.0 + ... + + |added| + Initial release version. + +Description +----------- diff --git a/docs/source/Network/NW003.rst b/docs/source/Network/NW003.rst new file mode 100644 index 000000000..3be1023fe --- /dev/null +++ b/docs/source/Network/NW003.rst @@ -0,0 +1,39 @@ +.. include:: _network_substitutions.repl + +.. _NW003_page: + +|NW003_typename| +================================================== + +|NW003_shortinfo| + +Network details +--------------- + +Type: |NW003_type| + +Name: |NW003_name| + +Status: |NW003_status| + +GitHub: |NW003_github|_ + +Maintainer: |NW003_maintainer| + +Change log +---------- + +.. versionchanged:: 2.0 + ... + + |added| + Major overhaul for 2.0 release. + +.. versionadded:: 1.0 + ... + + |added| + Initial release version. + +Description +----------- diff --git a/docs/source/Network/NW004.rst b/docs/source/Network/NW004.rst new file mode 100644 index 000000000..9a4885546 --- /dev/null +++ b/docs/source/Network/NW004.rst @@ -0,0 +1,39 @@ +.. include:: _network_substitutions.repl + +.. _NW004_page: + +|NW004_typename| +================================================== + +|NW004_shortinfo| + +Network details +--------------- + +Type: |NW004_type| + +Name: |NW004_name| + +Status: |NW004_status| + +GitHub: |NW004_github|_ + +Maintainer: |NW004_maintainer| + +Change log +---------- + +.. versionchanged:: 2.0 + ... + + |added| + Major overhaul for 2.0 release. + +.. versionadded:: 1.0 + ... + + |added| + Initial release version. + +Description +----------- diff --git a/docs/source/Network/NW005.rst b/docs/source/Network/NW005.rst new file mode 100644 index 000000000..5cf45a5de --- /dev/null +++ b/docs/source/Network/NW005.rst @@ -0,0 +1,56 @@ +.. include:: _network_substitutions.repl + +.. _NW005_page: + +|NW005_typename| +================================================== + +|NW005_shortinfo| + +Network details +--------------- + +Type: |NW005_type| + +Name: |NW005_name| + +Status: |NW005_status| + +GitHub: |NW005_github|_ + +Maintainer: |NW005_maintainer| + +Description +----------- + + +Configuration +------------- + + +Device Settings +^^^^^^^^^^^^^^^ + + + +Connection Settings +^^^^^^^^^^^^^^^^^^^ + + +Change log +---------- + +.. versionchanged:: 2.0 + ... + + |added| + Major overhaul for 2.0 release. + +.. versionadded:: 1.0 + ... + + |added| + Initial release version. + +Description +----------- diff --git a/docs/source/Network/NW005_DeviceSettings.png b/docs/source/Network/NW005_DeviceSettings.png new file mode 100644 index 000000000..89abbd08e Binary files /dev/null and b/docs/source/Network/NW005_DeviceSettings.png differ diff --git a/docs/source/Network/NW005_ModemState.png b/docs/source/Network/NW005_ModemState.png new file mode 100644 index 000000000..4162d8d58 Binary files /dev/null and b/docs/source/Network/NW005_ModemState.png differ diff --git a/docs/source/Network/NW005_UE_SystemInformation.png b/docs/source/Network/NW005_UE_SystemInformation.png new file mode 100644 index 000000000..0463b9022 Binary files /dev/null and b/docs/source/Network/NW005_UE_SystemInformation.png differ diff --git a/docs/source/Network/_Network.rst b/docs/source/Network/_Network.rst new file mode 100644 index 000000000..807f8d1b7 --- /dev/null +++ b/docs/source/Network/_Network.rst @@ -0,0 +1,31 @@ +.. include:: _network_substitutions.repl + +Network +******* + +A Network plugin is compable with a 'driver' for a network adapter. + +The first 2 entries will be the same for every ESPEasy setup and these cannot be removed. + +- Wi-Fi Station: To connect the ESP board to an access point. +- Wi-Fi AP: To let the ESP board act as an access point. + + + +.. _Network Plugins: + +Network Plugins +================== + +.. csv-table:: + :header: "Plugin name", "Plugin status", "Plugin number" + :widths: 10, 8, 5 + + ":ref:`NW001_page`","|NW001_status|","NW001" + ":ref:`NW002_page`","|NW002_status|","NW002" + ":ref:`NW003_page`","|NW003_status|","NW003" + ":ref:`NW004_page`","|NW004_status|","NW004" + ":ref:`NW005_page`","|NW005_status|","NW005" + +Network Parameters +===================== diff --git a/docs/source/Network/_network_substitutions.repl b/docs/source/Network/_network_substitutions.repl new file mode 100644 index 000000000..d65e951fe --- /dev/null +++ b/docs/source/Network/_network_substitutions.repl @@ -0,0 +1,58 @@ + + + +.. |NW001_name| replace:: :cyan:`WiFi Station` +.. |NW001_type| replace:: :cyan:`Network` +.. |NW001_typename| replace:: :cyan:`Network - WiFi Station` +.. |NW001_status| replace:: :green:`NORMAL` +.. |NW001_github| replace:: _NW001_WiFi_STA.cpp +.. _NW001_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/ESPEasy/net/_NW001_WiFi_STA.cpp +.. |NW001_usedby| replace:: `.` +.. |NW001_shortinfo| replace:: `.` +.. |NW001_maintainer| replace:: `TD-er` +.. |NW001_compileinfo| replace:: `.` + +.. |NW002_name| replace:: :cyan:`WiFi AP` +.. |NW002_type| replace:: :cyan:`Network` +.. |NW002_typename| replace:: :cyan:`Network - WiFi AP` +.. |NW002_status| replace:: :green:`NORMAL` +.. |NW002_github| replace:: _NW002_WiFi_AP.cpp +.. _NW002_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/ESPEasy/net/_NW002_WiFi_AP.cpp +.. |NW002_usedby| replace:: `.` +.. |NW002_shortinfo| replace:: `.` +.. |NW002_maintainer| replace:: `TD-er` +.. |NW002_compileinfo| replace:: `.` + +.. |NW003_name| replace:: :cyan:`Ethernet (RMII)` +.. |NW003_type| replace:: :cyan:`Network` +.. |NW003_typename| replace:: :cyan:`Network - Ethernet (RMII)` +.. |NW003_status| replace:: :green:`NORMAL` +.. |NW003_github| replace:: _NW003_ETH_RMII.cpp +.. _NW003_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/ESPEasy/net/_NW003_ETH_RMII.cpp +.. |NW003_usedby| replace:: `.` +.. |NW003_shortinfo| replace:: `.` +.. |NW003_maintainer| replace:: `TD-er` +.. |NW003_compileinfo| replace:: `.` + +.. |NW004_name| replace:: :cyan:`Ethernet (SPI)` +.. |NW004_type| replace:: :cyan:`Network` +.. |NW004_typename| replace:: :cyan:`Network - Ethernet (SPI)` +.. |NW004_status| replace:: :green:`NORMAL` +.. |NW004_github| replace:: _NW004_ETH_SPI.cpp +.. _NW004_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/ESPEasy/net/_NW004_ETH_SPI.cpp +.. |NW004_usedby| replace:: `.` +.. |NW004_shortinfo| replace:: `.` +.. |NW004_maintainer| replace:: `TD-er` +.. |NW004_compileinfo| replace:: `.` + +.. |NW005_name| replace:: :cyan:`PPP Modem` +.. |NW005_type| replace:: :cyan:`Network` +.. |NW005_typename| replace:: :cyan:`Network - PPP Modem` +.. |NW005_status| replace:: :green:`NORMAL` +.. |NW005_github| replace:: _NW005_PPP_modem.cpp +.. _NW005_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/ESPEasy/net/_NW005_PPP_modem.cpp +.. |NW005_usedby| replace:: `.` +.. |NW005_shortinfo| replace:: `.` +.. |NW005_maintainer| replace:: `TD-er` +.. |NW005_compileinfo| replace:: `.` + diff --git a/docs/source/Notify/N001.rst b/docs/source/Notify/N001.rst index 1ad5d1688..e73cef858 100644 --- a/docs/source/Notify/N001.rst +++ b/docs/source/Notify/N001.rst @@ -2,8 +2,8 @@ .. _N001_page: -Notifications Email (SMTP) -=========================== +|N001_typename| +=============================== |N001_shortinfo| diff --git a/docs/source/Notify/N002.rst b/docs/source/Notify/N002.rst index 6797a5b84..4cbcb51a5 100644 --- a/docs/source/Notify/N002.rst +++ b/docs/source/Notify/N002.rst @@ -3,8 +3,8 @@ .. _N002_page: -Notifications Buzzer -===================== +|N002_typename| +======================== |N002_shortinfo| diff --git a/docs/source/Notify/_notify_substitutions_n00x.repl b/docs/source/Notify/_notify_substitutions_n00x.repl index 4a4a15f4b..b6c9702e1 100644 --- a/docs/source/Notify/_notify_substitutions_n00x.repl +++ b/docs/source/Notify/_notify_substitutions_n00x.repl @@ -1,5 +1,5 @@ .. |N001_type| replace:: :cyan:`Notifications` -.. |N001_name| replace:: :cyan:`Notifications Email (SMTP)` +.. |N001_name| replace:: :cyan:`Notifications - Email (SMTP)` .. |N001_typename| replace:: :cyan:`Notifications - Email (SMTP)` .. |N001_porttype| replace:: `.` .. |N001_status| replace:: :green:`NORMAL` :yellow:`CLIMATE` :yellow:`COLLECTION` :yellow:`SONOFF` :yellow:`SHELLY` @@ -11,7 +11,7 @@ .. |N001_page| replace:: N001 .. |N002_type| replace:: :cyan:`Notifications` -.. |N002_name| replace:: :cyan:`Notifications Buzzer` +.. |N002_name| replace:: :cyan:`Notifications - Buzzer` .. |N002_typename| replace:: :cyan:`Notifications - Buzzer` .. |N002_porttype| replace:: `.` .. |N002_status| replace:: :green:`NORMAL` :yellow:`CLIMATE` :yellow:`COLLECTION` :yellow:`SONOFF` :yellow:`SHELLY` diff --git a/docs/source/Plugin/DeviceClass_binary.repl b/docs/source/Plugin/DeviceClass_binary.repl new file mode 100644 index 000000000..658f5aaef --- /dev/null +++ b/docs/source/Plugin/DeviceClass_binary.repl @@ -0,0 +1,12 @@ +MQTT Device class +^^^^^^^^^^^^^^^^^ + +(Only available if both MQTT Auto Discovery and Device Class features are included in the build.) + +* **MQTT Device class**: Select the Binary Device class that's to be used for this task device. Device classes marked with ``²`` are 'two-way' devices, meaning that the state will be updated when changed, either on the ESPEasy side, or on the MQTT (Home Assistant) side. For the MQTT Device classes ``switch`` and ``outlet``, both also marked with ``÷``, the discovery is marked as ``switch`` instead of ``light``. + +The default value used is ``switch``, also when not set (empty), and can be updated in Home Assistant by resending the MQTT Discovery. + +The available options are based on the Summer 2025 version of this `Home Assistant MQTT Binary sensor documentation page `_ and also related to this `Home Assistant MQTT Switch device page `_ (though that doesn't document *any* Auto Discovery information) + +The ``switch`` device class shows an On/Off icon, the default icon shown for other ``light`` or ``binary_sensor`` devices in Home Assistant (HA) can be changed for an alternative in the HA configuration, if desired, after Auto Discovery has created the device there. diff --git a/docs/source/Plugin/P000_commands.repl b/docs/source/Plugin/P000_commands.repl index 2a1d41a47..438bebfb1 100644 --- a/docs/source/Plugin/P000_commands.repl +++ b/docs/source/Plugin/P000_commands.repl @@ -303,6 +303,18 @@ Example output: ``IP:192.168.10.86(DHCP)``" " + Latitude"," + :red:`Internal`"," + Set the latitude for the unit, that's used when calculation the sunrise/sunset times. + + ``Latitude[,]`` + + Range: -90 .. 90 (decimal, negative is southern hemisphere) + + When no argument is provided, the current setting is shown. + + See also ``Longitude``." + " Let"," :red:`Internal`"," Set the value of variable n (1..INT_MAX), or use a variable name. @@ -344,6 +356,18 @@ ``LogPortStatus``" " + Longitude"," + :red:`Internal`"," + Set the longitude for the unit, that's used when calculation the sunrise/sunset times. + + ``Longitude[,]`` + + Range: -180 .. 180 (decimal, negative is west of the prime meridian) + + When no argument is provided, the current setting is shown. + + See also ``Latitude``." + " LoopTimerSet LoopTimerSet_ms"," @@ -521,6 +545,12 @@ Added: 2023-01-15 " " + PostToHTTPS"," + :green:`Rules`"," + Like ``PostToHTTP`` but using a TLS connection, by default on port 443 + + NB: No certificate validation is done! + " ProvisionConfig"," :red:`Internal`"," Fetch ``config.dat`` as configured in the Provisioning configuration (see Settings Archive) @@ -622,6 +652,12 @@ Added: 2023-04-17 " " + PutToHTTPS"," + :green:`Rules`"," + Like ``PutToHTTP`` but using a TLS connection, by default on port 443 + + NB: No certificate validation is done! + " Reboot"," :red:`Internal`"," Reboot the ESP @@ -696,6 +732,12 @@ On completion of the command, the returned status code can be handled in a generated event, f.e. ``http#hostname=404`` for not found error, or ``http#hostname=200`` for success. " " + SendToHTTPS"," + :green:`Rules`"," + Like ``SendToHTTP`` but using a TLS connection, by default on port 443 + + NB: No certificate validation is done! + " SendToUDP"," :green:`Rules`"," Send command to other network device using UDP (non-ESP Easy units) diff --git a/docs/source/Plugin/P001.rst b/docs/source/Plugin/P001.rst index d7f198f42..fdfe7c2bf 100644 --- a/docs/source/Plugin/P001.rst +++ b/docs/source/Plugin/P001.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p00x.repl +.. include:: ../Plugin/_plugin_substitutions_p00x.repl .. _P001_page: |P001_typename| @@ -25,6 +25,9 @@ Supported hardware |P001_usedby| + +.. include:: DeviceClass_binary.repl + Commands available ------------------ diff --git a/docs/source/Plugin/P002.rst b/docs/source/Plugin/P002.rst index fe795c826..84c3f0c20 100644 --- a/docs/source/Plugin/P002.rst +++ b/docs/source/Plugin/P002.rst @@ -437,10 +437,12 @@ Change log .. versionchanged:: 2.0 ... - |improved| 2020-04-25 Added support for ESP32 ADC pins + Hall Effect Sensor. - |improved| 2022-07-11 Added ESP32 Factory calibration, multipoint processing, binning and charts. |improved| 2022-07-27 Improved resolution when using ESP32 Factory Calibration. + |improved| 2022-07-11 Added ESP32 Factory calibration, multipoint processing, binning and charts. + + |improved| 2020-04-25 Added support for ESP32 ADC pins + Hall Effect Sensor. + .. versionadded:: 1.0 ... diff --git a/docs/source/Plugin/P009.rst b/docs/source/Plugin/P009.rst index ba17f11e9..17795e354 100644 --- a/docs/source/Plugin/P009.rst +++ b/docs/source/Plugin/P009.rst @@ -110,6 +110,8 @@ Advanced event management * **Use safe button (slower)**: This effectively adds an extra De-bounce delay and sends event value ``4`` when reached. +.. include:: DeviceClass_binary.repl + .. include:: DataAcquisition.repl * **Interval** By default, Interval will be set to 60 sec. It is the frequency used to read sensor values and send these to any Controllers configured for this device. diff --git a/docs/source/Plugin/P019.rst b/docs/source/Plugin/P019.rst index 678b229d6..11ee24a0e 100644 --- a/docs/source/Plugin/P019.rst +++ b/docs/source/Plugin/P019.rst @@ -112,6 +112,8 @@ Advanced event management * **Use safe button (slower)**: This effectively adds an extra De-bounce delay and sends event value ``4`` when reached. +.. include:: DeviceClass_binary.repl + .. include:: DataAcquisition.repl * **Interval** By default, Interval will be set to 60 sec. It is the frequency used to read sensor values and send these to any Controllers configured for this device. diff --git a/docs/source/Plugin/P021.rst b/docs/source/Plugin/P021.rst index 3578f33af..851c622e0 100644 --- a/docs/source/Plugin/P021.rst +++ b/docs/source/Plugin/P021.rst @@ -136,6 +136,8 @@ Sensor * **Auto-save interval** Here a time in minutes can be set after which any changed 'Set Level' via the ``config,task,,SetLevel,`` command will be saved. This *requires* that the above setting **Save 'Set Level' after change via config command** is **disabled**! When used, a setting of ca. 30 minutes, or even longer when the Set Level is changed often, seems apropriate, unless the unit often reboots, but then that cause should be investigated and solved. The timer, when activated, should survive a warm reboot. +.. warning:: Saving settings too often will cause damage to the Flash memory of the unit! + * **Extended functionality**:sup:`1)` Enable new functionality. Changing this will reload the form showing/hiding the new options below. * **Long time span**:sup:`1)` Timer values are per default entered in [seconds], enabling this option timing settings are tuned to long intervals using [hours] or [seconds]. Changing this will reload the form showing rescaled values and new units. @@ -160,6 +162,8 @@ Sensor :sup:`1)` Configuration may not be available due to build size limitations. +.. include:: DeviceClass_binary.repl + .. include:: DataAcquisition.repl Values diff --git a/docs/source/Plugin/P033.rst b/docs/source/Plugin/P033.rst index 1e94cff88..2cc260e88 100644 --- a/docs/source/Plugin/P033.rst +++ b/docs/source/Plugin/P033.rst @@ -32,6 +32,16 @@ Dummy tasks can be used like temporary variables to store data. One nice benefit is that these task values of Dummy tasks are also kept in RTC memory. So these may survive a reboot, as long as the ESP remains powered. +Device Configuration +-------------------- + +.. image:: P033_DeviceConfiguration.png + +* **Name**: Required by ESPEasy, must be unique among the list of available devices/tasks. + +* **Enabled**: The device can be disabled or enabled. When not enabled the device should not use any resources. + + Storing Values -------------- @@ -77,7 +87,7 @@ This selector is using a categorized display when the build includes this enhanc Available options, grouped per category: -**Basic**: ``Single, Dual, Tripple, Quad`` +**Basic**: ``Single, Dual, Triple, Quad`` **Environment**: ``Temp¹, Hum¹, Baro¹, Wind¹`` @@ -147,6 +157,13 @@ And all connected controllers of that task will be handed over the values presen Especially Domoticz controllers are very specific to the output data type. +Values +^^^^^^ + +The plugin provides the ``Dummy`` value, that can be changed to the desired name. A formula can be set to recalculate the displayed, and sent, value. The number of decimals can be set as desired, and defaults to 2. + +In selected builds, per Value is a **Stats** checkbox available, that when checked, gathers the data and presents recent data in a graph, as described here: :ref:`Task Value Statistics: ` + .. Supported hardware .. ------------------ @@ -169,6 +186,9 @@ Change log .. versionchanged:: 2.0 ... + |improved| + 2025-07-17 Add categorized output configuration (ESP32 only) + |improved| 2023-04-15 Add more types of task values, like (u)int32, (u)int64 and double. diff --git a/docs/source/Plugin/P033_DeviceConfiguration.png b/docs/source/Plugin/P033_DeviceConfiguration.png new file mode 100644 index 000000000..71403ee32 Binary files /dev/null and b/docs/source/Plugin/P033_DeviceConfiguration.png differ diff --git a/docs/source/Plugin/P033_OutputDataTypeOptions.png b/docs/source/Plugin/P033_OutputDataTypeOptions.png index 3a65148a0..7df4c57ce 100644 Binary files a/docs/source/Plugin/P033_OutputDataTypeOptions.png and b/docs/source/Plugin/P033_OutputDataTypeOptions.png differ diff --git a/docs/source/Plugin/P039.rst b/docs/source/Plugin/P039.rst index dbb625303..3fbcd1bac 100644 --- a/docs/source/Plugin/P039.rst +++ b/docs/source/Plugin/P039.rst @@ -54,7 +54,7 @@ Description This Plugin reads the data from Thermocouples and Resistor Temperature Detector sensors. You have to use an Adapter Board with a respective adapter/converter in order to read the values. Take a look at the usual sources to find such boards. -You can only use ESP8266 boards which expose the SPI Interface. This Plugin uses only the Hardware SPI Interface - no software SPI at the moment. +You can use any board which expose the SPI Interface. This Plugin uses only the selected SPI Interface. Nevertheless you need at least 3 Pins to use SPI. So using an very simple ESP-01 is unfortunately no option. **Basic Information on Thermocouples** @@ -68,7 +68,7 @@ Wiring Basic Information on SPI Bus you can find in `Wikipedia: SPI Bus `_ -You need an ESP8266 device with accessible SPI Pins. These Pins are: +You need an **ESP8266** device with accessible SPI Pins. These Pins are: ==== ============= ====== ======== ========================================================================= Name Description PIO NodeMCU Notes @@ -81,6 +81,8 @@ CS Chip Select GPIO15 D8 Hardware SPI (CS is configurable through .. note:: Write Access to device registers is required for MAX 31856 and MAX 31865 devices so far. +For **ESP32** you can use any VSPI, HSPI or User Defined configuration, as the GPIO multiplexer in ESP32 chips can handle any GPIO pin with Input and Output capability for SPI. + Supported Hardware ------------------ diff --git a/docs/source/Plugin/P057.rst b/docs/source/Plugin/P057.rst index ddc52f106..505a98480 100644 --- a/docs/source/Plugin/P057.rst +++ b/docs/source/Plugin/P057.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p05x.repl +.. include:: ../Plugin/_plugin_substitutions_p05x.repl .. _P057_page: |P057_typename| @@ -26,10 +26,59 @@ Supported hardware |P057_usedby| -.. Commands available -.. ^^^^^^^^^^^^^^^^^^ +Introduction +------------ -.. .. include:: P057_commands.repl +The HT16K33 16 port generic I/O chip can be configured to act as a LED driver or as a maxtrix-keyboard input. This plugin describes settings for it to be used as a LED / 7-segment display driver. + + + +Configuration +------------- + +.. image:: P057_DeviceConfiguration.png + + +Task settings +~~~~~~~~~~~~~ + +* **Name** The name of the task. This should be unique for all devices that are configured. (Initially empty) + +* **Enabled** For the device to work it has to be enabled. When checked, the device will be started as soon as the ESP starts. If desired, the device can also be enabled from f.e. a rule by using the ``TaskEnable,`` or ``TaskEnable,`` command, or disabled using the corresponding ``TaskDisable,|`` commands. + +I2C options +^^^^^^^^^^^ + +* **I2C Address**: Select the address the chip is set for, available options: + +.. image:: P057_I2CAddressOptions.png + +The available settings here depend on the build and hardware configuration used. At least the **Force Slow I2C speed** option is available, but selections for the I2C Multiplexer can also be shown. For details see the :ref:`Hardware_page` + + + +Device Settings +~~~~~~~~~~~~~~~ + +7-Seg. Clock +~~~~~~~~~~~~ + +* **Clock Type**: Select wether the time should be automatically displayed, in either 24h or 12h format, or ``none``, so other information can be shown, using the commands below. + +* **Seg. for Xx:xx**: Choose the segment to be used for the 10-hour digit. Usually 0. + +* **Seg. for xX:xx**: Choose the segment to be used for the hour digit. Usually 1. + +* **Seg. for xx:Xx**: Choose the segment to be used for the 10-minutes digit. Usually 3 (when a colon is included). + +* **Seg. for xx:xX**: Choose the segment to be used for the minutes digit. Usually 4 (when a colon is included). + +* **Seg. for Colon**: Choose the segment to be used for the colon sign (if any). usually 2. And the value to be used for lighting up the colon. Usually (also) 2. + +Commands available +^^^^^^^^^^^^^^^^^^ + +.. include:: P057_commands.repl .. Events .. ~~~~~~ @@ -42,8 +91,7 @@ Change log .. versionchanged:: 2.0 ... - |added| - Major overhaul for 2.0 release. + |added| 2025-12-29 Documentation added. .. versionadded:: 1.0 ... diff --git a/docs/source/Plugin/P057_ClockTypeOptions.png b/docs/source/Plugin/P057_ClockTypeOptions.png new file mode 100644 index 000000000..6b4516fea Binary files /dev/null and b/docs/source/Plugin/P057_ClockTypeOptions.png differ diff --git a/docs/source/Plugin/P057_DeviceConfiguration.png b/docs/source/Plugin/P057_DeviceConfiguration.png new file mode 100644 index 000000000..ba2ebbdc6 Binary files /dev/null and b/docs/source/Plugin/P057_DeviceConfiguration.png differ diff --git a/docs/source/Plugin/P057_I2CAddressOptions.png b/docs/source/Plugin/P057_I2CAddressOptions.png new file mode 100644 index 000000000..dfa10a7d5 Binary files /dev/null and b/docs/source/Plugin/P057_I2CAddressOptions.png differ diff --git a/docs/source/Plugin/P057_commands.repl b/docs/source/Plugin/P057_commands.repl new file mode 100644 index 000000000..c8d1725a3 --- /dev/null +++ b/docs/source/Plugin/P057_commands.repl @@ -0,0 +1,50 @@ +.. csv-table:: + :header: "Command Syntax", "Extra information" + :widths: 20, 30 + + " + | ``m,[,,...]`` + "," + | Light up leds on the display, description of params below, all values treated as decimal. + + | This is a low-level command, giving exact control over what led/segment is lit. + " + " + | ``mx,[,,...]`` + "," + | Light up leds on the display, description of params below, all values treated as hexadecimal. + + | This is a low-level command, giving exact control over what led/segment is lit. + " + " + | ``mnum,[,,...]`` + "," + | Light up segments on the display, description of params below, all values to be used as numeric content for a 7-segment display. + " + " + | ``mprint,`` + "," + | Light up numeric digits on a 7-segment display. If a colon is configured, and another character would land on that segment, then the colon-segment is skipped, a colon would be shown on the colon segment. + + | This would be the standard command to display numeric data on the display, when not used in Clock-mode. + " + " + | ``mbr,<0..255>`` + "," + | Set the brightness of the display in given range, 0 is off, 255 is max. brightness. + " + " + | ```` + "," + | A ```` can be one of these: + + | ```` : A numeric (or hexadecimal, without any prefix, for ``MX`` command) value, that is displayed starting on segment 0, auto-incrementing the segment for multiple values in a single command. + + | ``=`` : Address an explicit segment with a value. + + | ``clear`` : Set all values to 0, turning off all leds/segments. + + | ``test`` : Fill the leds/segments with a test-pattern. + + | ``log`` : Write the current led-buffer values to the log (INFO level). + " diff --git a/docs/source/Plugin/P087.rst b/docs/source/Plugin/P087.rst index fe50fe1d2..df4f1420d 100644 --- a/docs/source/Plugin/P087.rst +++ b/docs/source/Plugin/P087.rst @@ -90,6 +90,18 @@ Statistic data is only visible if the plugin is configured and enabled. This shows the latest data received and some statistics. +Data options +^^^^^^^^^^^^ + +(These options are only available if build is without ``LIMIT_BUILD_SIZE`` set) + +* **Receive binary data**: When enabled accepts any character for input. Does not terminate on a CR character received, but on end of data (no more bytes available), or when using the **Fixed length input data** setting. + +* **Fixed length input data**: When 0 this feature is disabled. When set > 0 (max. length 255 characters), reading is stopped after this number of characters. Best used for reading binary data. + +* **Event with hex. data (no prefix)**: When checked will generate the event with the received data converted to hexadecimal data (length is doubled), without a prefix (often hex data is prefixed with ``0x``). Data is still filtered (when enabled) and proxied *without* conversion! + + .. include:: DataAcquisition.repl * **Interval** By default, Interval will be set to 60 sec. @@ -226,6 +238,8 @@ Change log .. versionchanged:: 2.0 ... + |added| 2025-12-23 Add support for binary data, fixed length input and convert event data to hex. + |added| 2024-02-25 Add support for ``serialproxy_test`` command and retrieving the separate groups from parsed regex. |added| 2023-03-22 Add support for writing any binary data out via the serial port. diff --git a/docs/source/Plugin/P087_DeviceConfiguration.png b/docs/source/Plugin/P087_DeviceConfiguration.png index a7215ef21..b00b977d4 100644 Binary files a/docs/source/Plugin/P087_DeviceConfiguration.png and b/docs/source/Plugin/P087_DeviceConfiguration.png differ diff --git a/docs/source/Plugin/P102.rst b/docs/source/Plugin/P102.rst index 12030e886..580305b73 100644 --- a/docs/source/Plugin/P102.rst +++ b/docs/source/Plugin/P102.rst @@ -24,7 +24,7 @@ Used libraries: |P102_usedlibraries| Description ----------- -PZEM-004T V3.0 or Version 3.0 is the upgraded version to replace the old PZEM004T V1.0. +PZEM-004T (AC) V3.0 or Version 3.0 is the upgraded version to replace the old PZEM004T V1.0. The old version has been sold out in most of the online store and is no longer being produced. Improvements over V1: @@ -99,18 +99,238 @@ Active Energy * Resolution: 1Wh * Measurement accuracy: 0.5% +Description +----------- + +PZEM-017 v1.0/v2.0 (DC) For measuring DC loads. When the voltage is below 7V, an independent (**not from PC USB port!**) USB powersupply should be used on the USB input connector. + +Specifications +-------------- + +Voltage +^^^^^^^ + +* Measuring range: 0.05 - 300V +* Resolution: 0.01V +* Measurement accuracy: 1% + +Current +^^^^^^^ + +* Measuring range: + + * 0.02 - 50/100/200/300A(Depending on shunt) + +* Resolution: 0.01A + +* Measurement accuracy: 1% + + +Active power +^^^^^^^^^^^^ + +* Measuring range: + + * 0.2 - 90kW(Depending on shunt) + +* Starting measure power: 0.2W +* Resolution: 0.1W +* Measurement accuracy: 1% + + +Active Energy +^^^^^^^^^^^^^ + +* Measuring range: 0 - 9999.99kWh +* Resolution: 1Wh +* Measurement accuracy: 1% + +Installation and configuration +------------------------------ + +When installing multiple PZEM devices on a single ESP unit, some specific order of configuration is required to be able to correctly configure a second, third, etc. PZEM. + +This is described below, in the :cyan:`Installing multiple PZEM units on a single ESP` paragraph. + +Configuration +------------- + +.. image:: P102_DeviceConfiguration.png + +Task Settings +^^^^^^^^^^^^^ + +* **Name**: Required by ESPEasy, must be unique among the list of available devices/tasks. + +* **Enabled**: The device can be disabled or enabled. When not enabled the device should not use any resources. + Sensor ^^^^^^ See: :ref:`SerialHelper_page` +Device Settings +^^^^^^^^^^^^^^^ + +* **PZEM Model**: Select the PZEM model that is to be handled by this task. (For changing the Address the model doesn't matter as they use the same command.) + +Available options: + +* *PZEM-004Tv30 (AC)*: The AC model, and default setting. +* *PZEM-017v1 (DC)*: The DC model. Also compatible with later v2/2.0 models. + +For reading the correct values from a PZEM-017, that Model *must* be selected as it uses a different data format to fetch the results. + +PZEM Actions +^^^^^^^^^^^^ + +* **PZEM Mode**: By default, the ``Read value`` option is selected. + +Available options: + +* *Read value*: Normal operating mode, for reading the configured values from the PZEM. +* *Reset Energy*: For resetting the counted total energy, this option can be selected. See below for a description. +* *Program address*: Only available on the *first* PZEM device, for changing the PZEM unit MODBUS address, as described below. + +| + +* **Address of PZEM**: Set to the address of the PZEM unit to be used. When only a single device is connected, can be set to 0, as a sort of 'universal' address. + +Available address range for the *first* PZEM device: 0 .. 247, on subsequent PZEM devices: 1 .. 247. + +Output Configuration +^^^^^^^^^^^^^^^^^^^^ + +For all 4 available Values slots for this task, a value to use can be selected here. The available options depend om the **PZEM Model** setting: + +* *Voltage_V* +* *Current_A* +* *Power_W* +* *Energy_kWh* +* *Power_Factor_cosphi*: for ``PZEM-004Tv30 (AC)`` only. +* *Frequency_Hz*: for ``PZEM-004Tv30 (AC)`` only. + .. include:: DataAcquisition.repl +* **Interval**: By default, Interval will be set to 60 sec for this plugin. It is the frequency used to read sensor values and send these to any Controllers configured for this device. + +Values +^^^^^^ + +The measured values are configured above, and get their names based on the selection made there. + +For a complete description of all available columns see the `Plugin - Values <_Plugin.html#values>`_ section. + +| + + .. Events .. ~~~~~~ .. .. include:: P102_events.repl +Resetting the Energy counter +---------------------------- + +For resetting the counted Energy value in the PZEM unit, there are 2 methods: + +1. Use the *Reset Energy* **PZEM Mode** setting. +2. Send the command ``resetenergy,`` to the ESP unit. This can be done via the Command entry on the Tools page, from Rules, or via a url command as shown in the configuration page. + +Use the *Reset Energy* **PZEM Mode** setting +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +For resetting the incrementing Energy counter in the PZEM unit, this PZEM Mode option can be used. + +* Set **PZEM Mode** to ``Reset Energy``, and :cyan:`Submit` the page. + +The command will be executed immediately, and afterward the status will shown: + +.. image:: P102_ResetEnergyDone.png + +The blue message showing that the command was sent. The **PZEM Mode** is already reset to ``Read value``. + +To store this change in settings, the :cyan:`Submit` button should be used once more (you can of course change more settings if desired, that's why no automatic save is done.) + +Send the ``resetenergy,`` command +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +For an easy alternative to reset the Energy counter, you can use the ``resetenergy,`` command, f.e. from the ``Command`` field on the Tools page, to reset the counter for the specific modbus address (range 1..247, or 0 if only 1 device is on the bus). + +Installing multiple PZEM units on a single ESP +---------------------------------------------- + +Configuring separate MODBUS addresses on each device +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To uniquely identify each PZEM unit on the bus, it has to be configured to use a unique address. The range of these addresses is 1 .. 247. + +* Add the, numerically, first PZEM task, f.e. in Devices slot 1. + +* Configure the serial port, and on ESP32 also the GPIO pins, to be used, enable the plugin, and connect a single PZEM to the RS485 converter. When using ``HW Serial0``, the Serial Console Settings, on the Tools/Advanced page, have to be disabled to avoid issues with his port. + +* Set **PZEM Mode** to ``Program address`` and :cyan:`Submit` the page. + +.. image:: P102_ProgramAddress.png + +Fill the desired address in the **Address of PZEM** field, change the **Confirm address programming ?** field to ``YES``, and again :cyan:`Submit` the page. + +When successful, a message :green:`Address programming done !` is displayed, and the **PZEM Mode** is set to the default ``Read value`` setting. + +* Disconnect the PZEM unit from the RS485 bus, connect the next PZEM unit, and repeat above steps, using a diferent **Address of PZEM** value, for all units to be connected. + +N.B.: The last set Address of PZEM will be kept in the configuration, but can be changed to the desired first PZEM unit. + +N.B.2: To correctly store the **PZEM Mode** setting, the configuration should be saved once more, using the :cyan:`Submit` button! + +Configuring a secondary PZEM unit +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The first PZEM unit can be configured as shown above in the generic Configuration paragraph. Once thats completed, with the correct, unique, MODBUS Address set, both in all PZEM units to connect and the task configuration for the first PZEM task, that task should be Enabled, so the next task added can detect that it will be the second (or third, etc.) device on the MODBUS chain. + +After that task is running, a new PZEM task can be added, with a task number higher than the *first* PZEM task. + +.. warning:: This is crucial for the PZEM tasks to start correctly, and in the correct order, on reboot or power-up of the ESP! + +These secondary PZEM tasks do not use the Serial configuration, as they use the same connection as the *first* PZEM task. + +.. image:: P102_SecondConfiguration.png + +Sensor +^^^^^^ + +This selector should not be used, as it is ignored. + +Device Settings +^^^^^^^^^^^^^^^ + +* **PZEM Model**: Select the PZEM model that is to be handled by this task. (Changing the selection will save the new setting and reload the page.) + +For reading the correct values from a PZEM-017, that Model *must* be selected as it uses a different data format to fetch the results. + +PZEM Actions +^^^^^^^^^^^^ + +* **PZEM Mode**: By default, the ``Read value`` option is selected. + +Available options: + +* *Read value*: Normal operating mode, for reading the configured values from the PZEM. +* *Reset Energy*: For resetting the counted total energy, this option can be selected. See above for a description. + +* **Address of PZEM**: Set to the address of the PZEM unit to be used. + +Available address range: 1 .. 247. + +When using the same address as the *first* PZEM task, and the **PZEM Model** connected is a ``PZEM-004Tv30 (AC)``, the extra values ``Power_Factor_cosphi`` and ``Frequency_Hz`` can be made available in a task, for sending to a controller. + +| + +Commands +-------- + +.. include:: P102_commands.repl + Change log @@ -119,4 +339,6 @@ Change log .. versionchanged:: 2.0 ... + |added| 2025-08-06: Support for PZEM-017, updated documentation. + |added| 2020-04-25 diff --git a/docs/source/Plugin/P102_DeviceConfiguration.png b/docs/source/Plugin/P102_DeviceConfiguration.png new file mode 100644 index 000000000..95b8c81d4 Binary files /dev/null and b/docs/source/Plugin/P102_DeviceConfiguration.png differ diff --git a/docs/source/Plugin/P102_ProgramAddress.png b/docs/source/Plugin/P102_ProgramAddress.png new file mode 100644 index 000000000..aa2bdbfc1 Binary files /dev/null and b/docs/source/Plugin/P102_ProgramAddress.png differ diff --git a/docs/source/Plugin/P102_ResetEnergyDone.png b/docs/source/Plugin/P102_ResetEnergyDone.png new file mode 100644 index 000000000..84183a0b2 Binary files /dev/null and b/docs/source/Plugin/P102_ResetEnergyDone.png differ diff --git a/docs/source/Plugin/P102_SecondConfiguration.png b/docs/source/Plugin/P102_SecondConfiguration.png new file mode 100644 index 000000000..2b2dea13c Binary files /dev/null and b/docs/source/Plugin/P102_SecondConfiguration.png differ diff --git a/docs/source/Plugin/P102_commands.repl b/docs/source/Plugin/P102_commands.repl new file mode 100644 index 000000000..1ebe20567 --- /dev/null +++ b/docs/source/Plugin/P102_commands.repl @@ -0,0 +1,15 @@ +.. csv-table:: + :header: "Command Syntax", "Extra information" + :widths: 20, 30 + + " + + | ``ResetEnergy,`` + + "," + + | This command will reset the Energy counter for the PZEM unit (value is stored in the unit). The value retrieved on the next read should return the updated value. (Can be 0 if no energy is flowing, or the energy counted since the reset command was executed.) + + | This command can be executed by any task if multiple PZEM tasks are configured, as they all share the single MODBUS Serial configuration of the *first* PZEM task. + + " diff --git a/docs/source/Plugin/P116.rst b/docs/source/Plugin/P116.rst index 4e7621293..4b1309747 100644 --- a/docs/source/Plugin/P116.rst +++ b/docs/source/Plugin/P116.rst @@ -28,9 +28,10 @@ The ST7735, ST7789 and ST7796 chip families drive color TFT displays in various This plugin supports these display models: -* **ST7735** with resolutions 128 x 128, 128 x 160, 80 x 160, 135 x 240, 170 x 320 and 172 x 320 pixels +* **ST7735** with resolutions 128 x 128, 128 x 160, 80 x 160, 135 x 240 and 172 x 320 pixels * **ST7789** with resolutions 240 x 320, 240 x 240, 240 x 280 and 135 x 240 pixels * **ST7796** with resolution of 320 x 480 pixels. +* **ST77xx** (unknown if this is ST7735 or ST7789) with resolution of 170 x 320, 240 x 320, 240 x 280 pixels. The plugin can be configured to show predefined text, a reservation for up to 24 lines is made, but only the lines that can fit on-screen will be shown, or via commands, sent via http or from rules, text or graphical elements like lines, rectangles, circles, pixels, etc., can be shown. @@ -146,6 +147,8 @@ Available options: * *ST7735 135 x 240px*: Added to support a revision of the TTGO T-Display 16MB Flash module, that won't work with the ST7789 driver, the seller is claiming to use, but does work with this specially crafted ST7735 driver. * *ST7735 172 x 320px*: Added to support an ESP32-C6 module having a display with this specific resolution. * *ST77xx 170 x 320px*: Added to support ST7789v3 / ST7735 displays with this specific resolution. +* *ST77xx 240 x 320px*: Added to support ST7789v3 / ST7735 displays with this specific resolution. +* *ST77xx 240 x 280px*: Added to support ST7789v3 / ST7735 displays with this specific resolution. * *ST7789 240 x 320px*: Allows 32 lines of text in the smallest font scaling setting. Predefined text only goes to 24, extra lines can be displayed from rules or external commands. * *ST7789 240 x 240px*: Allows 24 lines of text in the smallest font scaling setting. * *ST7789 240 x 280px*: Allows 28 lines of text in the smallest font scaling setting. Predefined text only goes to 24, extra lines can be displayed from rules or external commands. diff --git a/docs/source/Plugin/P159.rst b/docs/source/Plugin/P159.rst index 30fd97358..a79975208 100644 --- a/docs/source/Plugin/P159.rst +++ b/docs/source/Plugin/P159.rst @@ -27,6 +27,8 @@ Description Detecting (human) presence can be achieved in several ways, like via IR detection sensors, or a light-beam that's interrupted by someone passing. But these sensors have quite some limitations, like limited sensitivity, no information about the distance, or weather a person is there but not moving. The limitations of these existing sensors are not applicable when using radar-based presence detection, and since some time, rather affordable radar human-presence detectors are available. These are available with 5 GHz, 10 GHz, 24 GHz and 60 GHz radar frequency transmitter/receiver chips. Because of the accuracy and range, achievable with 24 GHz sensors, these are quite popular, so support has been added to ESPEasy. +Some notes about "false positive" detections: based on our test and experience some false positive presence detections can happens. For example if the sensor is near to plant, the movement of leaves can be incorrectly translated as presence detection; also electric cables in front of the sensor can provide noise to the sensor. To avoid false positive presence detection try to leave the sensor as free from obstacles as possible. + Some examples of these sensors: diff --git a/docs/source/Plugin/P180.rst b/docs/source/Plugin/P180.rst index 463ded25e..bbd986c3c 100644 --- a/docs/source/Plugin/P180.rst +++ b/docs/source/Plugin/P180.rst @@ -30,6 +30,17 @@ This enables the implementation of yet unsupported I2C devices or use I2C device .. warning:: This plugin is in the 'pro' category of plugins (i.o.w. not really *Easy* to use), and requires in-depth studying of the datasheet for the device, extracting the required commands and calculation(s) for implementing correct usage. (Some examples available below) +Example device configurations +----------------------------- + +* :ref:`aht2x-temperature-humidity-sensor` +* :ref:`m5stack-ain4-20ma-unit-module` +* :ref:`bosch-bmp58x-temp-baro` +* :ref:`tiny-code-reader-qr-code-reader` +* :ref:`sensirion-sdp-810-differential-pressure-sensor` +* :ref:`tf-luna-lidar-distance-sensor` +* :ref:`qmc6310-3-axis-magnetic-sensor` + Configuration ------------- @@ -189,6 +200,8 @@ A few warnings though: Example I2C command sequences ----------------------------- +.. _aht2x-temperature-humidity-sensor: + AHT2x Temperature / Humidity sensor ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -231,6 +244,8 @@ Command sequence explanation: | +.. _m5stack-ain4-20ma-unit-module: + M5Stack AIN4-20mA Unit / Module ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -277,6 +292,8 @@ This device supports adjusting the calibration to the current current that is fl | +.. _bosch-bmp58x-temp-baro: + Bosch BMP58x Temp/Baro ^^^^^^^^^^^^^^^^^^^^^^ @@ -341,6 +358,8 @@ Command sequence explanation: | +.. _tiny-code-reader-qr-code-reader: + Tiny Code Reader (QR-code reader) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -370,7 +389,7 @@ Command sequence explanation: * ``value.1``: Store value in first **Values** field. * ``get.str.=%value%+2``: Read a string from the code-reader, for indicated length, and including the 2 byte lengthe prefix. * ``eval``: Load the value to be used. -* ``letstr.qrtxt.`{substring:1::'%value%'}``: Save the received string into a String variable named ``qrtxt``, skipping the first character, that holds the length. De actual size prefix is 2 bytes, but ``\0`` characters are ignored when reading a string, and the max. length that the reader will return is 255 characters, so the first byte is always 0 and thus skipped. +* ``letstr.qrtxt.`{substring:1::'%value%'}```: Save the received string into a String variable named ``qrtxt``, skipping the first character, that holds the length. De actual size prefix is 2 bytes, but ``\0`` characters are ignored when reading a string, and the max. length that the reader will return is 255 characters, so the first byte is always 0 and thus skipped. Additionally, for de-duplicating of reading and handling a QR code, this code can be appended to **I2C Commands 1** to reset the Size value after 10 seconds, and Rules code to handle the received text. @@ -409,12 +428,192 @@ This device needs a Rule to handle the result, as we can't store string values i | +.. _sensirion-sdp-810-differential-pressure-sensor: + +Sensirion SDP-810 Differential pressure sensor +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Based on this data sheet `Sensirion Differential Pressure Datasheet SDP8xx `_ + +.. note:: The checksum available from the sensor measurements is ignored! + +The plugin should be configured with these settings: + +* **I2C Address (Hex)**: ``0x25`` (or ``0x26`` when the model number is SDP-8x1) + +* **Output Data Type**: ``Temp / Baro`` + +* **Interval**: can be set to 60 seconds to retrieve a measurement every minute. + +* **Values**: the names can be changed to Temp or Temperature and Baro or Pressure, and the number of decimals adjusted as desired (usually 1 or 2 decimals is fine for temperature, and 0 for pressure). Stats can be enabled, best would be to set the Axis to L1 for Temperature, and R1 for Pressure, as the scales don't match the same range. + +* **I2C Init Commands** sequence for initializing the sensor: + +``put.b.2.0x36.0x03``: Start continuous measurement. + +* **I2C Exit Commands** sequence for shutting down the sensor: + +``put.b.2.0x3f.0xf9``: Stop continuous measurement. + +* **Cache-Name 1 (optional)**: Fill with a useable name, f.e. ``sdp``, so the I2C Command sequence is cached for much improved execution-performance. + +* **I2C Commands 1**: + +``get.b.9;eval;let.tmp.0x{substring:8:12:%h%};if.{and:%v_tmp%:0x8000}.1;let.tmp.%v_tmp%-0x10000;calc.%v_tmp%/0x{substring:14:18:%h%};value.1;let.prs.0x{substring:2:6:%h%};if.{and:%v_prs%:0x8000}.1;let.prs.%v_prs%-0x10000;calc.%v_prs%/0x{substring:14:18:%h%};value.2`` + +Command sequence explanation: + +* ``get.b.9``: Read 9 bytes from the sensor. +* ``eval``: Load the value to be used. +* ``let.tmp.0x{substring:8:12:%h%}``: Assign the raw temperature to variable ``tmp``. +* ``if.{and:%v_tmp%:0x8000}.1``: Check if positive, if so, skip next 1 command. +* ``let.tmp.%v_tmp%-0x10000``: Subtract 65536 to get the correct negative value. +* ``calc.%v_tmp%/0x{substring:14:18:%h%}``: Divide the raw temperature by the scale factor. +* ``value.1``: Store the temperature in Value 1. +* ``let.prs.0x{substring:2:6:%h%}``: Assign the raw pressure to variable ``prs``. +* ``if.{and:%v_prs%:0x8000}.1``: Check if positive, if so, skip next 1 command. +* ``let.prs.%v_prs%-0x10000``: Subtract 65536 to get the correct negative value. +* ``calc.%v_prs%/0x{substring:14:18:%h%}``: Divide the raw pressure by the scale factor. +* ``value.2``: Store the pressure in Value 2. + +Alternative read method +~~~~~~~~~~~~~~~~~~~~~~~ + +Instead of having the sensor doing continuous measurements, and returning the average value since the last read, a single measurement can be started, wait a little to complete the measurement, and read the results. + +In this case, the **I2C Init Commands** and **I2C Exit Commands** fields must be made empty, and **I2C Commands 1** should be set to: + +``put.b.2.0x36.0x24;delay.50;get.b.9;eval;let.tmp.0x{substring:8:12:%h%};if.{and:%v_tmp%:0x8000}.1;let.tmp.%v_tmp%-0x10000;calc.%v_tmp%/0x{substring:14:18:%h%};value.1;let.prs.0x{substring:2:6:%h%};if.{and:%v_prs%:0x8000}.1;let.prs.%v_prs%-0x10000;calc.%v_prs%/0x{substring:14:18:%h%};value.2`` + +This command sequence is nearly the same as with the Continuous measurement, except there are a few commands added *before* the values are read. + +* ``put.b.2.0x36.0x24``: Send the command to start a single measurement. +* ``delay.50``: Insert an asynchronous delay of 50 msec, as the sensor needs ca. 45 msec to complete a measurement. This delay does **not** block processing, as is described above. +* The rest of the commands is already explained in the Continuous measurement description, above this paragraph. + +| + +.. _tf-luna-lidar-distance-sensor: + +TF-Luna Lidar distance sensor +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The somewhat popular laser-distance sensor, with a range of 0.2 to 8 meter, TF-Luna, can be configured to use I2C as the communication protocol, instead of the default RS232 serial protocol, by connecting pin 5 of the sensor to GND on startup. + +The command sequence is based on documentation from this `WaveShare page `_ + +.. note:: The checksum available from the sensor measurements is ignored! + +When only retrieving the ``distance`` from the sensor +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The plugin should be configured with these settings: + +* **I2C Address (Hex)**: ``0x10`` + +* **Output Data Type**: ``Single`` + +* **Interval**: can be set to 1 second to retrieve a measurement every second. + +* **Values**: the name can be changed to Distance, and the number of decimals 0. Stats can be enabled, as desired. The Unit of Measure should he set to ``cm``, and the Value Type to ``Distance`` + +* **Cache-Name 1 (optional)**: Fill with a useable name, f.e. ``distance``, so the I2C Command sequence is cached for much improved execution-performance. + +* **I2C Commands 1**: + +``put.u8.0;delay.5;get.b.7;eval;calc.0x{substring:4:6:%h%}*256+0x{substring:2:4:%h%}`` + +Command sequence explanation: + +* ``put.u8.0`` : Announce data fetch +* ``delay.5`` : Give the sensor a few msec to prepare +* ``get.b.7`` : Read 7 bytes (entire available buffer) +* ``eval`` : Make data available for processing +* ``calc.0x{substring:4:6:%h%}*256+0x{substring:2:4:%h%}`` : Fetch byte 1 (* 256) + byte 0 to calculate distance in cm, value is set to Values 1 field + +When retrieving both ``distance`` and ``signal strength`` from the sensor +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The plugin should be configured with these settings: + +* **I2C Address (Hex)**: ``0x10`` + +* **Output Data Type**: ``Dual`` + +* **Interval**: can be set to 1 second to retrieve a measurement every second. + +* **Values**: the first name can be changed to ``Distance``, and the number of decimals 0. Stats can be enabled, as desired. The Unit of Measure should he set to ``cm``, and the Value Type to ``Distance``, the second name can be changed to f.e. ``SignalStrength``, and number of decimals to 0. Stats can be enabled, as desired. No further settings for this value, as it has an undefined unit and value type. + +* **Cache-Name 1 (optional)**: Fill with a useable name, f.e. ``distance``, so the I2C Command sequence is cached for much improved execution-performance. + +* **I2C Commands 1**: + +``put.u8.0;delay.5;get.b.7;eval;calc.0x{substring:4:6:%h%}*256+0x{substring:2:4:%h%};value.1;calc.0x{substring:8:10:%h%}*256+0x{substring:6:8:%h%};value.2`` + +NB: The fields for **Cache-Name 2** and **I2C Commands 2** are left empty, the entire command sequence is combined into the first command sequence, as the complete data set is read and the separate values calculated and assigned in a single command sequence. This makes processing more efficient. + +Command sequence explanation: + +* ``put.u8.0`` : Announce data fetch +* ``delay.5`` : Give the sensor a few msec to prepare +* ``get.b.7`` : Read 7 bytes (entire available buffer) +* ``eval`` : Make data available for processing +* ``calc.0x{substring:4:6:%h%}*256+0x{substring:2:4:%h%}`` : Fetch byte 1 (* 256) + byte 0 to calculate distance in cm +* ``value.1`` : current value, Distance, set to Values 1 field +* ``calc.0x{substring:8:10:%h%}*256+0x{substring:6:8:%h%}`` : Fetch byte 3 (* 256) + byte 2 to calculate signal strength (unknown unit) +* ``value.2`` : current value, Signal strength, set to Values 2 field + +| + +.. _qmc6310-3-axis-magnetic-sensor: + +QMC6310 3-axis magnetic sensor +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This sensor provides a 3-axis directional signal, as documented in the QST Corp `QMC6310 Datasheet `_ (url includes some Chinese characters). + +The plugin should be configured with these settings: + +* **I2C Address (Hex)**: ``0x1C`` + +* **Output Data Type**: ``Triple`` To activate this setting, the page must be submitted to show the extra input fields. + +* **Interval**: Can be set in the seconds range to retrieve a measurement often. + +* **Values**: The names can be changed to ``x``, ``y`` and ``z``, and the number of decimals to 0. Stats can be enabled, as desired. The Unit of Measure is undocumented. + +* **I2C Init Commands** sequence for initializing the sensor: + +``write.u8.0x29.0x06;write.u8.0x0B.0x00;write.u8.0x0A.0xCD``: Initialize the sensor according to the datasheet, Normal mode, field range 8 Gauss, output data rate 200 Hz, over-sample ratio 8, down-sample ratio 8. + +* **I2C Exit Commands** sequence for shutting down the sensor: + +``write.u8.0x0A.0x00``: Set sensor in Suspend mode. + +* **Cache-Name 1 (optional)**: Fill with a useable name, f.e. ``x``, so the I2C Command sequence is cached for much improved execution-performance. + +* **I2C Commands 1**: ``read.16le.0x01`` Read 2 bytes starting at register 1, the ``x`` value, and store in first Value field. + +* **Cache-Name 2 (optional)**: Fill with a useable name, f.e. ``y``. + +* **I2C Commands 2**: ``read.16le.0x03`` Read 2 bytes starting at register 3, the ``y`` value, and store in second Value field. + +* **Cache-Name 3 (optional)**: Fill with a useable name, f.e. ``z``. + +* **I2C Commands 3**: ``read.16le.0x05`` Read 2 bytes starting at register 5, the ``z`` value, and store in third Value field. + +To reduce the logging content, **Single event with all values** can be enabled, to generate a single event ``#All`` with all values as argument, and thus reduce the logging from 3 lines per Interval to a single line per Interval. + +| + Change log ---------- .. versionchanged:: 2.0 ... + |added| 2025-12-08 Add index to example configurations. + |added| 2025-04-19 Initial release version. diff --git a/docs/source/Plugin/_Plugin.rst b/docs/source/Plugin/_Plugin.rst index bbe0800b3..131b0c33f 100644 --- a/docs/source/Plugin/_Plugin.rst +++ b/docs/source/Plugin/_Plugin.rst @@ -93,15 +93,19 @@ Enabling "Stats" on a task value also extends how task values can be addressed w For example using just like normal task value data: -* ``[bme#temp.avg]`` Compute the average over the last N samples in the historic buffer (typically: 64 samples on ESP32, 16 on ESP8266) +* ``[bme#temp.avg]`` Compute the average over the last N samples in the historic buffer (typically: 250 samples on ESP32, 16 on ESP8266) * ``[bme#temp.avgX]`` Compute the average over the last X samples (or less if there are less samples available) -* ``[bme#temp.stddev]`` Compute the standard deviation over the last N samples in the historic buffer (typically: 64 samples on ESP32, 16 on ESP8266) +* ``[bme#temp.stddev]`` Compute the standard deviation over the last N samples in the historic buffer (typically: 250 samples on ESP32, 16 on ESP8266) * ``[bme#temp.stddevX]`` Compute the standard deviation over the last X samples (or less if there are less samples available) -* ``[bme#temp.max]`` Refer to the maximum recorded sample since the last ``resetpeaks``. N.B. Not all tasks log the min and max peaks. -* ``[bme#temp.min]`` See ``[bme#temp.max]`` +* ``[bme#temp.max]`` Refer to the maximum recorded sample in available samples (rolling maximum) +* ``[bme#temp.maxX]`` Refer to the maximum recorded sample in the last X samples (or less if there are less samples available) +* ``[bme#temp.maxp]`` (max-peak) Refer to the maximum recorded sample since the last ``resetpeaks``. N.B. Not all tasks log the min and max peaks. +* ``[bme#temp.min]`` Refer to the minimum recorded sample in available samples (rolling minimum) +* ``[bme#temp.minX]`` Refer to the minimum recorded sample in the last X samples (or less if there are less samples available) +* ``[bme#temp.minp]`` (min-peak) See ``[bme#temp.maxp]``. * ``[bme#temp.size]`` Return the number of samples in memory. -* ``[bme#temp.sample]`` Access the last sample in memory. -* ``[bme#temp.sampleN]`` Access the N-th last sample in memory. +* ``[bme#temp.sample]`` Return the number of samples in memory. (Doc. reflects the actual code!) +* ``[bme#temp.sampleN]`` Access the N-th last sample in memory, negative value accesses N-th value from the *oldest* available sample. Commands on "Stats" data: @@ -250,10 +254,10 @@ If set the UoM will be space-appended to the value when displayed on the Devices A list of 165+ Unit of Measure values is available (derived of what's supported/expected by Home Assistant): ``°C, °F, K, %, Pa, hPa, bar, mbar, inHg, psi, W, kW, V, Wh, kWh, A, VA, mm, cm, m, km,`` ``L, mL, m³, ft³, m³/h, ft³/h, lx, UV index, µg/m³, mg/m³, p/m³, ppm, ppb,`` -``°, €, $, ¢, µs, ms, s, min, h, d, w, m, y, in, ft, yd, mi, Hz, GHz, gal, fl. oz, m²,`` ``g, kg, mg, µg, oz, lb, µS/cm, W/m², mm/h, mm/s, in/s, m/s, in/h, km/h, mph, db, dBm,`` +``°, €, $, ¢, µs, ms, s, min, h, d, w, m, y, in, ft, yd, mi, Hz, GHz, gal, fl. oz, m²,`` ``g, kg, mg, µg, oz, lb, µS/cm, W/m², mm/h, mm/s, in/s, m/s, in/h, km/h, mph, dB, dBm,`` ``bit, kbit, Mbit, Gbit, B, kB, MB, GB, TB, PB, EB, ZB, YB, KiB, MiB, GiB, TiB, PiB, EiB, ZiB, YiB,`` ``bit/s, kbit/s, Mbit/s, Gbit/s, B/s, kB/s, MB/s, GB/s, KiB/s, MiB/s, GiB/s,`` ``ft/s, kn, mW, MW, GW, TW, BTU/(h·ft²), pH, cbar, mmHg, kPa, mA, µA, mV, µV, kV, cm², km², mm²,`` ``in², ft², yd², mi², ac, ha, kHz, MHz, mWh, MWh, GWh, TWh, cal, kcal, Mcal,`` -``Gcal, J, kJ, MJ, GJ, var, kvar, varh, kvarh, st, mg/dL, mmol/L, µSv, µSv/h, m³/s, ft³/min,`` ``L/h, L/min, L/s, gal/min, mL/s, g/m³, kWh/100km, Wh/km, mi/kWh, km/kWh, in/d, mm/d`` +``Gcal, J, kJ, MJ, GJ, var, kvar, varh, kvarh, st, mg/dL, mmol/L, µSv, µSv/h, m³/s, ft³/min,`` ``L/h, L/min, L/s, gal/min, mL/s, g/m³, kWh/100km, Wh/km, mi/kWh, km/kWh, in/d, mm/d, Ah`` Displaying the Unit of Measure in the Devices overview page can be disabled by unchecking the **Show Unit of Measure** checkbox on the Tools/Advanced page. @@ -261,6 +265,7 @@ The **Unit of Measure** selector is, because of the vast number of options, divi .. image:: Task_config_page_UoM_selector_part.png +Depending on the available data, the number of options is limited as much as possible, to reduce page-load time, as filling this many option in multiple comboboxes takes quite some time. Value Type ^^^^^^^^^^ @@ -324,7 +329,7 @@ There are different released versions of ESP Easy: :red:`DEVELOPMENT` is used for plugins that are still being developed and are not considered stable at all. Currently there are no DEVELOPMENT builds available. -:yellow:`ENERGY` :yellow:`DISPLAY` :yellow:`IR` :yellow:`IRext` :yellow:`NEOPIXEL` :yellow:`CLIMATE` are specialized builds holding all Energy-, Display-, Infra Red- (extended), NeoPixel- and Climate- related plugins. +:yellow:`ENERGY` :yellow:`DISPLAY` (split into sets A..B) :yellow:`IR` :yellow:`IRext` :yellow:`NEOPIXEL` :yellow:`CLIMATE` (split into sets A..B) are specialized builds holding all Energy-, Display-, Infra Red- (extended), NeoPixel- and Climate- related plugins. :yellow:`MAX` is the build that has all plugins that are available in the ESPEasy repository. Available for ESP32 16MB and ESP32 8MB Flash units (available for ESP32 Classic, ESP32-S3 and ESP32-C6). diff --git a/docs/source/Plugin/_plugin_categories.repl b/docs/source/Plugin/_plugin_categories.repl index cc72b4a5e..0c4fe03df 100644 --- a/docs/source/Plugin/_plugin_categories.repl +++ b/docs/source/Plugin/_plugin_categories.repl @@ -5,8 +5,8 @@ .. |Plugin_Display| replace:: :ref:`P012_page`, :ref:`P023_page`, :ref:`P036_page`, :ref:`P057_page`, :ref:`P073_page`, :ref:`P075_page`, :ref:`P095_page`, :ref:`P104_page`, :ref:`P116_page`, :ref:`P131_page`, :ref:`P148_page` .. |Plugin_Distance| replace:: :ref:`P013_page`, :ref:`P110_page`, :ref:`P113_page`, :ref:`P134_page` .. |Plugin_Dust| replace:: :ref:`P018_page`, :ref:`P053_page`, :ref:`P056_page`, :ref:`P144_page`, :ref:`P175_page` -.. |Plugin_Energy_AC| replace:: :ref:`P076_page`, :ref:`P077_page`, :ref:`P078_page`, :ref:`P102_page`, :ref:`P108_page` -.. |Plugin_Energy_DC| replace:: :ref:`P027_page`, :ref:`P085_page`, :ref:`P115_page`, :ref:`P132_page` +.. |Plugin_Energy_AC| replace:: :ref:`P076_page`, :ref:`P077_page`, :ref:`P078_page`, :ref:`P092_page`, :ref:`P102_page`, :ref:`P108_page`, :ref:`P176_page` +.. |Plugin_Energy_DC| replace:: :ref:`P027_page`, :ref:`P085_page`, :ref:`P102_page`, :ref:`P115_page`, :ref:`P132_page`, :ref:`P176_page` .. |Plugin_Energy_Heat| replace:: :ref:`P088_page`, :ref:`P093_page` .. |Plugin_Environment| replace:: :ref:`P004_page`, :ref:`P005_page`, :ref:`P006_page`, :ref:`P014_page`, :ref:`P024_page`, :ref:`P028_page`, :ref:`P030_page`, :ref:`P031_page`, :ref:`P032_page`, :ref:`P034_page`, :ref:`P039_page`, :ref:`P047_page`, :ref:`P051_page`, :ref:`P068_page`, :ref:`P069_page`, :ref:`P072_page`, :ref:`P103_page`, :ref:`P105_page`, :ref:`P106_page`, :ref:`P122_page`, :ref:`P150_page`, :ref:`P151_page`, :ref:`P153_page`, :ref:`P154_page`, :ref:`P163_page`, :ref:`P167_page`, :ref:`P169_page`, :ref:`P172_page`, :ref:`P173_page`, :ref:`P177_page` .. |Plugin_Extra_IO| replace:: :ref:`P011_page`, :ref:`P022_page`, :ref:`P178_page` diff --git a/docs/source/Plugin/_plugin_sets_overview.repl b/docs/source/Plugin/_plugin_sets_overview.repl index 84a7d39ab..6ba38b911 100644 --- a/docs/source/Plugin/_plugin_sets_overview.repl +++ b/docs/source/Plugin/_plugin_sets_overview.repl @@ -71,6 +71,8 @@ Build set: :green:`NORMAL` ":ref:`C009_page`","C009" ":ref:`C010_page`","C010" ":ref:`C013_page`","C013" + ":ref:`N001_page`","N001" + ":ref:`N002_page`","N002" Build set: :yellow:`COLLECTION A` --------------------------------------------- @@ -88,20 +90,26 @@ Build set: :yellow:`COLLECTION A` ":ref:`P004_page`","P004" ":ref:`P005_page`","P005" ":ref:`P006_page`","P006" + ":ref:`P007_page`","P007" + ":ref:`P008_page`","P008" + ":ref:`P009_page`","P009" ":ref:`P010_page`","P010" ":ref:`P011_page`","P011" ":ref:`P012_page`","P012" ":ref:`P013_page`","P013" ":ref:`P014_page`","P014" ":ref:`P015_page`","P015" + ":ref:`P017_page`","P017" ":ref:`P018_page`","P018" ":ref:`P019_page`","P019" ":ref:`P020_page`","P020" ":ref:`P021_page`","P021" + ":ref:`P022_page`","P022" ":ref:`P023_page`","P023" ":ref:`P024_page`","P024" ":ref:`P025_page`","P025" ":ref:`P026_page`","P026" + ":ref:`P027_page`","P027" ":ref:`P028_page`","P028" ":ref:`P029_page`","P029" ":ref:`P031_page`","P031" @@ -110,7 +118,6 @@ Build set: :yellow:`COLLECTION A` ":ref:`P034_page`","P034" ":ref:`P036_page`","P036" ":ref:`P037_page`","P037" - ":ref:`P038_page`","P038" ":ref:`P039_page`","P039" ":ref:`P040_page`","P040" ":ref:`P043_page`","P043" @@ -161,8 +168,10 @@ Build set: :yellow:`COLLECTION A` ":ref:`P134_page`","P134" ":ref:`P137_page`","P137" ":ref:`P138_page`","P138" + ":ref:`P139_page`","P139" ":ref:`P146_page`","P146" ":ref:`P152_page`","P152" + ":ref:`P180_page`","P180" ":ref:`C001_page`","C001" ":ref:`C002_page`","C002" ":ref:`C003_page`","C003" @@ -179,6 +188,8 @@ Build set: :yellow:`COLLECTION A` ":ref:`C014_page`","C014" ":ref:`C017_page`","C017" ":ref:`C018_page`","C018" + ":ref:`N001_page`","N001" + ":ref:`N002_page`","N002" Build set: :yellow:`COLLECTION B` --------------------------------------------- @@ -196,20 +207,26 @@ Build set: :yellow:`COLLECTION B` ":ref:`P004_page`","P004" ":ref:`P005_page`","P005" ":ref:`P006_page`","P006" + ":ref:`P007_page`","P007" + ":ref:`P008_page`","P008" + ":ref:`P009_page`","P009" ":ref:`P010_page`","P010" ":ref:`P011_page`","P011" ":ref:`P012_page`","P012" ":ref:`P013_page`","P013" ":ref:`P014_page`","P014" ":ref:`P015_page`","P015" + ":ref:`P017_page`","P017" ":ref:`P018_page`","P018" ":ref:`P019_page`","P019" ":ref:`P020_page`","P020" ":ref:`P021_page`","P021" + ":ref:`P022_page`","P022" ":ref:`P023_page`","P023" ":ref:`P024_page`","P024" ":ref:`P025_page`","P025" ":ref:`P026_page`","P026" + ":ref:`P027_page`","P027" ":ref:`P028_page`","P028" ":ref:`P029_page`","P029" ":ref:`P031_page`","P031" @@ -220,6 +237,7 @@ Build set: :yellow:`COLLECTION B` ":ref:`P037_page`","P037" ":ref:`P038_page`","P038" ":ref:`P039_page`","P039" + ":ref:`P040_page`","P040" ":ref:`P043_page`","P043" ":ref:`P044_page`","P044" ":ref:`P045_page`","P045" @@ -262,8 +280,10 @@ Build set: :yellow:`COLLECTION B` ":ref:`P115_page`","P115" ":ref:`P137_page`","P137" ":ref:`P138_page`","P138" + ":ref:`P139_page`","P139" ":ref:`P146_page`","P146" ":ref:`P152_page`","P152" + ":ref:`P180_page`","P180" ":ref:`C001_page`","C001" ":ref:`C002_page`","C002" ":ref:`C003_page`","C003" @@ -280,6 +300,8 @@ Build set: :yellow:`COLLECTION B` ":ref:`C014_page`","C014" ":ref:`C017_page`","C017" ":ref:`C018_page`","C018" + ":ref:`N001_page`","N001" + ":ref:`N002_page`","N002" Build set: :yellow:`COLLECTION C` --------------------------------------------- @@ -297,20 +319,26 @@ Build set: :yellow:`COLLECTION C` ":ref:`P004_page`","P004" ":ref:`P005_page`","P005" ":ref:`P006_page`","P006" + ":ref:`P007_page`","P007" + ":ref:`P008_page`","P008" + ":ref:`P009_page`","P009" ":ref:`P010_page`","P010" ":ref:`P011_page`","P011" ":ref:`P012_page`","P012" ":ref:`P013_page`","P013" ":ref:`P014_page`","P014" ":ref:`P015_page`","P015" + ":ref:`P017_page`","P017" ":ref:`P018_page`","P018" ":ref:`P019_page`","P019" ":ref:`P020_page`","P020" ":ref:`P021_page`","P021" + ":ref:`P022_page`","P022" ":ref:`P023_page`","P023" ":ref:`P024_page`","P024" ":ref:`P025_page`","P025" ":ref:`P026_page`","P026" + ":ref:`P027_page`","P027" ":ref:`P028_page`","P028" ":ref:`P029_page`","P029" ":ref:`P031_page`","P031" @@ -319,8 +347,8 @@ Build set: :yellow:`COLLECTION C` ":ref:`P034_page`","P034" ":ref:`P036_page`","P036" ":ref:`P037_page`","P037" - ":ref:`P038_page`","P038" ":ref:`P039_page`","P039" + ":ref:`P040_page`","P040" ":ref:`P043_page`","P043" ":ref:`P044_page`","P044" ":ref:`P045_page`","P045" @@ -359,9 +387,11 @@ Build set: :yellow:`COLLECTION C` ":ref:`P111_page`","P111" ":ref:`P137_page`","P137" ":ref:`P138_page`","P138" + ":ref:`P139_page`","P139" ":ref:`P143_page`","P143" ":ref:`P146_page`","P146" ":ref:`P152_page`","P152" + ":ref:`P180_page`","P180" ":ref:`C001_page`","C001" ":ref:`C002_page`","C002" ":ref:`C003_page`","C003" @@ -378,6 +408,8 @@ Build set: :yellow:`COLLECTION C` ":ref:`C014_page`","C014" ":ref:`C017_page`","C017" ":ref:`C018_page`","C018" + ":ref:`N001_page`","N001" + ":ref:`N002_page`","N002" Build set: :yellow:`COLLECTION D` --------------------------------------------- @@ -395,20 +427,26 @@ Build set: :yellow:`COLLECTION D` ":ref:`P004_page`","P004" ":ref:`P005_page`","P005" ":ref:`P006_page`","P006" + ":ref:`P007_page`","P007" + ":ref:`P008_page`","P008" + ":ref:`P009_page`","P009" ":ref:`P010_page`","P010" ":ref:`P011_page`","P011" ":ref:`P012_page`","P012" ":ref:`P013_page`","P013" ":ref:`P014_page`","P014" ":ref:`P015_page`","P015" + ":ref:`P017_page`","P017" ":ref:`P018_page`","P018" ":ref:`P019_page`","P019" ":ref:`P020_page`","P020" ":ref:`P021_page`","P021" + ":ref:`P022_page`","P022" ":ref:`P023_page`","P023" ":ref:`P024_page`","P024" ":ref:`P025_page`","P025" ":ref:`P026_page`","P026" + ":ref:`P027_page`","P027" ":ref:`P028_page`","P028" ":ref:`P029_page`","P029" ":ref:`P031_page`","P031" @@ -417,7 +455,6 @@ Build set: :yellow:`COLLECTION D` ":ref:`P034_page`","P034" ":ref:`P036_page`","P036" ":ref:`P037_page`","P037" - ":ref:`P038_page`","P038" ":ref:`P039_page`","P039" ":ref:`P040_page`","P040" ":ref:`P043_page`","P043" @@ -459,8 +496,10 @@ Build set: :yellow:`COLLECTION D` ":ref:`P127_page`","P127" ":ref:`P137_page`","P137" ":ref:`P138_page`","P138" + ":ref:`P139_page`","P139" ":ref:`P146_page`","P146" ":ref:`P152_page`","P152" + ":ref:`P180_page`","P180" ":ref:`C001_page`","C001" ":ref:`C002_page`","C002" ":ref:`C003_page`","C003" @@ -477,6 +516,8 @@ Build set: :yellow:`COLLECTION D` ":ref:`C014_page`","C014" ":ref:`C017_page`","C017" ":ref:`C018_page`","C018" + ":ref:`N001_page`","N001" + ":ref:`N002_page`","N002" Build set: :yellow:`COLLECTION E` --------------------------------------------- @@ -494,20 +535,26 @@ Build set: :yellow:`COLLECTION E` ":ref:`P004_page`","P004" ":ref:`P005_page`","P005" ":ref:`P006_page`","P006" + ":ref:`P007_page`","P007" + ":ref:`P008_page`","P008" + ":ref:`P009_page`","P009" ":ref:`P010_page`","P010" ":ref:`P011_page`","P011" ":ref:`P012_page`","P012" ":ref:`P013_page`","P013" ":ref:`P014_page`","P014" ":ref:`P015_page`","P015" + ":ref:`P017_page`","P017" ":ref:`P018_page`","P018" ":ref:`P019_page`","P019" ":ref:`P020_page`","P020" ":ref:`P021_page`","P021" + ":ref:`P022_page`","P022" ":ref:`P023_page`","P023" ":ref:`P024_page`","P024" ":ref:`P025_page`","P025" ":ref:`P026_page`","P026" + ":ref:`P027_page`","P027" ":ref:`P028_page`","P028" ":ref:`P029_page`","P029" ":ref:`P031_page`","P031" @@ -560,9 +607,11 @@ Build set: :yellow:`COLLECTION E` ":ref:`P135_page`","P135" ":ref:`P137_page`","P137" ":ref:`P138_page`","P138" + ":ref:`P139_page`","P139" ":ref:`P144_page`","P144" ":ref:`P146_page`","P146" ":ref:`P152_page`","P152" + ":ref:`P180_page`","P180" ":ref:`C001_page`","C001" ":ref:`C002_page`","C002" ":ref:`C003_page`","C003" @@ -579,6 +628,8 @@ Build set: :yellow:`COLLECTION E` ":ref:`C014_page`","C014" ":ref:`C017_page`","C017" ":ref:`C018_page`","C018" + ":ref:`N001_page`","N001" + ":ref:`N002_page`","N002" Build set: :yellow:`COLLECTION F` --------------------------------------------- @@ -596,20 +647,26 @@ Build set: :yellow:`COLLECTION F` ":ref:`P004_page`","P004" ":ref:`P005_page`","P005" ":ref:`P006_page`","P006" + ":ref:`P007_page`","P007" + ":ref:`P008_page`","P008" + ":ref:`P009_page`","P009" ":ref:`P010_page`","P010" ":ref:`P011_page`","P011" ":ref:`P012_page`","P012" ":ref:`P013_page`","P013" ":ref:`P014_page`","P014" ":ref:`P015_page`","P015" + ":ref:`P017_page`","P017" ":ref:`P018_page`","P018" ":ref:`P019_page`","P019" ":ref:`P020_page`","P020" ":ref:`P021_page`","P021" + ":ref:`P022_page`","P022" ":ref:`P023_page`","P023" ":ref:`P024_page`","P024" ":ref:`P025_page`","P025" ":ref:`P026_page`","P026" + ":ref:`P027_page`","P027" ":ref:`P028_page`","P028" ":ref:`P029_page`","P029" ":ref:`P031_page`","P031" @@ -618,8 +675,8 @@ Build set: :yellow:`COLLECTION F` ":ref:`P034_page`","P034" ":ref:`P036_page`","P036" ":ref:`P037_page`","P037" - ":ref:`P038_page`","P038" ":ref:`P039_page`","P039" + ":ref:`P040_page`","P040" ":ref:`P043_page`","P043" ":ref:`P044_page`","P044" ":ref:`P045_page`","P045" @@ -656,6 +713,7 @@ Build set: :yellow:`COLLECTION F` ":ref:`P122_page`","P122" ":ref:`P137_page`","P137" ":ref:`P138_page`","P138" + ":ref:`P139_page`","P139" ":ref:`P145_page`","P145" ":ref:`P146_page`","P146" ":ref:`P147_page`","P147" @@ -663,6 +721,7 @@ Build set: :yellow:`COLLECTION F` ":ref:`P151_page`","P151" ":ref:`P152_page`","P152" ":ref:`P153_page`","P153" + ":ref:`P180_page`","P180" ":ref:`C001_page`","C001" ":ref:`C002_page`","C002" ":ref:`C003_page`","C003" @@ -679,6 +738,8 @@ Build set: :yellow:`COLLECTION F` ":ref:`C014_page`","C014" ":ref:`C017_page`","C017" ":ref:`C018_page`","C018" + ":ref:`N001_page`","N001" + ":ref:`N002_page`","N002" Build set: :yellow:`COLLECTION G` --------------------------------------------- @@ -696,20 +757,26 @@ Build set: :yellow:`COLLECTION G` ":ref:`P004_page`","P004" ":ref:`P005_page`","P005" ":ref:`P006_page`","P006" + ":ref:`P007_page`","P007" + ":ref:`P008_page`","P008" + ":ref:`P009_page`","P009" ":ref:`P010_page`","P010" ":ref:`P011_page`","P011" ":ref:`P012_page`","P012" ":ref:`P013_page`","P013" ":ref:`P014_page`","P014" ":ref:`P015_page`","P015" + ":ref:`P017_page`","P017" ":ref:`P018_page`","P018" ":ref:`P019_page`","P019" ":ref:`P020_page`","P020" ":ref:`P021_page`","P021" + ":ref:`P022_page`","P022" ":ref:`P023_page`","P023" ":ref:`P024_page`","P024" ":ref:`P025_page`","P025" ":ref:`P026_page`","P026" + ":ref:`P027_page`","P027" ":ref:`P028_page`","P028" ":ref:`P029_page`","P029" ":ref:`P031_page`","P031" @@ -718,8 +785,124 @@ Build set: :yellow:`COLLECTION G` ":ref:`P034_page`","P034" ":ref:`P036_page`","P036" ":ref:`P037_page`","P037" - ":ref:`P038_page`","P038" ":ref:`P039_page`","P039" + ":ref:`P040_page`","P040" + ":ref:`P043_page`","P043" + ":ref:`P044_page`","P044" + ":ref:`P045_page`","P045" + ":ref:`P046_page`","P046" + ":ref:`P047_page`","P047" + ":ref:`P048_page`","P048" + ":ref:`P049_page`","P049" + ":ref:`P050_page`","P050" + ":ref:`P051_page`","P051" + ":ref:`P052_page`","P052" + ":ref:`P053_page`","P053" + ":ref:`P054_page`","P054" + ":ref:`P055_page`","P055" + ":ref:`P056_page`","P056" + ":ref:`P057_page`","P057" + ":ref:`P058_page`","P058" + ":ref:`P059_page`","P059" + ":ref:`P060_page`","P060" + ":ref:`P061_page`","P061" + ":ref:`P062_page`","P062" + ":ref:`P063_page`","P063" + ":ref:`P064_page`","P064" + ":ref:`P065_page`","P065" + ":ref:`P066_page`","P066" + ":ref:`P073_page`","P073" + ":ref:`P075_page`","P075" + ":ref:`P079_page`","P079" + ":ref:`P081_page`","P081" + ":ref:`P082_page`","P082" + ":ref:`P089_page`","P089" + ":ref:`P095_page`","P095" + ":ref:`P105_page`","P105" + ":ref:`P137_page`","P137" + ":ref:`P138_page`","P138" + ":ref:`P139_page`","P139" + ":ref:`P142_page`","P142" + ":ref:`P146_page`","P146" + ":ref:`P152_page`","P152" + ":ref:`P154_page`","P154" + ":ref:`P159_page`","P159" + ":ref:`P162_page`","P162" + ":ref:`P163_page`","P163" + ":ref:`P164_page`","P164" + ":ref:`P166_page`","P166" + ":ref:`P168_page`","P168" + ":ref:`P170_page`","P170" + ":ref:`P172_page`","P172" + ":ref:`P173_page`","P173" + ":ref:`P177_page`","P177" + ":ref:`P178_page`","P178" + ":ref:`P180_page`","P180" + ":ref:`C001_page`","C001" + ":ref:`C002_page`","C002" + ":ref:`C003_page`","C003" + ":ref:`C004_page`","C004" + ":ref:`C005_page`","C005" + ":ref:`C006_page`","C006" + ":ref:`C007_page`","C007" + ":ref:`C008_page`","C008" + ":ref:`C009_page`","C009" + ":ref:`C010_page`","C010" + ":ref:`C011_page`","C011" + ":ref:`C012_page`","C012" + ":ref:`C013_page`","C013" + ":ref:`C014_page`","C014" + ":ref:`C017_page`","C017" + ":ref:`C018_page`","C018" + ":ref:`N001_page`","N001" + ":ref:`N002_page`","N002" + +Build set: :yellow:`COLLECTION H` +--------------------------------------------- + +.. collapse:: Details... + + .. csv-table:: + :header: "Plugin name", "Plugin number" + :widths: 10, 5 + + ":ref:`P000_page`","P000" + ":ref:`P001_page`","P001" + ":ref:`P002_page`","P002" + ":ref:`P003_page`","P003" + ":ref:`P004_page`","P004" + ":ref:`P005_page`","P005" + ":ref:`P006_page`","P006" + ":ref:`P007_page`","P007" + ":ref:`P008_page`","P008" + ":ref:`P009_page`","P009" + ":ref:`P010_page`","P010" + ":ref:`P011_page`","P011" + ":ref:`P012_page`","P012" + ":ref:`P013_page`","P013" + ":ref:`P014_page`","P014" + ":ref:`P015_page`","P015" + ":ref:`P017_page`","P017" + ":ref:`P018_page`","P018" + ":ref:`P019_page`","P019" + ":ref:`P020_page`","P020" + ":ref:`P021_page`","P021" + ":ref:`P022_page`","P022" + ":ref:`P023_page`","P023" + ":ref:`P024_page`","P024" + ":ref:`P025_page`","P025" + ":ref:`P026_page`","P026" + ":ref:`P027_page`","P027" + ":ref:`P028_page`","P028" + ":ref:`P029_page`","P029" + ":ref:`P031_page`","P031" + ":ref:`P032_page`","P032" + ":ref:`P033_page`","P033" + ":ref:`P034_page`","P034" + ":ref:`P036_page`","P036" + ":ref:`P037_page`","P037" + ":ref:`P039_page`","P039" + ":ref:`P040_page`","P040" ":ref:`P043_page`","P043" ":ref:`P044_page`","P044" ":ref:`P045_page`","P045" @@ -753,20 +936,10 @@ Build set: :yellow:`COLLECTION G` ":ref:`P095_page`","P095" ":ref:`P137_page`","P137" ":ref:`P138_page`","P138" - ":ref:`P142_page`","P142" + ":ref:`P139_page`","P139" ":ref:`P146_page`","P146" ":ref:`P152_page`","P152" - ":ref:`P154_page`","P154" - ":ref:`P159_page`","P159" - ":ref:`P162_page`","P162" - ":ref:`P163_page`","P163" - ":ref:`P164_page`","P164" - ":ref:`P166_page`","P166" - ":ref:`P168_page`","P168" - ":ref:`P170_page`","P170" - ":ref:`P172_page`","P172" - ":ref:`P173_page`","P173" - ":ref:`P178_page`","P178" + ":ref:`P180_page`","P180" ":ref:`C001_page`","C001" ":ref:`C002_page`","C002" ":ref:`C003_page`","C003" @@ -783,8 +956,10 @@ Build set: :yellow:`COLLECTION G` ":ref:`C014_page`","C014" ":ref:`C017_page`","C017" ":ref:`C018_page`","C018" + ":ref:`N001_page`","N001" + ":ref:`N002_page`","N002" -Build set: :yellow:`CLIMATE` +Build set: :yellow:`CLIMATE A` --------------------------------------------- .. collapse:: Details... @@ -801,14 +976,12 @@ Build set: :yellow:`CLIMATE` ":ref:`P005_page`","P005" ":ref:`P006_page`","P006" ":ref:`P010_page`","P010" - ":ref:`P011_page`","P011" ":ref:`P012_page`","P012" ":ref:`P013_page`","P013" ":ref:`P014_page`","P014" ":ref:`P015_page`","P015" ":ref:`P018_page`","P018" ":ref:`P019_page`","P019" - ":ref:`P020_page`","P020" ":ref:`P021_page`","P021" ":ref:`P023_page`","P023" ":ref:`P024_page`","P024" @@ -833,14 +1006,89 @@ Build set: :yellow:`CLIMATE` ":ref:`P053_page`","P053" ":ref:`P056_page`","P056" ":ref:`P059_page`","P059" - ":ref:`P063_page`","P063" ":ref:`P068_page`","P068" ":ref:`P069_page`","P069" ":ref:`P072_page`","P072" ":ref:`P073_page`","P073" - ":ref:`P079_page`","P079" ":ref:`P081_page`","P081" ":ref:`P083_page`","P083" + ":ref:`P089_page`","P089" + ":ref:`P090_page`","P090" + ":ref:`P103_page`","P103" + ":ref:`P105_page`","P105" + ":ref:`P106_page`","P106" + ":ref:`P117_page`","P117" + ":ref:`P118_page`","P118" + ":ref:`P127_page`","P127" + ":ref:`P133_page`","P133" + ":ref:`P135_page`","P135" + ":ref:`P142_page`","P142" + ":ref:`P144_page`","P144" + ":ref:`P147_page`","P147" + ":ref:`P150_page`","P150" + ":ref:`P151_page`","P151" + ":ref:`P153_page`","P153" + ":ref:`P154_page`","P154" + ":ref:`P164_page`","P164" + ":ref:`P180_page`","P180" + ":ref:`C011_page`","C011" + ":ref:`N001_page`","N001" + ":ref:`N002_page`","N002" + +Build set: :yellow:`CLIMATE B` +--------------------------------------------- + +.. collapse:: Details... + + .. csv-table:: + :header: "Plugin name", "Plugin number" + :widths: 10, 5 + + ":ref:`P000_page`","P000" + ":ref:`P001_page`","P001" + ":ref:`P002_page`","P002" + ":ref:`P003_page`","P003" + ":ref:`P004_page`","P004" + ":ref:`P005_page`","P005" + ":ref:`P006_page`","P006" + ":ref:`P010_page`","P010" + ":ref:`P012_page`","P012" + ":ref:`P013_page`","P013" + ":ref:`P014_page`","P014" + ":ref:`P015_page`","P015" + ":ref:`P018_page`","P018" + ":ref:`P019_page`","P019" + ":ref:`P021_page`","P021" + ":ref:`P023_page`","P023" + ":ref:`P024_page`","P024" + ":ref:`P025_page`","P025" + ":ref:`P026_page`","P026" + ":ref:`P028_page`","P028" + ":ref:`P029_page`","P029" + ":ref:`P031_page`","P031" + ":ref:`P032_page`","P032" + ":ref:`P033_page`","P033" + ":ref:`P034_page`","P034" + ":ref:`P036_page`","P036" + ":ref:`P037_page`","P037" + ":ref:`P038_page`","P038" + ":ref:`P039_page`","P039" + ":ref:`P043_page`","P043" + ":ref:`P044_page`","P044" + ":ref:`P047_page`","P047" + ":ref:`P049_page`","P049" + ":ref:`P051_page`","P051" + ":ref:`P052_page`","P052" + ":ref:`P053_page`","P053" + ":ref:`P056_page`","P056" + ":ref:`P059_page`","P059" + ":ref:`P068_page`","P068" + ":ref:`P069_page`","P069" + ":ref:`P072_page`","P072" + ":ref:`P073_page`","P073" + ":ref:`P081_page`","P081" + ":ref:`P083_page`","P083" + ":ref:`P089_page`","P089" ":ref:`P090_page`","P090" ":ref:`P103_page`","P103" ":ref:`P105_page`","P105" @@ -852,11 +1100,9 @@ Build set: :yellow:`CLIMATE` ":ref:`P135_page`","P135" ":ref:`P142_page`","P142" ":ref:`P144_page`","P144" - ":ref:`P146_page`","P146" ":ref:`P147_page`","P147" ":ref:`P150_page`","P150" ":ref:`P151_page`","P151" - ":ref:`P152_page`","P152" ":ref:`P153_page`","P153" ":ref:`P154_page`","P154" ":ref:`P164_page`","P164" @@ -866,7 +1112,80 @@ Build set: :yellow:`CLIMATE` ":ref:`P172_page`","P172" ":ref:`P173_page`","P173" ":ref:`P175_page`","P175" + ":ref:`P177_page`","P177" ":ref:`P178_page`","P178" + ":ref:`P180_page`","P180" + ":ref:`C011_page`","C011" + ":ref:`N001_page`","N001" + ":ref:`N002_page`","N002" + +Build set: :yellow:`DISPLAY A` +--------------------------------------------- + +.. collapse:: Details... + + .. csv-table:: + :header: "Plugin name", "Plugin number" + :widths: 10, 5 + + ":ref:`P000_page`","P000" + ":ref:`P001_page`","P001" + ":ref:`P002_page`","P002" + ":ref:`P003_page`","P003" + ":ref:`P004_page`","P004" + ":ref:`P005_page`","P005" + ":ref:`P006_page`","P006" + ":ref:`P007_page`","P007" + ":ref:`P008_page`","P008" + ":ref:`P009_page`","P009" + ":ref:`P010_page`","P010" + ":ref:`P011_page`","P011" + ":ref:`P012_page`","P012" + ":ref:`P013_page`","P013" + ":ref:`P014_page`","P014" + ":ref:`P015_page`","P015" + ":ref:`P017_page`","P017" + ":ref:`P018_page`","P018" + ":ref:`P019_page`","P019" + ":ref:`P020_page`","P020" + ":ref:`P021_page`","P021" + ":ref:`P022_page`","P022" + ":ref:`P023_page`","P023" + ":ref:`P024_page`","P024" + ":ref:`P025_page`","P025" + ":ref:`P026_page`","P026" + ":ref:`P027_page`","P027" + ":ref:`P028_page`","P028" + ":ref:`P029_page`","P029" + ":ref:`P031_page`","P031" + ":ref:`P032_page`","P032" + ":ref:`P033_page`","P033" + ":ref:`P034_page`","P034" + ":ref:`P036_page`","P036" + ":ref:`P037_page`","P037" + ":ref:`P039_page`","P039" + ":ref:`P040_page`","P040" + ":ref:`P043_page`","P043" + ":ref:`P044_page`","P044" + ":ref:`P049_page`","P049" + ":ref:`P052_page`","P052" + ":ref:`P053_page`","P053" + ":ref:`P056_page`","P056" + ":ref:`P057_page`","P057" + ":ref:`P059_page`","P059" + ":ref:`P063_page`","P063" + ":ref:`P073_page`","P073" + ":ref:`P075_page`","P075" + ":ref:`P079_page`","P079" + ":ref:`P104_page`","P104" + ":ref:`P109_page`","P109" + ":ref:`P137_page`","P137" + ":ref:`P138_page`","P138" + ":ref:`P139_page`","P139" + ":ref:`P143_page`","P143" + ":ref:`P146_page`","P146" + ":ref:`P152_page`","P152" + ":ref:`P180_page`","P180" ":ref:`C001_page`","C001" ":ref:`C002_page`","C002" ":ref:`C003_page`","C003" @@ -877,10 +1196,257 @@ Build set: :yellow:`CLIMATE` ":ref:`C008_page`","C008" ":ref:`C009_page`","C009" ":ref:`C010_page`","C010" - ":ref:`C011_page`","C011" ":ref:`C013_page`","C013" + ":ref:`N001_page`","N001" + ":ref:`N002_page`","N002" -Build set: :yellow:`DISPLAY` +Build set: :yellow:`DISPLAY B` +--------------------------------------------- + +.. collapse:: Details... + + .. csv-table:: + :header: "Plugin name", "Plugin number" + :widths: 10, 5 + + ":ref:`P000_page`","P000" + ":ref:`P001_page`","P001" + ":ref:`P002_page`","P002" + ":ref:`P003_page`","P003" + ":ref:`P004_page`","P004" + ":ref:`P005_page`","P005" + ":ref:`P006_page`","P006" + ":ref:`P007_page`","P007" + ":ref:`P008_page`","P008" + ":ref:`P009_page`","P009" + ":ref:`P010_page`","P010" + ":ref:`P011_page`","P011" + ":ref:`P012_page`","P012" + ":ref:`P013_page`","P013" + ":ref:`P014_page`","P014" + ":ref:`P015_page`","P015" + ":ref:`P017_page`","P017" + ":ref:`P018_page`","P018" + ":ref:`P019_page`","P019" + ":ref:`P020_page`","P020" + ":ref:`P021_page`","P021" + ":ref:`P022_page`","P022" + ":ref:`P023_page`","P023" + ":ref:`P024_page`","P024" + ":ref:`P025_page`","P025" + ":ref:`P026_page`","P026" + ":ref:`P027_page`","P027" + ":ref:`P028_page`","P028" + ":ref:`P029_page`","P029" + ":ref:`P031_page`","P031" + ":ref:`P032_page`","P032" + ":ref:`P033_page`","P033" + ":ref:`P034_page`","P034" + ":ref:`P036_page`","P036" + ":ref:`P037_page`","P037" + ":ref:`P039_page`","P039" + ":ref:`P040_page`","P040" + ":ref:`P043_page`","P043" + ":ref:`P044_page`","P044" + ":ref:`P049_page`","P049" + ":ref:`P052_page`","P052" + ":ref:`P053_page`","P053" + ":ref:`P056_page`","P056" + ":ref:`P059_page`","P059" + ":ref:`P063_page`","P063" + ":ref:`P073_page`","P073" + ":ref:`P079_page`","P079" + ":ref:`P095_page`","P095" + ":ref:`P099_page`","P099" + ":ref:`P109_page`","P109" + ":ref:`P116_page`","P116" + ":ref:`P123_page`","P123" + ":ref:`P137_page`","P137" + ":ref:`P138_page`","P138" + ":ref:`P139_page`","P139" + ":ref:`P141_page`","P141" + ":ref:`P143_page`","P143" + ":ref:`P146_page`","P146" + ":ref:`P148_page`","P148" + ":ref:`P152_page`","P152" + ":ref:`P180_page`","P180" + ":ref:`C001_page`","C001" + ":ref:`C002_page`","C002" + ":ref:`C003_page`","C003" + ":ref:`C004_page`","C004" + ":ref:`C005_page`","C005" + ":ref:`C006_page`","C006" + ":ref:`C007_page`","C007" + ":ref:`C008_page`","C008" + ":ref:`C009_page`","C009" + ":ref:`C010_page`","C010" + ":ref:`C013_page`","C013" + ":ref:`N001_page`","N001" + ":ref:`N002_page`","N002" + +Build set: :yellow:`ENERGY` +--------------------------------------------- + +.. collapse:: Details... + + .. csv-table:: + :header: "Plugin name", "Plugin number" + :widths: 10, 5 + + ":ref:`P000_page`","P000" + ":ref:`P001_page`","P001" + ":ref:`P002_page`","P002" + ":ref:`P003_page`","P003" + ":ref:`P004_page`","P004" + ":ref:`P005_page`","P005" + ":ref:`P006_page`","P006" + ":ref:`P007_page`","P007" + ":ref:`P008_page`","P008" + ":ref:`P009_page`","P009" + ":ref:`P010_page`","P010" + ":ref:`P011_page`","P011" + ":ref:`P012_page`","P012" + ":ref:`P013_page`","P013" + ":ref:`P014_page`","P014" + ":ref:`P015_page`","P015" + ":ref:`P017_page`","P017" + ":ref:`P018_page`","P018" + ":ref:`P019_page`","P019" + ":ref:`P020_page`","P020" + ":ref:`P021_page`","P021" + ":ref:`P022_page`","P022" + ":ref:`P023_page`","P023" + ":ref:`P024_page`","P024" + ":ref:`P025_page`","P025" + ":ref:`P026_page`","P026" + ":ref:`P027_page`","P027" + ":ref:`P028_page`","P028" + ":ref:`P029_page`","P029" + ":ref:`P031_page`","P031" + ":ref:`P032_page`","P032" + ":ref:`P033_page`","P033" + ":ref:`P034_page`","P034" + ":ref:`P036_page`","P036" + ":ref:`P037_page`","P037" + ":ref:`P039_page`","P039" + ":ref:`P040_page`","P040" + ":ref:`P043_page`","P043" + ":ref:`P044_page`","P044" + ":ref:`P049_page`","P049" + ":ref:`P052_page`","P052" + ":ref:`P053_page`","P053" + ":ref:`P056_page`","P056" + ":ref:`P059_page`","P059" + ":ref:`P063_page`","P063" + ":ref:`P073_page`","P073" + ":ref:`P076_page`","P076" + ":ref:`P077_page`","P077" + ":ref:`P078_page`","P078" + ":ref:`P079_page`","P079" + ":ref:`P085_page`","P085" + ":ref:`P089_page`","P089" + ":ref:`P093_page`","P093" + ":ref:`P102_page`","P102" + ":ref:`P108_page`","P108" + ":ref:`P115_page`","P115" + ":ref:`P132_page`","P132" + ":ref:`P137_page`","P137" + ":ref:`P138_page`","P138" + ":ref:`P139_page`","P139" + ":ref:`P146_page`","P146" + ":ref:`P148_page`","P148" + ":ref:`P152_page`","P152" + ":ref:`P176_page`","P176" + ":ref:`P180_page`","P180" + ":ref:`C001_page`","C001" + ":ref:`C002_page`","C002" + ":ref:`C003_page`","C003" + ":ref:`C004_page`","C004" + ":ref:`C005_page`","C005" + ":ref:`C006_page`","C006" + ":ref:`C007_page`","C007" + ":ref:`C008_page`","C008" + ":ref:`C009_page`","C009" + ":ref:`C010_page`","C010" + ":ref:`C013_page`","C013" + ":ref:`N001_page`","N001" + ":ref:`N002_page`","N002" + +Build set: :yellow:`IR` +--------------------------------------------- + +.. collapse:: Details... + + .. csv-table:: + :header: "Plugin name", "Plugin number" + :widths: 10, 5 + + ":ref:`P000_page`","P000" + ":ref:`P001_page`","P001" + ":ref:`P002_page`","P002" + ":ref:`P003_page`","P003" + ":ref:`P004_page`","P004" + ":ref:`P005_page`","P005" + ":ref:`P006_page`","P006" + ":ref:`P007_page`","P007" + ":ref:`P008_page`","P008" + ":ref:`P009_page`","P009" + ":ref:`P010_page`","P010" + ":ref:`P011_page`","P011" + ":ref:`P012_page`","P012" + ":ref:`P013_page`","P013" + ":ref:`P014_page`","P014" + ":ref:`P015_page`","P015" + ":ref:`P016_page`","P016" + ":ref:`P017_page`","P017" + ":ref:`P018_page`","P018" + ":ref:`P019_page`","P019" + ":ref:`P020_page`","P020" + ":ref:`P021_page`","P021" + ":ref:`P022_page`","P022" + ":ref:`P023_page`","P023" + ":ref:`P024_page`","P024" + ":ref:`P025_page`","P025" + ":ref:`P026_page`","P026" + ":ref:`P027_page`","P027" + ":ref:`P028_page`","P028" + ":ref:`P029_page`","P029" + ":ref:`P031_page`","P031" + ":ref:`P032_page`","P032" + ":ref:`P033_page`","P033" + ":ref:`P034_page`","P034" + ":ref:`P035_page`","P035" + ":ref:`P036_page`","P036" + ":ref:`P037_page`","P037" + ":ref:`P039_page`","P039" + ":ref:`P040_page`","P040" + ":ref:`P043_page`","P043" + ":ref:`P044_page`","P044" + ":ref:`P049_page`","P049" + ":ref:`P052_page`","P052" + ":ref:`P053_page`","P053" + ":ref:`P056_page`","P056" + ":ref:`P059_page`","P059" + ":ref:`P063_page`","P063" + ":ref:`P073_page`","P073" + ":ref:`P079_page`","P079" + ":ref:`P146_page`","P146" + ":ref:`P152_page`","P152" + ":ref:`C001_page`","C001" + ":ref:`C002_page`","C002" + ":ref:`C003_page`","C003" + ":ref:`C004_page`","C004" + ":ref:`C005_page`","C005" + ":ref:`C006_page`","C006" + ":ref:`C007_page`","C007" + ":ref:`C008_page`","C008" + ":ref:`C009_page`","C009" + ":ref:`C010_page`","C010" + ":ref:`C013_page`","C013" + ":ref:`N001_page`","N001" + ":ref:`N002_page`","N002" + +Build set: :yellow:`IRext` --------------------------------------------- .. collapse:: Details... @@ -933,229 +1499,6 @@ Build set: :yellow:`DISPLAY` ":ref:`P052_page`","P052" ":ref:`P053_page`","P053" ":ref:`P056_page`","P056" - ":ref:`P057_page`","P057" - ":ref:`P059_page`","P059" - ":ref:`P063_page`","P063" - ":ref:`P073_page`","P073" - ":ref:`P075_page`","P075" - ":ref:`P079_page`","P079" - ":ref:`P095_page`","P095" - ":ref:`P099_page`","P099" - ":ref:`P104_page`","P104" - ":ref:`P109_page`","P109" - ":ref:`P116_page`","P116" - ":ref:`P123_page`","P123" - ":ref:`P137_page`","P137" - ":ref:`P138_page`","P138" - ":ref:`P141_page`","P141" - ":ref:`P143_page`","P143" - ":ref:`P146_page`","P146" - ":ref:`P148_page`","P148" - ":ref:`P152_page`","P152" - ":ref:`P165_page`","P165" - ":ref:`C001_page`","C001" - ":ref:`C002_page`","C002" - ":ref:`C003_page`","C003" - ":ref:`C004_page`","C004" - ":ref:`C005_page`","C005" - ":ref:`C006_page`","C006" - ":ref:`C007_page`","C007" - ":ref:`C008_page`","C008" - ":ref:`C009_page`","C009" - ":ref:`C010_page`","C010" - ":ref:`C013_page`","C013" - -Build set: :yellow:`ENERGY` ---------------------------------------------- - -.. collapse:: Details... - - .. csv-table:: - :header: "Plugin name", "Plugin number" - :widths: 10, 5 - - ":ref:`P000_page`","P000" - ":ref:`P001_page`","P001" - ":ref:`P002_page`","P002" - ":ref:`P003_page`","P003" - ":ref:`P004_page`","P004" - ":ref:`P005_page`","P005" - ":ref:`P006_page`","P006" - ":ref:`P010_page`","P010" - ":ref:`P011_page`","P011" - ":ref:`P012_page`","P012" - ":ref:`P013_page`","P013" - ":ref:`P014_page`","P014" - ":ref:`P015_page`","P015" - ":ref:`P018_page`","P018" - ":ref:`P019_page`","P019" - ":ref:`P020_page`","P020" - ":ref:`P021_page`","P021" - ":ref:`P023_page`","P023" - ":ref:`P024_page`","P024" - ":ref:`P025_page`","P025" - ":ref:`P026_page`","P026" - ":ref:`P027_page`","P027" - ":ref:`P028_page`","P028" - ":ref:`P029_page`","P029" - ":ref:`P031_page`","P031" - ":ref:`P032_page`","P032" - ":ref:`P033_page`","P033" - ":ref:`P034_page`","P034" - ":ref:`P036_page`","P036" - ":ref:`P037_page`","P037" - ":ref:`P038_page`","P038" - ":ref:`P039_page`","P039" - ":ref:`P043_page`","P043" - ":ref:`P044_page`","P044" - ":ref:`P049_page`","P049" - ":ref:`P052_page`","P052" - ":ref:`P053_page`","P053" - ":ref:`P056_page`","P056" - ":ref:`P059_page`","P059" - ":ref:`P063_page`","P063" - ":ref:`P073_page`","P073" - ":ref:`P076_page`","P076" - ":ref:`P077_page`","P077" - ":ref:`P078_page`","P078" - ":ref:`P079_page`","P079" - ":ref:`P085_page`","P085" - ":ref:`P093_page`","P093" - ":ref:`P102_page`","P102" - ":ref:`P108_page`","P108" - ":ref:`P115_page`","P115" - ":ref:`P132_page`","P132" - ":ref:`P137_page`","P137" - ":ref:`P138_page`","P138" - ":ref:`P146_page`","P146" - ":ref:`P148_page`","P148" - ":ref:`P152_page`","P152" - ":ref:`P176_page`","P176" - ":ref:`C001_page`","C001" - ":ref:`C002_page`","C002" - ":ref:`C003_page`","C003" - ":ref:`C004_page`","C004" - ":ref:`C005_page`","C005" - ":ref:`C006_page`","C006" - ":ref:`C007_page`","C007" - ":ref:`C008_page`","C008" - ":ref:`C009_page`","C009" - ":ref:`C010_page`","C010" - ":ref:`C013_page`","C013" - -Build set: :yellow:`IR` ---------------------------------------------- - -.. collapse:: Details... - - .. csv-table:: - :header: "Plugin name", "Plugin number" - :widths: 10, 5 - - ":ref:`P000_page`","P000" - ":ref:`P001_page`","P001" - ":ref:`P002_page`","P002" - ":ref:`P003_page`","P003" - ":ref:`P004_page`","P004" - ":ref:`P005_page`","P005" - ":ref:`P006_page`","P006" - ":ref:`P010_page`","P010" - ":ref:`P011_page`","P011" - ":ref:`P012_page`","P012" - ":ref:`P013_page`","P013" - ":ref:`P014_page`","P014" - ":ref:`P015_page`","P015" - ":ref:`P016_page`","P016" - ":ref:`P018_page`","P018" - ":ref:`P019_page`","P019" - ":ref:`P020_page`","P020" - ":ref:`P021_page`","P021" - ":ref:`P023_page`","P023" - ":ref:`P024_page`","P024" - ":ref:`P025_page`","P025" - ":ref:`P026_page`","P026" - ":ref:`P028_page`","P028" - ":ref:`P029_page`","P029" - ":ref:`P031_page`","P031" - ":ref:`P032_page`","P032" - ":ref:`P033_page`","P033" - ":ref:`P034_page`","P034" - ":ref:`P035_page`","P035" - ":ref:`P036_page`","P036" - ":ref:`P037_page`","P037" - ":ref:`P038_page`","P038" - ":ref:`P039_page`","P039" - ":ref:`P040_page`","P040" - ":ref:`P043_page`","P043" - ":ref:`P044_page`","P044" - ":ref:`P049_page`","P049" - ":ref:`P052_page`","P052" - ":ref:`P053_page`","P053" - ":ref:`P056_page`","P056" - ":ref:`P059_page`","P059" - ":ref:`P063_page`","P063" - ":ref:`P073_page`","P073" - ":ref:`P079_page`","P079" - ":ref:`P146_page`","P146" - ":ref:`P152_page`","P152" - ":ref:`C001_page`","C001" - ":ref:`C002_page`","C002" - ":ref:`C003_page`","C003" - ":ref:`C004_page`","C004" - ":ref:`C005_page`","C005" - ":ref:`C006_page`","C006" - ":ref:`C007_page`","C007" - ":ref:`C008_page`","C008" - ":ref:`C009_page`","C009" - ":ref:`C010_page`","C010" - ":ref:`C013_page`","C013" - -Build set: :yellow:`IRext` ---------------------------------------------- - -.. collapse:: Details... - - .. csv-table:: - :header: "Plugin name", "Plugin number" - :widths: 10, 5 - - ":ref:`P000_page`","P000" - ":ref:`P001_page`","P001" - ":ref:`P002_page`","P002" - ":ref:`P003_page`","P003" - ":ref:`P004_page`","P004" - ":ref:`P005_page`","P005" - ":ref:`P006_page`","P006" - ":ref:`P010_page`","P010" - ":ref:`P011_page`","P011" - ":ref:`P012_page`","P012" - ":ref:`P013_page`","P013" - ":ref:`P014_page`","P014" - ":ref:`P015_page`","P015" - ":ref:`P018_page`","P018" - ":ref:`P019_page`","P019" - ":ref:`P020_page`","P020" - ":ref:`P021_page`","P021" - ":ref:`P023_page`","P023" - ":ref:`P024_page`","P024" - ":ref:`P025_page`","P025" - ":ref:`P026_page`","P026" - ":ref:`P028_page`","P028" - ":ref:`P029_page`","P029" - ":ref:`P031_page`","P031" - ":ref:`P032_page`","P032" - ":ref:`P033_page`","P033" - ":ref:`P034_page`","P034" - ":ref:`P036_page`","P036" - ":ref:`P037_page`","P037" - ":ref:`P038_page`","P038" - ":ref:`P039_page`","P039" - ":ref:`P043_page`","P043" - ":ref:`P044_page`","P044" - ":ref:`P049_page`","P049" - ":ref:`P052_page`","P052" - ":ref:`P053_page`","P053" - ":ref:`P056_page`","P056" ":ref:`P059_page`","P059" ":ref:`P063_page`","P063" ":ref:`P073_page`","P073" @@ -1174,6 +1517,8 @@ Build set: :yellow:`IRext` ":ref:`C009_page`","C009" ":ref:`C010_page`","C010" ":ref:`C013_page`","C013" + ":ref:`N001_page`","N001" + ":ref:`N002_page`","N002" Build set: :yellow:`NEOPIXEL` --------------------------------------------- @@ -1191,20 +1536,26 @@ Build set: :yellow:`NEOPIXEL` ":ref:`P004_page`","P004" ":ref:`P005_page`","P005" ":ref:`P006_page`","P006" + ":ref:`P007_page`","P007" + ":ref:`P008_page`","P008" + ":ref:`P009_page`","P009" ":ref:`P010_page`","P010" ":ref:`P011_page`","P011" ":ref:`P012_page`","P012" ":ref:`P013_page`","P013" ":ref:`P014_page`","P014" ":ref:`P015_page`","P015" + ":ref:`P017_page`","P017" ":ref:`P018_page`","P018" ":ref:`P019_page`","P019" ":ref:`P020_page`","P020" ":ref:`P021_page`","P021" + ":ref:`P022_page`","P022" ":ref:`P023_page`","P023" ":ref:`P024_page`","P024" ":ref:`P025_page`","P025" ":ref:`P026_page`","P026" + ":ref:`P027_page`","P027" ":ref:`P028_page`","P028" ":ref:`P029_page`","P029" ":ref:`P031_page`","P031" @@ -1215,6 +1566,7 @@ Build set: :yellow:`NEOPIXEL` ":ref:`P037_page`","P037" ":ref:`P038_page`","P038" ":ref:`P039_page`","P039" + ":ref:`P040_page`","P040" ":ref:`P041_page`","P041" ":ref:`P042_page`","P042" ":ref:`P043_page`","P043" @@ -1228,10 +1580,12 @@ Build set: :yellow:`NEOPIXEL` ":ref:`P070_page`","P070" ":ref:`P073_page`","P073" ":ref:`P079_page`","P079" + ":ref:`P089_page`","P089" ":ref:`P128_page`","P128" ":ref:`P131_page`","P131" ":ref:`P137_page`","P137" ":ref:`P138_page`","P138" + ":ref:`P139_page`","P139" ":ref:`P146_page`","P146" ":ref:`P152_page`","P152" ":ref:`P165_page`","P165" @@ -1246,6 +1600,8 @@ Build set: :yellow:`NEOPIXEL` ":ref:`C009_page`","C009" ":ref:`C010_page`","C010" ":ref:`C013_page`","C013" + ":ref:`N001_page`","N001" + ":ref:`N002_page`","N002" Build set: :yellow:`MAX` --------------------------------------------- @@ -1345,6 +1701,7 @@ Build set: :yellow:`MAX` ":ref:`P086_page`","P086" ":ref:`P087_page`","P087" ":ref:`P088_page`","P088" + ":ref:`P089_page`","P089" ":ref:`P090_page`","P090" ":ref:`P091_page`","P091" ":ref:`P092_page`","P092" @@ -1391,6 +1748,8 @@ Build set: :yellow:`MAX` ":ref:`P135_page`","P135" ":ref:`P137_page`","P137" ":ref:`P138_page`","P138" + ":ref:`P139_page`","P139" + ":ref:`P140_page`","P140" ":ref:`P141_page`","P141" ":ref:`P142_page`","P142" ":ref:`P143_page`","P143" @@ -1418,7 +1777,9 @@ Build set: :yellow:`MAX` ":ref:`P173_page`","P173" ":ref:`P175_page`","P175" ":ref:`P176_page`","P176" + ":ref:`P177_page`","P177" ":ref:`P178_page`","P178" + ":ref:`P180_page`","P180" ":ref:`C001_page`","C001" ":ref:`C002_page`","C002" ":ref:`C003_page`","C003" @@ -1436,4 +1797,6 @@ Build set: :yellow:`MAX` ":ref:`C016_page`","C016" ":ref:`C017_page`","C017" ":ref:`C018_page`","C018" + ":ref:`N001_page`","N001" + ":ref:`N002_page`","N002" diff --git a/docs/source/Plugin/_plugin_substitutions_p00x.repl b/docs/source/Plugin/_plugin_substitutions_p00x.repl index 45eaab1a1..e8c62df87 100644 --- a/docs/source/Plugin/_plugin_substitutions_p00x.repl +++ b/docs/source/Plugin/_plugin_substitutions_p00x.repl @@ -56,9 +56,9 @@ .. |P003_compileinfo| replace:: `.` .. |P003_usedlibraries| replace:: `.` -.. |P004_name| replace:: :cyan:`1-Wire Temperature` +.. |P004_name| replace:: :cyan:`DS18xxx/MAX31xxx/1-Wire Temperature` .. |P004_type| replace:: :cyan:`Environment` -.. |P004_typename| replace:: :cyan:`Environment - 1-Wire Temperature` +.. |P004_typename| replace:: :cyan:`Environment - DS18xxx/MAX31xxx/1-Wire Temperature` .. |P004_porttype| replace:: `1-Wire` .. |P004_status| replace:: :green:`NORMAL` :yellow:`CLIMATE` .. |P004_github| replace:: P004_Dallas.ino diff --git a/docs/source/Plugin/_plugin_substitutions_p01x.repl b/docs/source/Plugin/_plugin_substitutions_p01x.repl index f88b837fb..c9f68c4ac 100644 --- a/docs/source/Plugin/_plugin_substitutions_p01x.repl +++ b/docs/source/Plugin/_plugin_substitutions_p01x.repl @@ -28,7 +28,7 @@ .. |P012_type| replace:: :cyan:`Display` .. |P012_typename| replace:: :cyan:`Display - LCD2004` .. |P012_porttype| replace:: `.` -.. |P012_status| replace:: :green:`NORMAL` :yellow:`DISPLAY` :yellow:`CLIMATE` +.. |P012_status| replace:: :green:`NORMAL` :yellow:`CLIMATE` :yellow:`DISPLAY A` .. |P012_github| replace:: P012_LCD.ino .. _P012_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P012_LCD.ino .. |P012_usedby| replace:: `.` diff --git a/docs/source/Plugin/_plugin_substitutions_p02x.repl b/docs/source/Plugin/_plugin_substitutions_p02x.repl index 1a9d4fd51..eb1c1005d 100644 --- a/docs/source/Plugin/_plugin_substitutions_p02x.repl +++ b/docs/source/Plugin/_plugin_substitutions_p02x.repl @@ -41,7 +41,7 @@ .. |P023_type| replace:: :cyan:`Display` .. |P023_typename| replace:: :cyan:`Display - OLED SSD1306` .. |P023_porttype| replace:: `.` -.. |P023_status| replace:: :green:`NORMAL` :yellow:`DISPLAY` :yellow:`CLIMATE` +.. |P023_status| replace:: :green:`NORMAL` :yellow:`CLIMATE` :yellow:`DISPLAY A` .. |P023_github| replace:: P023_OLED.ino .. _P023_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P023_OLED.ino .. |P023_usedby| replace:: `.` diff --git a/docs/source/Plugin/_plugin_substitutions_p07x.repl b/docs/source/Plugin/_plugin_substitutions_p07x.repl index c6be0725b..280f933fe 100644 --- a/docs/source/Plugin/_plugin_substitutions_p07x.repl +++ b/docs/source/Plugin/_plugin_substitutions_p07x.repl @@ -68,7 +68,7 @@ .. |P075_type| replace:: :cyan:`Display` .. |P075_typename| replace:: :cyan:`Display - Nextion` .. |P075_porttype| replace:: `.` -.. |P075_status| replace:: :yellow:`DISPLAY` :yellow:`COLLECTION` +.. |P075_status| replace:: :yellow:`COLLECTION` :yellow:`DISPLAY A` .. |P075_github| replace:: P075_Nextion.ino .. _P075_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P075_Nextion.ino .. |P075_usedby| replace:: `.` diff --git a/docs/source/Plugin/_plugin_substitutions_p08x.repl b/docs/source/Plugin/_plugin_substitutions_p08x.repl index 691078412..506a7cc6b 100644 --- a/docs/source/Plugin/_plugin_substitutions_p08x.repl +++ b/docs/source/Plugin/_plugin_substitutions_p08x.repl @@ -93,7 +93,7 @@ .. |P087_type| replace:: :cyan:`Communication` .. |P087_typename| replace:: :cyan:`Communication - SerialProxy` .. |P087_porttype| replace:: `Serial` -.. |P087_status| replace:: :yellow:`COLLECTION C` +.. |P087_status| replace:: :yellow:`COLLECTION C` :yellow:`ENERGY` .. |P087_github| replace:: P087_SerialProxy.ino .. _P087_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P087_SerialProxy.ino .. |P087_usedby| replace:: `.` diff --git a/docs/source/Plugin/_plugin_substitutions_p09x.repl b/docs/source/Plugin/_plugin_substitutions_p09x.repl index 0154a7f40..6890fa878 100644 --- a/docs/source/Plugin/_plugin_substitutions_p09x.repl +++ b/docs/source/Plugin/_plugin_substitutions_p09x.repl @@ -38,7 +38,7 @@ .. |P093_name| replace:: :cyan:`Mitsubishi Heat Pump` .. |P093_type| replace:: :cyan:`Energy (Heat)` .. |P093_typename| replace:: :cyan:`Energy (Heat) - Mitsubishi Heat Pump` -.. |P093_status| replace:: :yellow:`ENERGY` :yellow:`COLLECTION D` +.. |P093_status| replace:: :yellow:`COLLECTION D` :yellow:`ENERGY` .. |P093_github| replace:: P093_MitsubishiHP.ino .. _P093_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P093_MitsubishiHP.ino .. |P093_usedby| replace:: `.` @@ -63,7 +63,7 @@ .. |P095_type| replace:: :cyan:`Display` .. |P095_typename| replace:: :cyan:`Display - TFT ILI934x/ILI948x` .. |P095_porttype| replace:: `.` -.. |P095_status| replace:: :yellow:`DISPLAY` :yellow:`COLLECTION` :yellow:`(ESP32)` +.. |P095_status| replace:: :yellow:`COLLECTION` :yellow:`\(ESP32\)` :yellow:`DISPLAY B` .. |P095_github| replace:: _P095_ILI9341.ino .. _P095_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P095_ILI9341.ino .. |P095_usedby| replace:: `.` @@ -102,7 +102,7 @@ .. |P099_type| replace:: :cyan:`Touch` .. |P099_typename| replace:: :cyan:`Touch - XPT2046 touchscreen on TFT display` .. |P099_porttype| replace:: `SPI` -.. |P099_status| replace:: :yellow:`DISPLAY` +.. |P099_status| replace:: :yellow:`DISPLAY B` .. |P099_github| replace:: _P099_XPT2046Touch.ino .. _P099_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P099_XPT2046Touch.ino .. |P099_usedby| replace:: `.` diff --git a/docs/source/Plugin/_plugin_substitutions_p10x.repl b/docs/source/Plugin/_plugin_substitutions_p10x.repl index 6099d1014..9a282e375 100644 --- a/docs/source/Plugin/_plugin_substitutions_p10x.repl +++ b/docs/source/Plugin/_plugin_substitutions_p10x.repl @@ -24,18 +24,18 @@ .. |P101_compileinfo| replace:: `.` .. |P101_usedlibraries| replace:: https://github.com/a7md0/WakeOnLan -.. |P102_name| replace:: :cyan:`PZEM-004Tv30-Multiple` -.. |P102_type| replace:: :cyan:`Energy (AC)` -.. |P102_typename| replace:: :cyan:`Energy (AC) - PZEM-004Tv30-Multiple` +.. |P102_name| replace:: :cyan:`PZEM-004Tv30 / PZEM-017v1` +.. |P102_type| replace:: :cyan:`Energy` +.. |P102_typename| replace:: :cyan:`Energy - PZEM-004T (AC) / PZEM-017 (DC)` .. |P102_porttype| replace:: `.` .. |P102_status| replace:: :yellow:`ENERGY` .. |P102_github| replace:: P102_PZEM004Tv3.ino .. _P102_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P102_PZEM004Tv3.ino .. |P102_usedby| replace:: `.` .. |P102_shortinfo| replace:: `.` -.. |P102_maintainer| replace:: TD-er +.. |P102_maintainer| replace:: TD-er tonhuisman .. |P102_compileinfo| replace:: `.` -.. |P102_usedlibraries| replace:: https://github.com/olehs/PZEM004T +.. |P102_usedlibraries| replace:: https://github.com/olehs/PZEM004T https://github.com/mandulaj/PZEM-004T-v30 .. |P103_name| replace:: :cyan:`Atlas Scientific EZO pH ORP EC DO HUM` .. |P103_type| replace:: :cyan:`Environment` @@ -55,7 +55,7 @@ .. |P104_type| replace:: :cyan:`Display` .. |P104_typename| replace:: :cyan:`Display - MAX7219 dot matrix` .. |P104_porttype| replace:: `SPI` -.. |P104_status| replace:: :yellow:`DISPLAY` +.. |P104_status| replace:: :yellow:`DISPLAY A` .. |P104_github| replace:: P104_max7219_Dotmatrix.ino .. _P104_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P104_max7210_Dotmatrix.ino .. |P104_usedby| replace:: `.` @@ -122,7 +122,7 @@ .. |P108_type| replace:: :cyan:`Energy (AC)` .. |P108_typename| replace:: :cyan:`Energy (AC) - DDS238-x` .. |P108_porttype| replace:: `.` -.. |P108_status| replace:: :yellow:`ENERGY` :yellow:`COLLECTION B` +.. |P108_status| replace:: :yellow:`COLLECTION B` :yellow:`ENERGY` .. |P108_github| replace:: P108_DDS238.ino .. _P108_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P108_DDS238.ino .. |P108_usedby| replace:: `.` diff --git a/docs/source/Plugin/_plugin_substitutions_p11x.repl b/docs/source/Plugin/_plugin_substitutions_p11x.repl index 12a1398a9..031b9b7ca 100644 --- a/docs/source/Plugin/_plugin_substitutions_p11x.repl +++ b/docs/source/Plugin/_plugin_substitutions_p11x.repl @@ -67,7 +67,7 @@ .. |P115_type| replace:: :cyan:`Energy` .. |P115_typename| replace:: :cyan:`Energy - MAX1704x` .. |P115_porttype| replace:: `.` -.. |P115_status| replace:: :yellow:`ENERGY` :yellow:`COLLECTION B` +.. |P115_status| replace:: :yellow:`COLLECTION B` :yellow:`ENERGY` .. |P115_github| replace:: _P115_MAX1704x_v2.ino .. _P115_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P115_MAX1704x_v2.ino .. |P115_usedby| replace:: `.` @@ -80,7 +80,7 @@ .. |P116_type| replace:: :cyan:`Display` .. |P116_typename| replace:: :cyan:`Display - ST77xx TFT` .. |P116_porttype| replace:: `.` -.. |P116_status| replace:: :yellow:`DISPLAY` +.. |P116_status| replace:: :yellow:`DISPLAY B` .. |P116_github| replace:: _P116_ST77xx.ino .. _P116_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P116_ST77xx.ino .. |P116_usedby| replace:: `.` diff --git a/docs/source/Plugin/_plugin_substitutions_p12x.repl b/docs/source/Plugin/_plugin_substitutions_p12x.repl index 7d03c8ef9..26cd48c12 100644 --- a/docs/source/Plugin/_plugin_substitutions_p12x.repl +++ b/docs/source/Plugin/_plugin_substitutions_p12x.repl @@ -41,7 +41,7 @@ .. |P123_type| replace:: :cyan:`Touch` .. |P123_typename| replace:: :cyan:`Touch - I2C Touchscreens` .. |P123_porttype| replace:: `.` -.. |P123_status| replace:: :yellow:`DISPLAY` +.. |P123_status| replace:: :yellow:`DISPLAY B` .. |P123_github| replace:: P123_I2CTouch.ino .. _P123_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P123_I2CTouch.ino .. |P123_usedby| replace:: `.` diff --git a/docs/source/Plugin/_plugin_substitutions_p14x.repl b/docs/source/Plugin/_plugin_substitutions_p14x.repl index 3996585e1..49ff184bc 100644 --- a/docs/source/Plugin/_plugin_substitutions_p14x.repl +++ b/docs/source/Plugin/_plugin_substitutions_p14x.repl @@ -15,7 +15,7 @@ .. |P141_type| replace:: :cyan:`Display` .. |P141_typename| replace:: :cyan:`Display - PCD8544 Nokia 5110 LCD` .. |P141_porttype| replace:: `.` -.. |P141_status| replace:: :yellow:`DISPLAY` +.. |P141_status| replace:: :yellow:`DISPLAY B` .. |P141_github| replace:: P141_PCD8544_Nokia5110.ino .. _P141_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P141_PCD8544_Nokia5110.ino .. |P141_usedby| replace:: `.` @@ -106,7 +106,7 @@ .. |P148_type| replace:: :cyan:`Display` .. |P148_typename| replace:: :cyan:`Display - POWR3xxD/THR3xxD` .. |P148_porttype| replace:: `.` -.. |P148_status| replace:: :yellow:`ENERGY` :yellow:`DISPLAY` +.. |P148_status| replace:: :yellow:`DISPLAY B` :yellow:`ENERGY` .. |P148_github| replace:: P148_POWRxxD_THR3xxD.ino .. _P148_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P148_POWRxxD_THR3xxD.ino .. |P148_usedby| replace:: `.` diff --git a/docs/source/Plugin/_plugin_substitutions_p16x.repl b/docs/source/Plugin/_plugin_substitutions_p16x.repl index 843ffeea8..4c533a76e 100644 --- a/docs/source/Plugin/_plugin_substitutions_p16x.repl +++ b/docs/source/Plugin/_plugin_substitutions_p16x.repl @@ -41,7 +41,7 @@ .. |P165_type| replace:: :cyan:`Display` .. |P165_typename| replace:: :cyan:`Display - NeoPixel (7-Segment)` .. |P165_porttype| replace:: `.` -.. |P165_status| replace:: :yellow:`DISPLAY` :yellow:`NEOPIXEL` +.. |P165_status| replace:: :yellow:`NEOPIXEL` .. |P165_github| replace:: P165_7SegNeopixel.ino .. _P165_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P165_7SegNeopixel.ino .. |P165_usedby| replace:: `.` @@ -67,7 +67,7 @@ .. |P167_type| replace:: :cyan:`Environment` .. |P167_typename| replace:: :cyan:`Environment - Sensirion SEN5x (IKEA Vindstyrka)` .. |P167_porttype| replace:: `.` -.. |P167_status| replace:: :yellow:`CLIMATE` +.. |P167_status| replace:: :yellow:`CLIMATE B` .. |P167_github| replace:: P167_Vindstyrka.ino .. _P167_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P167_Vindstyrka.ino .. |P167_usedby| replace:: `.` @@ -80,7 +80,7 @@ .. |P168_type| replace:: :cyan:`Light/Lux` .. |P168_typename| replace:: :cyan:`Light/Lux - VEML6030/VEML7700` .. |P168_porttype| replace:: `.` -.. |P168_status| replace:: :yellow:`COLLECTION G` :yellow:`CLIMATE` +.. |P168_status| replace:: :yellow:`COLLECTION G` :yellow:`CLIMATE B` .. |P168_github| replace:: P168_VEML6030_7700.ino .. _P168_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P168_VEML6030_7700.ino .. |P168_usedby| replace:: `.` @@ -93,7 +93,7 @@ .. |P169_type| replace:: :cyan:`Environment` .. |P169_typename| replace:: :cyan:`Environment - AS3935 Lightning Detector` .. |P169_porttype| replace:: `.` -.. |P169_status| replace:: :yellow:`CLIMATE` +.. |P169_status| replace:: :yellow:`CLIMATE B` .. |P169_github| replace:: P169_AS3935_LightningDetector.ino .. _P169_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P169_AS3935_LightningDetector.ino .. |P169_usedby| replace:: `.` diff --git a/docs/source/Plugin/_plugin_substitutions_p17x.repl b/docs/source/Plugin/_plugin_substitutions_p17x.repl index d22d9a31e..48d81f750 100644 --- a/docs/source/Plugin/_plugin_substitutions_p17x.repl +++ b/docs/source/Plugin/_plugin_substitutions_p17x.repl @@ -15,7 +15,7 @@ .. |P172_type| replace:: :cyan:`Environment` .. |P172_typename| replace:: :cyan:`Environment - BMP3xx (SPI)` .. |P172_porttype| replace:: `.` -.. |P172_status| replace:: :yellow:`COLLECTION G` :yellow:`CLIMATE` +.. |P172_status| replace:: :yellow:`COLLECTION G` :yellow:`CLIMATE B` .. |P172_github| replace:: P172_BMP3xx_SPI.ino .. _P172_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P172_BMP3xx_SPI.ino .. |P172_usedby| replace:: `.` @@ -28,7 +28,7 @@ .. |P173_type| replace:: :cyan:`Environment` .. |P173_typename| replace:: :cyan:`Environment - SHTC3` .. |P173_porttype| replace:: `.` -.. |P173_status| replace:: :yellow:`COLLECTION G` :yellow:`CLIMATE` +.. |P173_status| replace:: :yellow:`COLLECTION H` :yellow:`CLIMATE B` .. |P173_github| replace:: P173_SHTC3.ino .. _P173_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P173_SHTC3.ino .. |P173_usedby| replace:: `.` @@ -41,7 +41,7 @@ .. |P175_type| replace:: :cyan:`Dust` .. |P175_typename| replace:: :cyan:`Dust - PMSx003i (I2C)` .. |P175_porttype| replace:: `I2C` -.. |P175_status| replace:: :yellow:`CLIMATE` +.. |P175_status| replace:: :yellow:`CLIMATE B` .. |P175_github| replace:: P175_PMSx003i.ino .. _P175_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P175_PMSx003i.ino .. |P175_usedby| replace:: `.` @@ -67,7 +67,7 @@ .. |P177_type| replace:: :cyan:`Environment` .. |P177_typename| replace:: :cyan:`Environment - XDB401 I2C Pressure` .. |P177_porttype| replace:: `.` -.. |P177_status| replace:: :yellow:`COLLECTION G` :yellow:`CLIMATE` +.. |P177_status| replace:: :yellow:`COLLECTION H` :yellow:`CLIMATE B` .. |P177_github| replace:: P177_XDB_pressure.ino .. _P177_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P177_XDB_pressure.ino .. |P177_usedby| replace:: `.` @@ -80,7 +80,7 @@ .. |P178_type| replace:: :cyan:`Extra IO` .. |P178_typename| replace:: :cyan:`Extra IO - LU9685 Servo controller` .. |P178_porttype| replace:: `.` -.. |P178_status| replace:: :yellow:`COLLECTION G` :yellow:`CLIMATE` +.. |P178_status| replace:: :yellow:`COLLECTION H` :yellow:`CLIMATE B` .. |P178_github| replace:: P178_LU9685.ino .. _P178_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P178_LU9685.ino .. |P178_usedby| replace:: `LU9685 16/20 channel servo controller` diff --git a/docs/source/Reference/GPIO.rst b/docs/source/Reference/GPIO.rst index fd2fd282f..ca9b50539 100644 --- a/docs/source/Reference/GPIO.rst +++ b/docs/source/Reference/GPIO.rst @@ -614,6 +614,21 @@ ESP32-C2 (ESP8684) The strapping combination of GPIO-8 = 0 and GPIO-9 = 0 is invalid and will trigger unexpected behavior. +ESP32-C5 +~~~~~~~~ + +* GPIO-26 ... 28: "Select Bootloader Mode" pin +* GPIO-27: Enable or disable ROM messages printing +* GPIO-7: JTAG signal source +* GPIO-2: MTMS, used to select crystal frequency in non-standard boot mode + + +GPIO-2 is only used when the crystal frequency isn't explicitly set via efuses (which should not happen). + +GPIO-25 and -3 are used to set SDIO sampling and driving clock edge. +However this is also never used as SDIO mode means no flash and the chip is connected to a CPU and used as WiFi card. + + ESP32-C6 ~~~~~~~~ diff --git a/docs/source/Reference/SystemVariable.rst b/docs/source/Reference/SystemVariable.rst index 82293b6d4..62d8554b9 100644 --- a/docs/source/Reference/SystemVariable.rst +++ b/docs/source/Reference/SystemVariable.rst @@ -123,6 +123,14 @@ More uses of these system variables can be seen in the rules section and formula Does not have the ``+xm`` and ``-xh`` calculations that ``%sunrise%`` and ``%sunset%`` support. - + * - ``%latitude%`` + - 50.12345 + - Configured Latitude (decimal degrees) as used for calculating the sunrise and sunset times. + - + * - ``%longitude%`` + - 50.12345 + - Configured Longitude (decimal degrees) as used for calculating the sunrise and sunset times. + - * - ``%lcltime_am%`` - 2020-03-16 1:23:54 AM - Current date/time (AM/PM) if NTP is enabled (YYYY-MM-DD hh:mm:ss xM). @@ -357,6 +365,12 @@ The conversion always outputs a string, but not all of these can be converted ba * - mm to imperial: ``%c_mm2imp%(1900)`` - mm to imperial: ``6'2.8"`` - Millimeter to imperial units + * - Degrees to radians: ``%c_d2r%(22)`` + - Degrees to radians: ``0.38`` + - Degrees to radians + * - Radians to degrees: ``%c_r2d%(0.357)`` + - Radians to degrees: ``20.45`` + - Radians to degrees * - Mins to days: ``%c_m2day%(1900)`` - Mins to days: ``1.32`` - Minutes expressed in days diff --git a/docs/source/Rules/Rules.rst b/docs/source/Rules/Rules.rst index 77345f80a..d15df393e 100644 --- a/docs/source/Rules/Rules.rst +++ b/docs/source/Rules/Rules.rst @@ -1587,9 +1587,11 @@ Basic Math Functions * ``round(x)`` Rounds to the nearest integer, but rounds halfway cases away from zero (instead of to the nearest even integer). * ``^`` The caret is used as the exponentiation operator for calculating the value of x to the power of y (x\ :sup:`y`). -* ``map(value:fromLow:fromHigh:toLow:toHigh)`` Maps value x in the fromLow/fromHigh range to toLow/toHigh values. Similar to the Arduino map() function. See examples below. (Using a colon as an argument separator to not interfere with regular argument processing) +* ``map(value:fromLow:fromHigh:toLow:toHigh)`` Maps ``value`` in the fromLow/fromHigh range to toLow/toHigh values. Similar to the Arduino map() function. See examples below. (Using a colon as an argument separator to not interfere with regular argument processing) * ``mapc(value:fromLow:fromHigh:toLow:toHigh)`` same as map, but constrains the result to the fromLow/fromHigh range. +* ``fmod(x:y)`` Like the modulo operator ``%`` that returns an integer remainder, ``fmod`` returns the floating-point remainder of the division ``x / y``. Added: 2025-12-13 (Not available in limited builds) + Rules example: @@ -1662,6 +1664,7 @@ Radian Angle: * ``aSin(x)`` Arc Sine of x (radian) * ``aCos(x)`` Arc Cosine of x (radian) * ``aTan(x)`` Arc Tangent of x (radian) +* ``aTan2(x:y)`` Arc Tangent of x / y (radian) Added: 2025-12-13 Degree Angle: @@ -1671,6 +1674,7 @@ Degree Angle: * ``aSin_d(x)`` Arc Sine of x (degree) * ``aCos_d(x)`` Arc Cosine of x (degree) * ``aTan_d(x)`` Arc Tangent of x (degree) +* ``aTan2_d(x:y)`` Arc Tangent of x / y (degree) Added: 2025-12-13 diff --git a/docs/source/Tools/Tools.rst b/docs/source/Tools/Tools.rst index d7160eec2..2d65ff4e6 100644 --- a/docs/source/Tools/Tools.rst +++ b/docs/source/Tools/Tools.rst @@ -482,6 +482,16 @@ If the Unit of Measure, configurable per Task Value, should not be displayed on NB: This option is excluded from the build if this feature is not enabled. +MQTT Connect in background +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Added: 2025-08-23 + +Depending on the internet connection, f.e. when connecting via a low-end mobile network or other slow connection type, starting a connection to a MQTT Broker can take quite some time. + +On ESP32, tasks like starting the MQTT connection can be delegated to an independent background task, to avoid blocking the normal working of ESPEasy, that will report the result when completed. This feature is enabled by default, as it is the preferred setting, but when this way of connecting is causing issues, it can be disabled. + + Allow OTA without size-check ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -951,6 +961,18 @@ System Variables Shows an overview of the available variables set from rules, and available ``%variable%`` variables in several categories. +Included Plugins +================ + +Show a list of all plugins available in the build, Plugins, Controllers and Notifications. + +For Devices, the plugins are listed both sorted by Description and by Plugin ID, for easier retrieval. + +Controller plugins also show columns for MQTT and TLS (when included in the build). + +.. image:: images/Tools_PluginList_Controllers.png + + Interfaces ********** diff --git a/docs/source/Tools/images/Tools_PluginList_Controllers.png b/docs/source/Tools/images/Tools_PluginList_Controllers.png new file mode 100644 index 000000000..aa4c6cb01 Binary files /dev/null and b/docs/source/Tools/images/Tools_PluginList_Controllers.png differ diff --git a/docs/source/conf.py b/docs/source/conf.py index 45b906505..96817160c 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -21,7 +21,7 @@ import sphinx_bootstrap_theme # -- Project information ----------------------------------------------------- project = u'ESP Easy' -copyright = u'2018-2025, ESP Easy' +copyright = u'2018-2026, ESP Easy' author = u'Grovkillen, TD-er & Friends' # The short X.Y version diff --git a/docs/source/index.rst b/docs/source/index.rst index 00e9cc33e..7adfb7c4e 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -14,8 +14,9 @@ Welcome to ESP Easy's documentation! ESPEasy/ESPchips.rst ESPEasy/FunctionBlocks.rst Config/Config.rst - Controller/_Controller.rst Hardware/Hardware.rst + Network/_Network.rst + Controller/_Controller.rst Plugin/_Plugin.rst Rules/Rules.rst Notify/_Notifications.rst diff --git a/lib/Adafruit_ST77xx/Adafruit_ST7735.cpp b/lib/Adafruit_ST77xx/Adafruit_ST7735.cpp index f0494dd85..0957c2535 100644 --- a/lib/Adafruit_ST77xx/Adafruit_ST7735.cpp +++ b/lib/Adafruit_ST77xx/Adafruit_ST7735.cpp @@ -206,6 +206,22 @@ static const uint8_t PROGMEM ST77XX_RASET, 4, // 2: Row addr set, 4 args, no delay: 0x00, 0x00, // XSTART = 0 320 >> 8, 320 & 0xFF }, // XEND = 320 + Rcmd2black240x320[] = { // 7735R init, part 2 (mini 240x320) + 2, // 2 commands in list: + ST77XX_CASET, 4, // 1: Column addr set, 4 args, no delay: + 0x00, 0x00, // XSTART = 0 + 0x00, 240, // XEND = 240 + ST77XX_RASET, 4, // 2: Row addr set, 4 args, no delay: + 0x00, 0x00, // XSTART = 0 + 320 >> 8, 320 & 0xFF }, // XEND = 320 + Rcmd2black240x280[] = { // 7735R init, part 2 (mini 240x280) + 2, // 2 commands in list: + ST77XX_CASET, 4, // 1: Column addr set, 4 args, no delay: + 0x00, 0x00, // XSTART = 0 + 0x00, 240, // XEND = 240 + ST77XX_RASET, 4, // 2: Row addr set, 4 args, no delay: + 0x00, 0x00, // XSTART = 0 + 280 >> 8, 280 & 0xFF }, // XEND = 240 #endif // if ST7735_EXTRA_INIT Rcmd3[] = { // 7735R init, part 3 (red or green tab) @@ -292,6 +308,20 @@ void Adafruit_ST7735::initR(uint8_t options) { const uint8_t data = 0x00; sendCommand(ST77XX_INVON, &data, 0); sendCommand(ST77XX_INVON, &data, 0); // Write twice... + } else if (options == INITR_BLACKTAB240x320) { + _height = ST7735_TFTHEIGHT_320; + _width = ST7735_TFTWIDTH_240; + displayInit(Rcmd2black240x320); + const uint8_t data = 0x00; + sendCommand(ST77XX_INVON, &data, 0); + sendCommand(ST77XX_INVON, &data, 0); // Write twice... + } else if (options == INITR_BLACKTAB240x280) { + _height = ST7735_TFTHEIGHT_280; + _width = ST7735_TFTWIDTH_240; + displayInit(Rcmd2black240x280); + const uint8_t data = 0x00; + sendCommand(ST77XX_INVON, &data, 0); + sendCommand(ST77XX_INVON, &data, 0); // Write twice... #endif // if ST7735_EXTRA_INIT } else { // colstart, rowstart left at default '0' values @@ -339,7 +369,7 @@ void Adafruit_ST7735::setRotation(uint8_t m) { if ((tabcolor == INITR_BLACKTAB) || (tabcolor == INITR_MINI160x80)) { madctl = ST77XX_MADCTL_MX | ST77XX_MADCTL_MY | ST77XX_MADCTL_RGB; #if ST7735_EXTRA_INIT - } else if ((tabcolor == INITR_BLACKTAB135x240) || (tabcolor == INITR_BLACKTAB172x320) || (tabcolor == INITR_BLACKTAB170x320)) { + } else if ((tabcolor == INITR_BLACKTAB135x240) || (tabcolor == INITR_BLACKTAB172x320) || (tabcolor == INITR_BLACKTAB170x320) || (tabcolor == INITR_BLACKTAB240x320) || (tabcolor == INITR_BLACKTAB240x280)) { madctl = ST77XX_MADCTL_MY | ST77XX_MADCTL_MV | ST77XX_MADCTL_RGB; #endif // if ST7735_EXTRA_INIT } else { @@ -368,6 +398,16 @@ void Adafruit_ST7735::setRotation(uint8_t m) { _width = ST7735_TFTWIDTH_170; _colstart = 35; _rowstart = 0; + } else if (tabcolor == INITR_BLACKTAB240x320) { + _height = ST7735_TFTHEIGHT_320; + _width = ST7735_TFTWIDTH_240; + _colstart = 0; + _rowstart = 0; + } else if (tabcolor == INITR_BLACKTAB240x280) { + _height = ST7735_TFTHEIGHT_280; + _width = ST7735_TFTWIDTH_240; + _colstart = 0; + _rowstart = 20; #endif // if ST7735_EXTRA_INIT } else { _height = ST7735_TFTHEIGHT_160; @@ -380,7 +420,7 @@ void Adafruit_ST7735::setRotation(uint8_t m) { if ((tabcolor == INITR_BLACKTAB) || (tabcolor == INITR_MINI160x80)) { madctl = ST77XX_MADCTL_MY | ST77XX_MADCTL_MV | ST77XX_MADCTL_RGB; #if ST7735_EXTRA_INIT - } else if ((tabcolor == INITR_BLACKTAB135x240) || (tabcolor == INITR_BLACKTAB172x320) || (tabcolor == INITR_BLACKTAB170x320)) { + } else if ((tabcolor == INITR_BLACKTAB135x240) || (tabcolor == INITR_BLACKTAB172x320) || (tabcolor == INITR_BLACKTAB170x320) || (tabcolor == INITR_BLACKTAB240x320) || (tabcolor == INITR_BLACKTAB240x280)) { madctl = ST77XX_MADCTL_MX | ST77XX_MADCTL_MY | ST77XX_MADCTL_RGB; #endif // if ST7735_EXTRA_INIT } else { @@ -409,6 +449,16 @@ void Adafruit_ST7735::setRotation(uint8_t m) { _height = ST7735_TFTWIDTH_170; _colstart = 35; _rowstart = 0; + } else if (tabcolor == INITR_BLACKTAB240x320) { + _width = ST7735_TFTHEIGHT_320; + _height = ST7735_TFTWIDTH_240; + _colstart = 0; + _rowstart = 0; + } else if (tabcolor == INITR_BLACKTAB240x280) { + _width = ST7735_TFTHEIGHT_280; + _height = ST7735_TFTWIDTH_240; + _colstart = 0; + _rowstart = 20; #endif // if ST7735_EXTRA_INIT } else { _width = ST7735_TFTHEIGHT_160; @@ -421,7 +471,7 @@ void Adafruit_ST7735::setRotation(uint8_t m) { if ((tabcolor == INITR_BLACKTAB) || (tabcolor == INITR_MINI160x80)) { madctl = ST77XX_MADCTL_RGB; #if ST7735_EXTRA_INIT - } else if ((tabcolor == INITR_BLACKTAB135x240) || (tabcolor == INITR_BLACKTAB172x320) || (tabcolor == INITR_BLACKTAB170x320)) { + } else if ((tabcolor == INITR_BLACKTAB135x240) || (tabcolor == INITR_BLACKTAB172x320) || (tabcolor == INITR_BLACKTAB170x320) || (tabcolor == INITR_BLACKTAB240x320) || (tabcolor == INITR_BLACKTAB240x280)) { madctl = ST77XX_MADCTL_MX | ST77XX_MADCTL_MV | ST77XX_MADCTL_RGB; #endif // if ST7735_EXTRA_INIT } else { @@ -450,6 +500,16 @@ void Adafruit_ST7735::setRotation(uint8_t m) { _width = ST7735_TFTWIDTH_170; _colstart = 35; _rowstart = 0; + } else if (tabcolor == INITR_BLACKTAB240x320) { + _height = ST7735_TFTHEIGHT_320; + _width = ST7735_TFTWIDTH_240; + _colstart = 0; + _rowstart = 0; + } else if (tabcolor == INITR_BLACKTAB240x280) { + _height = ST7735_TFTHEIGHT_280; + _width = ST7735_TFTWIDTH_240; + _colstart = 0; + _rowstart = 20; #endif // if ST7735_EXTRA_INIT } else { _height = ST7735_TFTHEIGHT_160; @@ -462,7 +522,7 @@ void Adafruit_ST7735::setRotation(uint8_t m) { if ((tabcolor == INITR_BLACKTAB) || (tabcolor == INITR_MINI160x80)) { madctl = ST77XX_MADCTL_MX | ST77XX_MADCTL_MV | ST77XX_MADCTL_RGB; #if ST7735_EXTRA_INIT - } else if ((tabcolor == INITR_BLACKTAB135x240) || (tabcolor == INITR_BLACKTAB172x320) || (tabcolor == INITR_BLACKTAB170x320)) { + } else if ((tabcolor == INITR_BLACKTAB135x240) || (tabcolor == INITR_BLACKTAB172x320) || (tabcolor == INITR_BLACKTAB170x320) || (tabcolor == INITR_BLACKTAB240x320) || (tabcolor == INITR_BLACKTAB240x280)) { madctl = ST77XX_MADCTL_RGB; #endif // if ST7735_EXTRA_INIT } else { @@ -491,6 +551,16 @@ void Adafruit_ST7735::setRotation(uint8_t m) { _height = ST7735_TFTWIDTH_170; _colstart = 35; _rowstart = 0; + } else if (tabcolor == INITR_BLACKTAB240x320) { + _width = ST7735_TFTHEIGHT_320; + _height = ST7735_TFTWIDTH_240; + _colstart = 0; + _rowstart = 0; + } else if (tabcolor == INITR_BLACKTAB240x280) { + _width = ST7735_TFTHEIGHT_280; + _height = ST7735_TFTWIDTH_240; + _colstart = 0; + _rowstart = 20; #endif // if ST7735_EXTRA_INIT } else { _width = ST7735_TFTHEIGHT_160; diff --git a/lib/Adafruit_ST77xx/Adafruit_ST7735.h b/lib/Adafruit_ST77xx/Adafruit_ST7735.h index 8ad70e7c0..f47eff757 100644 --- a/lib/Adafruit_ST77xx/Adafruit_ST7735.h +++ b/lib/Adafruit_ST77xx/Adafruit_ST7735.h @@ -33,6 +33,8 @@ #define INITR_BLACKTAB135x240 0x07 #define INITR_BLACKTAB172x320 0x08 #define INITR_BLACKTAB170x320 0x09 +#define INITR_BLACKTAB240x320 0x0A +#define INITR_BLACKTAB240x280 0x0B // Some register settings #define ST7735_MADCTL_BGR 0x08 diff --git a/lib/Adafruit_ST77xx/Adafruit_ST77xx.h b/lib/Adafruit_ST77xx/Adafruit_ST77xx.h index e3af093d4..68b924203 100644 --- a/lib/Adafruit_ST77xx/Adafruit_ST77xx.h +++ b/lib/Adafruit_ST77xx/Adafruit_ST77xx.h @@ -36,9 +36,11 @@ #define ST7735_TFTWIDTH_135 135 #define ST7735_TFTWIDTH_172 172 #define ST7735_TFTWIDTH_170 170 +#define ST7735_TFTWIDTH_240 240 #define ST7735_TFTHEIGHT_128 128 // for 1.44" display #define ST7735_TFTHEIGHT_160 160 // for 1.8" and mini display #define ST7735_TFTHEIGHT_240 240 +#define ST7735_TFTHEIGHT_280 280 #define ST7735_TFTHEIGHT_320 320 #define ST_CMD_DELAY 0x80 // special signifier for command lists diff --git a/lib/ESPEasySerial/ESPEasySerial.cpp b/lib/ESPEasySerial/ESPEasySerial.cpp index c835a7a97..38af845a4 100644 --- a/lib/ESPEasySerial/ESPEasySerial.cpp +++ b/lib/ESPEasySerial/ESPEasySerial.cpp @@ -182,6 +182,14 @@ int ESPeasySerial::read(void) return _serialPort->read(); } +int ESPeasySerial::read(uint8_t *buffer, size_t size) +{ + if (!isValid()) { + return -1; + } + return _serialPort->read(buffer, size); +} + int ESPeasySerial::available(void) { if (!isValid()) { diff --git a/lib/ESPEasySerial/ESPEasySerialPort.cpp b/lib/ESPEasySerial/ESPEasySerialPort.cpp index e8db0e6b0..3a6c52b16 100644 --- a/lib/ESPEasySerial/ESPEasySerialPort.cpp +++ b/lib/ESPEasySerial/ESPEasySerialPort.cpp @@ -17,6 +17,15 @@ const __FlashStringHelper* ESPEasySerialPort_toString(ESPEasySerialPort port, bo #if USABLE_SOC_UART_NUM > 2 case ESPEasySerialPort::serial2: return shortName ? F("serial2") : F("HW Serial2"); #endif // if USABLE_SOC_UART_NUM > 2 +#if USABLE_SOC_UART_NUM > 3 + case ESPEasySerialPort::serial3: return shortName ? F("serial3") : F("HW Serial3"); +#endif +#if USABLE_SOC_UART_NUM > 4 + case ESPEasySerialPort::serial4: return shortName ? F("serial4") : F("HW Serial4"); +#endif +#if USABLE_SOC_UART_NUM > 5 + case ESPEasySerialPort::serial5: return shortName ? F("serial5") : F("HW Serial5"); +#endif #if USES_SW_SERIAL case ESPEasySerialPort::software: return shortName ? F("serialsw") : F("SW Serial"); #endif // if USES_SW_SERIAL @@ -49,6 +58,15 @@ bool isHWserial(ESPEasySerialPort port) #if USABLE_SOC_UART_NUM > 2 case ESPEasySerialPort::serial2: #endif // if USABLE_SOC_UART_NUM > 2 +#if USABLE_SOC_UART_NUM > 3 + case ESPEasySerialPort::serial3: +#endif +#if USABLE_SOC_UART_NUM > 4 + case ESPEasySerialPort::serial4: +#endif +#if USABLE_SOC_UART_NUM > 5 + case ESPEasySerialPort::serial5: +#endif return true; default: break; @@ -69,6 +87,15 @@ bool useGPIOpins(ESPEasySerialPort port) #if USABLE_SOC_UART_NUM > 2 case ESPEasySerialPort::serial2: #endif // if USABLE_SOC_UART_NUM > 2 +#if USABLE_SOC_UART_NUM > 3 + case ESPEasySerialPort::serial3: +#endif +#if USABLE_SOC_UART_NUM > 4 + case ESPEasySerialPort::serial4: +#endif +#if USABLE_SOC_UART_NUM > 5 + case ESPEasySerialPort::serial5: +#endif #if USES_SW_SERIAL case ESPEasySerialPort::software: #endif // if USES_SW_SERIAL @@ -96,6 +123,15 @@ bool validSerialPort(ESPEasySerialPort port) #if USABLE_SOC_UART_NUM > 2 case ESPEasySerialPort::serial2: #endif // if USABLE_SOC_UART_NUM > 2 +#if USABLE_SOC_UART_NUM > 3 + case ESPEasySerialPort::serial3: +#endif +#if USABLE_SOC_UART_NUM > 4 + case ESPEasySerialPort::serial4: +#endif +#if USABLE_SOC_UART_NUM > 5 + case ESPEasySerialPort::serial5: +#endif #if USES_SW_SERIAL case ESPEasySerialPort::software: #endif // if USES_SW_SERIAL diff --git a/lib/ESPEasySerial/ESPEasySerialPort.h b/lib/ESPEasySerial/ESPEasySerialPort.h index 79eb51a39..a2b5c2df6 100644 --- a/lib/ESPEasySerial/ESPEasySerialPort.h +++ b/lib/ESPEasySerial/ESPEasySerialPort.h @@ -20,6 +20,16 @@ enum class ESPEasySerialPort : uint8_t { #if USABLE_SOC_UART_NUM > 2 serial2 = 5, #endif // if USABLE_SOC_UART_NUM > 2 +#if USABLE_SOC_UART_NUM > 3 + serial3 = 10, +#endif +#if USABLE_SOC_UART_NUM > 4 + serial4 = 11, +#endif +#if USABLE_SOC_UART_NUM > 5 + serial5 = 12, +#endif + #if USES_SW_SERIAL software = 6, #endif // if USES_SW_SERIAL diff --git a/lib/ESPEasySerial/ESPEasySerialType.h b/lib/ESPEasySerial/ESPEasySerialType.h index d77ab506b..6370239ee 100644 --- a/lib/ESPEasySerial/ESPEasySerialType.h +++ b/lib/ESPEasySerial/ESPEasySerialType.h @@ -22,12 +22,16 @@ # define SOC_RX0 44 # elif CONFIG_IDF_TARGET_ESP32C6 # define SOC_RX0 17 +# elif CONFIG_IDF_TARGET_ESP32C61 +# define SOC_RX0 17 +# elif CONFIG_IDF_TARGET_ESP32C5 +# define SOC_RX0 12 # elif CONFIG_IDF_TARGET_ESP32C3 # define SOC_RX0 20 # elif CONFIG_IDF_TARGET_ESP32C2 # define SOC_RX0 19 # endif // if CONFIG_IDF_TARGET_ESP32 -# endif // ifndef SOC_RX0 +# endif // ifndef SOC_RX0 # ifndef SOC_TX0 # if CONFIG_IDF_TARGET_ESP32 @@ -36,68 +40,99 @@ # define SOC_TX0 43 # elif CONFIG_IDF_TARGET_ESP32C6 # define SOC_TX0 16 +# elif CONFIG_IDF_TARGET_ESP32C61 +# define SOC_TX0 16 +# elif CONFIG_IDF_TARGET_ESP32C5 +# define SOC_TX0 11 # elif CONFIG_IDF_TARGET_ESP32C3 # define SOC_TX0 21 # elif CONFIG_IDF_TARGET_ESP32C2 # define SOC_TX0 20 # endif // if CONFIG_IDF_TARGET_ESP32 -# endif // ifndef SOC_TX0 +# endif // ifndef SOC_TX0 -# if SOC_UART_NUM > 1 + +# if SOC_UART_HP_NUM > 1 # ifndef SOC_RX1 -# if CONFIG_IDF_TARGET_ESP32 -# define SOC_RX1 13 -# elif CONFIG_IDF_TARGET_ESP32S2 -# define SOC_RX1 18 -# elif CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 -# define SOC_RX1 4 -# elif CONFIG_IDF_TARGET_ESP32S3 -# define SOC_RX1 15 -# endif // if CONFIG_IDF_TARGET_ESP32 -# endif // ifndef SOC_RX1 +# ifdef RX1 +# define SOC_RX1 RX1 +# else +# if CONFIG_IDF_TARGET_ESP32 +# define SOC_RX1 13 +# elif CONFIG_IDF_TARGET_ESP32S2 +# define SOC_RX1 18 +# elif CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32C61 +# define SOC_RX1 4 +# elif CONFIG_IDF_TARGET_ESP32S3 +# define SOC_RX1 15 +# endif // if CONFIG_IDF_TARGET_ESP32 +# endif // ifdef RX1 +# endif // ifndef SOC_RX1 # ifndef SOC_TX1 -# if CONFIG_IDF_TARGET_ESP32 -# define SOC_TX1 15 -# elif CONFIG_IDF_TARGET_ESP32S2 -# define SOC_TX1 17 -# elif CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 -# define SOC_TX1 5 -# elif CONFIG_IDF_TARGET_ESP32S3 -# define SOC_TX1 16 -# endif // if CONFIG_IDF_TARGET_ESP32 -# endif // ifndef SOC_TX1 +# ifdef TX1 +# define SOC_TX1 TX1 +# else +# if CONFIG_IDF_TARGET_ESP32 +# define SOC_TX1 15 +# elif CONFIG_IDF_TARGET_ESP32S2 +# define SOC_TX1 17 +# elif CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32C61 +# define SOC_TX1 5 +# elif CONFIG_IDF_TARGET_ESP32S3 +# define SOC_TX1 16 +# endif // if CONFIG_IDF_TARGET_ESP32 +# endif // ifdef TX1 +# endif // ifndef SOC_TX1 -# endif // if SOC_UART_NUM > 1 +# endif // if SOC_UART_HP_NUM > 1 -# if SOC_UART_NUM > 2 +# if SOC_UART_HP_NUM > 2 # ifndef SOC_RX2 -# if CONFIG_IDF_TARGET_ESP32 -# define SOC_RX2 16 -# elif CONFIG_IDF_TARGET_ESP32S3 -# define SOC_RX2 19 -# elif CONFIG_IDF_TARGET_ESP32C6 -# define SOC_RX2 6 -# else // if CONFIG_IDF_TARGET_ESP32 -# define SOC_RX2 -1 -# endif // if CONFIG_IDF_TARGET_ESP32 -# endif // ifndef SOC_RX2 +# ifdef RX2 +# define SOC_RX2 RX2 +# else + +# if CONFIG_IDF_TARGET_ESP32 +# define SOC_RX2 16 +# elif CONFIG_IDF_TARGET_ESP32S3 +# define SOC_RX2 19 +# elif CONFIG_IDF_TARGET_ESP32C6 +# define SOC_RX2 6 +# elif CONFIG_IDF_TARGET_ESP32C61 +# define SOC_RX2 6 +# elif CONFIG_IDF_TARGET_ESP32C5 +# define SOC_RX2 6 +# else // if CONFIG_IDF_TARGET_ESP32 +# define SOC_RX2 -1 +# endif // if CONFIG_IDF_TARGET_ESP32 +# endif // ifdef RX2 +# endif // ifndef SOC_RX2 # ifndef SOC_TX2 -# if CONFIG_IDF_TARGET_ESP32 -# define SOC_TX2 17 -# elif CONFIG_IDF_TARGET_ESP32S3 -# define SOC_TX2 20 -# elif CONFIG_IDF_TARGET_ESP32C6 -# define SOC_TX2 7 -# else // if CONFIG_IDF_TARGET_ESP32 -# define SOC_TX2 -1 -# endif // if CONFIG_IDF_TARGET_ESP32 -# endif // ifndef SOC_TX2 +# ifdef TX2 +# define SOC_TX2 TX2 +# else -# endif // if SOC_UART_NUM > 2 +# if CONFIG_IDF_TARGET_ESP32 +# define SOC_TX2 17 +# elif CONFIG_IDF_TARGET_ESP32S3 +# define SOC_TX2 20 +# elif CONFIG_IDF_TARGET_ESP32C6 +# define SOC_TX2 7 +# elif CONFIG_IDF_TARGET_ESP32C61 +# define SOC_TX2 7 +# elif CONFIG_IDF_TARGET_ESP32C5 +# define SOC_TX2 7 +# else // if CONFIG_IDF_TARGET_ESP32 +# define SOC_TX2 -1 +# endif // if CONFIG_IDF_TARGET_ESP32 +# endif // ifdef TX2 +# endif // ifndef SOC_TX2 + +# endif // if SOC_UART_HP_NUM > 2 #endif // ifdef ESP32 @@ -109,6 +144,7 @@ struct ESPeasySerialType { static ESPEasySerialPort getSerialType(ESPEasySerialPort typeHint, int receivePin, int transmitPin); + }; diff --git a/lib/ESPEasySerial/ESPEasySerialType_ESP32.cpp b/lib/ESPEasySerial/ESPEasySerialType_ESP32.cpp index cb705867e..6738194d1 100644 --- a/lib/ESPEasySerial/ESPEasySerialType_ESP32.cpp +++ b/lib/ESPEasySerial/ESPEasySerialType_ESP32.cpp @@ -16,6 +16,15 @@ bool ESPeasySerialType::getSerialTypePins(ESPEasySerialPort serType, int& rxPin, # if USABLE_SOC_UART_NUM > 2 case ESPEasySerialPort::serial2: rxPin = SOC_RX2; txPin = SOC_TX2; return true; # endif // if USABLE_SOC_UART_NUM > 2 +# if USABLE_SOC_UART_NUM > 3 + case ESPEasySerialPort::serial3: return true; +# endif +# if USABLE_SOC_UART_NUM > 4 + case ESPEasySerialPort::serial4: return true; +# endif +# if USABLE_SOC_UART_NUM > 5 + case ESPEasySerialPort::serial5: return true; +# endif # if USES_I2C_SC16IS752 case ESPEasySerialPort::sc16is752: return true; # endif // if USES_I2C_SC16IS752 diff --git a/lib/ESPEasySerial/ESPEasySerial_common_defines.h b/lib/ESPEasySerial/ESPEasySerial_common_defines.h index 88d2a508c..f77432ede 100644 --- a/lib/ESPEasySerial/ESPEasySerial_common_defines.h +++ b/lib/ESPEasySerial/ESPEasySerial_common_defines.h @@ -8,7 +8,7 @@ #ifndef SOC_UART_NUM # ifdef ESP8266 # define SOC_UART_NUM 2 -# elif defined(ESP32_CLASSIC) || defined(ESP32S2) || defined(ESP32S3) || defined(ESP32C2) || defined(ESP32C3) || defined(ESP32C6) +# elif defined(ESP32_CLASSIC) || defined(ESP32S2) || defined(ESP32S3) || defined(ESP32C2) || defined(ESP32C3) || defined(ESP32C5) || defined(ESP32C6) || defined(ESP32C61) # include # else // ifdef ESP8266 static_assert(false, "Implement processor architecture"); @@ -35,12 +35,12 @@ static_assert(false, "Implement processor architecture"); #ifdef ESP32 /* - #if CONFIG_IDF_TARGET_ESP32C6 || // support USB via HWCDC using JTAG interface + #if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32C61 || // support USB via HWCDC using JTAG interface CONFIG_IDF_TARGET_ESP32C3 || // support USB via HWCDC using JTAG interface CONFIG_IDF_TARGET_ESP32S2 || // support USB via USBCDC CONFIG_IDF_TARGET_ESP32S3 // support USB via HWCDC using JTAG interface or USBCDC */ -# if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 +# if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32C61 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 // #if CONFIG_TINYUSB_CDC_ENABLED // This define is not recognized here so use USE_USB_CDC_CONSOLE # ifdef USE_USB_CDC_CONSOLE @@ -48,7 +48,14 @@ static_assert(false, "Implement processor architecture"); // ESP32C3/S3 embedded USB using JTAG interface # ifndef USES_HWCDC -# define USES_HWCDC 1 +# if CONFIG_IDF_TARGET_ESP32S2 +# define USES_HWCDC 0 +# ifndef USES_USBCDC +# define USES_USBCDC 1 +# endif // ifndef USES_USBCDC +# else +# define USES_HWCDC 1 +# endif # endif // ifndef USES_HWCDC # else // No ARDUINO_USB_MODE # ifndef USES_USBCDC @@ -96,7 +103,11 @@ static_assert(false, "Implement processor architecture"); #endif // ifndef USES_HWCDC #ifndef USES_USBCDC +#ifdef ESP32S2 +# define USES_USBCDC 1 +#else # define USES_USBCDC 0 +#endif #endif // ifndef USES_USBCDC diff --git a/lib/ESPEasySerial/ESPeasySerial.h b/lib/ESPEasySerial/ESPeasySerial.h index 2540cc5c7..00d766db3 100644 --- a/lib/ESPEasySerial/ESPeasySerial.h +++ b/lib/ESPEasySerial/ESPeasySerial.h @@ -89,6 +89,7 @@ public: int peek(void); size_t write(uint8_t val) override; int read(void) override; + int read(uint8_t *buffer, size_t size); int available(void) override; int availableForWrite(void); void flush(void) override; diff --git a/lib/ESPEasySerial/Port_ESPEasySerial_HardwareSerial.cpp b/lib/ESPEasySerial/Port_ESPEasySerial_HardwareSerial.cpp index 20f29dda7..aad83e661 100644 --- a/lib/ESPEasySerial/Port_ESPEasySerial_HardwareSerial.cpp +++ b/lib/ESPEasySerial/Port_ESPEasySerial_HardwareSerial.cpp @@ -40,6 +40,16 @@ void Port_ESPEasySerial_HardwareSerial_t::resetConfig(const ESPEasySerialConfig& #if USABLE_SOC_UART_NUM > 2 case ESPEasySerialPort::serial2: #endif // if USABLE_SOC_UART_NUM > 2 + #if USABLE_SOC_UART_NUM > 3 + case ESPEasySerialPort::serial3: +#endif +#if USABLE_SOC_UART_NUM > 4 + case ESPEasySerialPort::serial4: +#endif +#if USABLE_SOC_UART_NUM > 5 + case ESPEasySerialPort::serial5: +#endif + _config.port = config.port; break; default: @@ -64,6 +74,18 @@ void Port_ESPEasySerial_HardwareSerial_t::resetConfig(const ESPEasySerialConfig& } else if (_config.port == ESPEasySerialPort::serial2) { _serial = &Serial2; #endif // if USABLE_SOC_UART_NUM > 2 +#if USABLE_SOC_UART_NUM > 3 + } else if (_config.port == ESPEasySerialPort::serial3) { + _serial = &Serial3; +#endif +#if USABLE_SOC_UART_NUM > 4 + } else if (_config.port == ESPEasySerialPort::serial4) { + _serial = &Serial4; +#endif +#if USABLE_SOC_UART_NUM > 5 + } else if (_config.port == ESPEasySerialPort::serial5) { + _serial = &Serial5; +#endif } else { _config.port = ESPEasySerialPort::not_set; } diff --git a/lib/ESPEasySerial/Port_ESPEasySerial_USBCDC.cpp b/lib/ESPEasySerial/Port_ESPEasySerial_USBCDC.cpp index 633ff2b83..c4e0bfaa0 100644 --- a/lib/ESPEasySerial/Port_ESPEasySerial_USBCDC.cpp +++ b/lib/ESPEasySerial/Port_ESPEasySerial_USBCDC.cpp @@ -3,12 +3,14 @@ #if USES_USBCDC -# if !ARDUINO_USB_CDC_ON_BOOT +#include + +# if !USB_SERIAL_IS_DEFINED USBCDC ESPEasySerial_USBCDC_port0(0); // USBCDC ESPEasySerial_USBCDC_port1(1); -# endif // if !ARDUINO_USB_CDC_ON_BOOT - +# endif +/* volatile bool usbActive = false; volatile int32_t eventidTriggered = ESP_EVENT_ANY_ID; @@ -59,15 +61,15 @@ static void usbcdcEventCallback(void *arg, esp_event_base_t event_base, int32_t usbActive = true; break; case ARDUINO_USB_CDC_RX_EVENT: - /* - Serial.printf("CDC RX EVENT [%u]: ", data->rx.len); - { - uint8_t buf[data->rx.len]; - size_t len = Serial.read(buf, data->rx.len); - Serial.write(buf, len); - } - Serial.println(); - */ + +// Serial.printf("CDC RX EVENT [%u]: ", data->rx.len); +// { +// uint8_t buf[data->rx.len]; +// size_t len = Serial.read(buf, data->rx.len); +// Serial.write(buf, len); +// } +// Serial.println(); + usbActive = true; break; case ARDUINO_USB_CDC_TX_EVENT: @@ -83,12 +85,14 @@ static void usbcdcEventCallback(void *arg, esp_event_base_t event_base, int32_t } } } +*/ Port_ESPEasySerial_USBCDC_t::Port_ESPEasySerial_USBCDC_t(const ESPEasySerialConfig& config) { - # if ARDUINO_USB_CDC_ON_BOOT + # if USB_SERIAL_IS_DEFINED + _serial = &USBSerial; - # else // if ARDUINO_USB_CDC_ON_BOOT + # else _serial = nullptr; if (config.port == ESPEasySerialPort::usb_cdc_0) { @@ -100,10 +104,11 @@ Port_ESPEasySerial_USBCDC_t::Port_ESPEasySerial_USBCDC_t(const ESPEasySerialConf _serial = &ESPEasySerial_USBCDC_port1; } */ - # endif // if ARDUINO_USB_CDC_ON_BOOT + # endif if (_serial != nullptr) { _config.port = config.port; +// _serial->end(); // USB.onEvent(usbcdcEventCallback); // _serial->onEvent(usbcdcEventCallback); @@ -115,7 +120,7 @@ Port_ESPEasySerial_USBCDC_t::Port_ESPEasySerial_USBCDC_t(const ESPEasySerialConf _serial->begin(); // _serial->onEvent(usbcdcEventCallback); - // USB.begin(); +// USB.begin(); delay(1); } } diff --git a/lib/ESPEasySerial/Port_ESPEasySerial_USBCDC.h b/lib/ESPEasySerial/Port_ESPEasySerial_USBCDC.h index 1a6ae9b4b..d3525a50f 100644 --- a/lib/ESPEasySerial/Port_ESPEasySerial_USBCDC.h +++ b/lib/ESPEasySerial/Port_ESPEasySerial_USBCDC.h @@ -11,7 +11,11 @@ # include "Port_ESPEasySerial_base.h" -class Port_ESPEasySerial_USBCDC_t : public Port_ESPEasySerial_base { +# include + + +class Port_ESPEasySerial_USBCDC_t : public Port_ESPEasySerial_base +{ public: Port_ESPEasySerial_USBCDC_t(const ESPEasySerialConfig& config); @@ -41,27 +45,27 @@ public: size_t setRxBufferSize(size_t new_size); size_t setTxBufferSize(size_t new_size); - bool setRS485Mode(int8_t rtsPin, bool enableCollisionDetection = false); + bool setRS485Mode(int8_t rtsPin, + bool enableCollisionDetection = false); virtual int getBaudRate() const override; private: -# if ARDUINO_USB_CDC_ON_BOOT - USBCDC *_serial = &Serial; +#if USB_SERIAL_IS_DEFINED + USBCDC *_serial = &USBSerial; # else // if ARDUINO_USB_CDC_ON_BOOT USBCDC *_serial = nullptr; # endif // if ARDUINO_USB_CDC_ON_BOOT -}; +}; // class Port_ESPEasySerial_USBCDC_t // Need to define these objects as extern as they need to be defined before setup() is being called. -# if ARDUINO_USB_CDC_ON_BOOT -# else +# if !USB_SERIAL_IS_DEFINED extern USBCDC ESPEasySerial_USBCDC_port0; // extern USBCDC ESPEasySerial_USBCDC_port1; -# endif // if ARDUINO_USB_CDC_ON_BOOT +# endif #endif // if USES_USBCDC diff --git a/lib/ESPEasySerial/Port_ESPEasySerial_USB_HWCDC.cpp b/lib/ESPEasySerial/Port_ESPEasySerial_USB_HWCDC.cpp index 0da2053c0..f57ae8ec4 100644 --- a/lib/ESPEasySerial/Port_ESPEasySerial_USB_HWCDC.cpp +++ b/lib/ESPEasySerial/Port_ESPEasySerial_USB_HWCDC.cpp @@ -2,7 +2,9 @@ #if USES_HWCDC -// #include +#if !ARDUINO_USB_CDC_ON_BOOT + #include +#endif volatile bool usbActive = false; @@ -47,18 +49,19 @@ static void hwcdcEventCallback(void *arg, esp_event_base_t event_base, int32_t e } Port_ESPEasySerial_USB_HWCDC_t::Port_ESPEasySerial_USB_HWCDC_t(const ESPEasySerialConfig& config) - : + # if ARDUINO_USB_CDC_ON_BOOT // Serial used for USB CDC - _hwcdc_serial(&Serial) + : _hwcdc_serial(&Serial) # else // if ARDUINO_USB_CDC_ON_BOOT - _hwcdc_serial(&USBSerial) + : _hwcdc_serial(&myUsbSerial) # endif // if ARDUINO_USB_CDC_ON_BOOT { _config.port = ESPEasySerialPort::usb_hw_cdc; - - // USB.begin(); + #if !ARDUINO_USB_CDC_ON_BOOT + USB.begin(); + #endif if (_hwcdc_serial != nullptr) { - // _hwcdc_serial->end(); + _hwcdc_serial->end(); // _config.rxBuffSize = _hwcdc_serial->setRxBufferSize(_config.rxBuffSize); // _config.txBuffSize = _hwcdc_serial->setTxBufferSize(_config.txBuffSize); @@ -103,24 +106,24 @@ void Port_ESPEasySerial_USB_HWCDC_t::end() { int Port_ESPEasySerial_USB_HWCDC_t::available(void) { - if (_hwcdc_serial != nullptr) { - return _hwcdc_serial->available(); + if (_hwcdc_serial != nullptr && _hwcdc_serial->isConnected()) { + return _hwcdc_serial->available(); } return 0; } int Port_ESPEasySerial_USB_HWCDC_t::availableForWrite(void) { - if (_hwcdc_serial != nullptr) { - const int res = _hwcdc_serial->availableForWrite(); - return res > 64 ? 64 : res; + if (_hwcdc_serial != nullptr && _hwcdc_serial->isConnected()) { + const int res = _hwcdc_serial->availableForWrite(); + return res > 64 ? 64 : res; } return 0; } int Port_ESPEasySerial_USB_HWCDC_t::peek(void) { - if (_hwcdc_serial != nullptr) { + if (_hwcdc_serial != nullptr && _hwcdc_serial->isConnected()) { return _hwcdc_serial->peek(); } return 0; @@ -128,7 +131,7 @@ int Port_ESPEasySerial_USB_HWCDC_t::peek(void) int Port_ESPEasySerial_USB_HWCDC_t::read(void) { - if (_hwcdc_serial != nullptr) { + if (_hwcdc_serial != nullptr && _hwcdc_serial->isConnected()) { return _hwcdc_serial->read(); } return 0; @@ -137,7 +140,7 @@ int Port_ESPEasySerial_USB_HWCDC_t::read(void) size_t Port_ESPEasySerial_USB_HWCDC_t::read(uint8_t *buffer, size_t size) { - if (_hwcdc_serial != nullptr) { + if (_hwcdc_serial != nullptr && _hwcdc_serial->isConnected()) { return _hwcdc_serial->read(buffer, size); } return 0; @@ -145,7 +148,7 @@ size_t Port_ESPEasySerial_USB_HWCDC_t::read(uint8_t *buffer, void Port_ESPEasySerial_USB_HWCDC_t::flush(void) { - if (_hwcdc_serial != nullptr) { + if (_hwcdc_serial != nullptr && _hwcdc_serial->isConnected()) { return _hwcdc_serial->flush(); } } @@ -174,10 +177,9 @@ size_t Port_ESPEasySerial_USB_HWCDC_t::write(const uint8_t *buffer, Port_ESPEasySerial_USB_HWCDC_t::operator bool() const { - if (_hwcdc_serial != nullptr) { + if (_hwcdc_serial != nullptr && _hwcdc_serial->isConnected()) { // return usbActive; - const bool connected = (*_hwcdc_serial); - return connected; + return true; } return false; } diff --git a/lib/ESPEasySerial/Port_ESPEasySerial_USB_HWCDC.h b/lib/ESPEasySerial/Port_ESPEasySerial_USB_HWCDC.h index a71bfd411..7de5565a5 100644 --- a/lib/ESPEasySerial/Port_ESPEasySerial_USB_HWCDC.h +++ b/lib/ESPEasySerial/Port_ESPEasySerial_USB_HWCDC.h @@ -43,7 +43,9 @@ public: bool setRS485Mode(int8_t rtsPin, bool enableCollisionDetection = false); private: - +#if !ARDUINO_USB_CDC_ON_BOOT + HWCDC myUsbSerial; +#endif HWCDC *_hwcdc_serial= nullptr; }; diff --git a/lib/GPIO_DirectAccess/GPIO_Direct_Access.h b/lib/GPIO_DirectAccess/GPIO_Direct_Access.h index 36dd37c1f..625ea1fec 100644 --- a/lib/GPIO_DirectAccess/GPIO_Direct_Access.h +++ b/lib/GPIO_DirectAccess/GPIO_Direct_Access.h @@ -139,6 +139,7 @@ void DIRECT_PINMODE_INPUT_ISR(IO_REG_TYPE pin); #include #include #include +#include #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) #include #endif @@ -152,17 +153,17 @@ void DIRECT_PINMODE_INPUT_ISR(IO_REG_TYPE pin); static inline __attribute__((always_inline)) IO_REG_TYPE directRead(IO_REG_TYPE pin) { -#if CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 +#if CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C5 return (GPIO.in.val >> pin) & 0x1; -#elif CONFIG_IDF_TARGET_ESP32C6 +#elif CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32C61 || CONFIG_IDF_TARGET_ESP32P4 if ( pin < 32 ) return (GPIO.in.val >> pin) & 0x1; - else if ( pin < 46 ) + else if ( pin < SOC_GPIO_PIN_COUNT ) return (GPIO.in1.val >> (pin - 32)) & 0x1; #else // plain ESP32 if ( pin < 32 ) return (GPIO.in >> pin) & 0x1; - else if ( pin < 46 ) + else if ( pin < SOC_GPIO_PIN_COUNT ) return (GPIO.in1.val >> (pin - 32)) & 0x1; #endif @@ -172,18 +173,18 @@ IO_REG_TYPE directRead(IO_REG_TYPE pin) static inline __attribute__((always_inline)) void directWriteLow(IO_REG_TYPE pin) { -#if CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 +#if CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C5 GPIO.out_w1tc.val = ((uint32_t)1 << pin); -#elif CONFIG_IDF_TARGET_ESP32C6 +#elif CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32C61 || CONFIG_IDF_TARGET_ESP32P4 if ( pin < 32 ) GPIO.out_w1tc.val = ((uint32_t)1 << pin); - else if ( pin < 46 ) + else if ( pin < SOC_GPIO_PIN_COUNT ) GPIO.out1_w1tc.val = ((uint32_t)1 << (pin - 32)); #else // plain ESP32 if ( pin < 32 ) GPIO.out_w1tc = ((uint32_t)1 << pin); - else if ( pin < 46 ) + else if ( pin < SOC_GPIO_PIN_COUNT ) GPIO.out1_w1tc.val = ((uint32_t)1 << (pin - 32)); #endif } @@ -191,18 +192,18 @@ void directWriteLow(IO_REG_TYPE pin) static inline __attribute__((always_inline)) void directWriteHigh(IO_REG_TYPE pin) { -#if CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 +#if CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C5 GPIO.out_w1ts.val = ((uint32_t)1 << pin); -#elif CONFIG_IDF_TARGET_ESP32C6 +#elif CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32C61 || CONFIG_IDF_TARGET_ESP32P4 if ( pin < 32 ) GPIO.out_w1ts.val = ((uint32_t)1 << pin); - else if ( pin < 46 ) + else if ( pin < SOC_GPIO_PIN_COUNT ) GPIO.out1_w1ts.val = ((uint32_t)1 << (pin - 32)); #else // plain ESP32 if ( pin < 32 ) GPIO.out_w1ts = ((uint32_t)1 << pin); - else if ( pin < 46 ) + else if ( pin < SOC_GPIO_PIN_COUNT ) GPIO.out1_w1ts.val = ((uint32_t)1 << (pin - 32)); #endif } @@ -210,7 +211,7 @@ void directWriteHigh(IO_REG_TYPE pin) static inline __attribute__((always_inline)) void directModeInput(IO_REG_TYPE pin) { -#if CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 +#if CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C5 GPIO.enable_w1tc.val = ((uint32_t)1 << (pin)); #else if ( GPIO_IS_VALID_GPIO(pin) ) @@ -225,7 +226,7 @@ void directModeInput(IO_REG_TYPE pin) } #endif // Input -#if CONFIG_IDF_TARGET_ESP32C6 +#if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32C61 || CONFIG_IDF_TARGET_ESP32P4 if ( pin < 32 ) GPIO.enable_w1tc.val = ((uint32_t)1 << pin); else @@ -243,7 +244,7 @@ void directModeInput(IO_REG_TYPE pin) static inline __attribute__((always_inline)) void directModeOutput(IO_REG_TYPE pin) { -#if CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 +#if CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C5 GPIO.enable_w1ts.val = ((uint32_t)1 << (pin)); #else if ( GPIO_IS_VALID_GPIO(pin) && pin <= 33 ) // pins above 33 can be only inputs @@ -258,7 +259,7 @@ void directModeOutput(IO_REG_TYPE pin) } #endif // Output -#if CONFIG_IDF_TARGET_ESP32C6 +#if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32C61 || CONFIG_IDF_TARGET_ESP32P4 if ( pin < 32 ) GPIO.enable_w1ts.val = ((uint32_t)1 << pin); else // already validated to pins <= 33 diff --git a/lib/ImprovWiFi/src/ImprovTypes.h b/lib/ImprovWiFi/src/ImprovTypes.h index 3e77a7cf3..cde1be8ea 100644 --- a/lib/ImprovWiFi/src/ImprovTypes.h +++ b/lib/ImprovWiFi/src/ImprovTypes.h @@ -87,8 +87,12 @@ struct ImprovWiFiParamsStruct { "ESP32-C2" #elif defined(CONFIG_IDF_TARGET_ESP32C3) "ESP32-C3" +#elif defined(CONFIG_IDF_TARGET_ESP32C5) + "ESP32-C5" #elif defined(CONFIG_IDF_TARGET_ESP32C6) "ESP32-C6" +#elif defined(CONFIG_IDF_TARGET_ESP32C61) + "ESP32-C61" #elif defined(CONFIG_IDF_TARGET_ESP32H2) "ESP32-H2" #elif defined(ESP8266) diff --git a/lib/NeoPixelBus/src/internal/DotStarEsp32DmaSpiMethod.h b/lib/NeoPixelBus/src/internal/DotStarEsp32DmaSpiMethod.h index 953a216e7..398e03344 100644 --- a/lib/NeoPixelBus/src/internal/DotStarEsp32DmaSpiMethod.h +++ b/lib/NeoPixelBus/src/internal/DotStarEsp32DmaSpiMethod.h @@ -29,12 +29,12 @@ License along with NeoPixel. If not, see #include "driver/spi_master.h" -#if (defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C2) || defined(CONFIG_IDF_TARGET_ESP32C6)) && !defined(HSPI_HOST) +#if (defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C2) || defined(CONFIG_IDF_TARGET_ESP32C6)) && !defined(HSPI_HOST) // HSPI_HOST depreciated in C3 #define HSPI_HOST SPI2_HOST #endif -#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C2) && !defined(CONFIG_IDF_TARGET_ESP32C6) +#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C2) && !defined(CONFIG_IDF_TARGET_ESP32C6) class Esp32VspiBus { public: @@ -52,7 +52,7 @@ public: const static int ParallelBits = 1; }; -#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C2) && !defined(CONFIG_IDF_TARGET_ESP32C6) +#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C2) && !defined(CONFIG_IDF_TARGET_ESP32C6) class Esp32Vspi2BitBus { public: @@ -70,7 +70,7 @@ public: const static int ParallelBits = 2; }; -#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C2) && !defined(CONFIG_IDF_TARGET_ESP32C6) +#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C2) && !defined(CONFIG_IDF_TARGET_ESP32C6) class Esp32Vspi4BitBus { public: @@ -174,7 +174,7 @@ public: // If pins aren't specified, initialize bus with just the default SCK and MOSI pins for the SPI peripheral (no SS, no >1-bit pins) void Initialize() { -#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C2) && !defined(CONFIG_IDF_TARGET_ESP32C6) +#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C2) && !defined(CONFIG_IDF_TARGET_ESP32C6) if (T_SPIBUS::SpiHostDevice == VSPI_HOST) { Initialize(SCK, -1, MOSI, -1, -1, -1); @@ -277,7 +277,7 @@ private: int8_t _ssPin; }; -#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C2) && !defined(CONFIG_IDF_TARGET_ESP32C6) +#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C2) && !defined(CONFIG_IDF_TARGET_ESP32C6) // Clock Speed and Default Definitions for DotStarEsp32DmaVspi typedef DotStarEsp32DmaSpiMethod DotStarEsp32DmaVspi40MhzMethod; typedef DotStarEsp32DmaSpiMethod DotStarEsp32DmaVspi20MhzMethod; @@ -303,7 +303,7 @@ typedef DotStarEsp32DmaSpiMethod DotStarEsp32DmaHspiHz typedef DotStarEsp32DmaHspi10MhzMethod DotStarEsp32DmaHspiMethod; -#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C2) && !defined(CONFIG_IDF_TARGET_ESP32C6) +#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C2) && !defined(CONFIG_IDF_TARGET_ESP32C6) // Clock Speed and Default Definitions for DotStarEsp32DmaVspi2Bit typedef DotStarEsp32DmaSpiMethod DotStarEsp32DmaVspi2Bit40MhzMethod; typedef DotStarEsp32DmaSpiMethod DotStarEsp32DmaVspi2Bit20MhzMethod; @@ -329,7 +329,7 @@ typedef DotStarEsp32DmaSpiMethod DotStarEsp32DmaHsp typedef DotStarEsp32DmaHspi2Bit10MhzMethod DotStarEsp32DmaHspi2BitMethod; -#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C2) && !defined(CONFIG_IDF_TARGET_ESP32C6) +#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C2) && !defined(CONFIG_IDF_TARGET_ESP32C6) // Clock Speed and Default Definitions for DotStarEsp32DmaVspi4Bit typedef DotStarEsp32DmaSpiMethod DotStarEsp32DmaVspi4Bit40MhzMethod; typedef DotStarEsp32DmaSpiMethod DotStarEsp32DmaVspi4Bit20MhzMethod; diff --git a/lib/NeoPixelBus/src/internal/Esp32_i2s.c b/lib/NeoPixelBus/src/internal/Esp32_i2s.c index 5823b1875..c4ff33239 100644 --- a/lib/NeoPixelBus/src/internal/Esp32_i2s.c +++ b/lib/NeoPixelBus/src/internal/Esp32_i2s.c @@ -21,7 +21,7 @@ // ESP32C3/S3 I2S is not supported yet due to significant changes to interface #ifndef CONFIG_SOC_RMT_TX_CANDIDATES_PER_GROUP // turn this off with something new from idf5.1 -#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) && !defined(CONFIG_IDF_TARGET_ESP32C2) +#if !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) && !defined(CONFIG_IDF_TARGET_ESP32C2) #include #include @@ -113,7 +113,7 @@ typedef struct { static uint8_t i2s_silence_buf[I2S_DMA_SILENCE_SIZE] = { 0 }; -#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) +#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32C3) // (I2S_NUM_MAX == 2) static i2s_bus_t I2S[I2S_NUM_MAX] = { {&I2S0, -1, -1, -1, -1, 0, NULL, NULL, i2s_silence_buf, I2S_DMA_SILENCE_SIZE, NULL, I2S_DMA_BLOCK_COUNT_DEFAULT, 0, 0, I2s_Is_Idle}, @@ -187,7 +187,7 @@ esp_err_t i2sSetClock(uint8_t bus_num, uint8_t div_num, uint8_t div_b, uint8_t d typeof(i2s->clkm_conf) clkm_conf; clkm_conf.val = 0; -#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) +#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32C3) clkm_conf.clka_en = 0; #else clkm_conf.clk_sel = 2; @@ -222,7 +222,7 @@ void i2sSetPins(uint8_t bus_num, int8_t out, bool invert) { pinMode(out, OUTPUT); int i2sSignal; -#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) +#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32C3) // (I2S_NUM_MAX == 2) if (bus_num == 1) { i2sSignal = I2S1O_DATA_OUT23_IDX; @@ -268,7 +268,7 @@ void i2sInit(uint8_t bus_num, return; } -#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) +#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32C3) // (I2S_NUM_MAX == 2) if (bus_num) { periph_module_enable(PERIPH_I2S1_MODULE); @@ -310,7 +310,7 @@ void i2sInit(uint8_t bus_num, lc_conf.out_eof_mode = 1; i2s->lc_conf.val = lc_conf.val; -#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) +#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32C3) i2s->pdm_conf.pcm2pdm_conv_en = 0; i2s->pdm_conf.pdm2pcm_conv_en = 0; #endif @@ -341,7 +341,7 @@ void i2sInit(uint8_t bus_num, i2s->fifo_conf.tx_fifo_mod_force_en = 1; -#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) +#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32C3) i2s->pdm_conf.rx_pdm_en = 0; i2s->pdm_conf.tx_pdm_en = 0; #endif @@ -351,7 +351,7 @@ void i2sInit(uint8_t bus_num, // enable intr in cpu // int i2sIntSource; -#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) +#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32C3) // (I2S_NUM_MAX == 2) if (bus_num == 1) { i2sIntSource = ETS_I2S1_INTR_SOURCE; diff --git a/lib/NeoPixelBus/src/internal/Esp32_i2s.h b/lib/NeoPixelBus/src/internal/Esp32_i2s.h index bfc024a8c..b3df5def0 100644 --- a/lib/NeoPixelBus/src/internal/Esp32_i2s.h +++ b/lib/NeoPixelBus/src/internal/Esp32_i2s.h @@ -4,7 +4,7 @@ #ifndef CONFIG_SOC_RMT_TX_CANDIDATES_PER_GROUP // turn this off with something new from idf5.1 // ESP32C3 I2S is not supported yet due to significant changes to interface -#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C2) +#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C2) #ifdef __cplusplus extern "C" { diff --git a/lib/NeoPixelBus/src/internal/NeoBusChannel.h b/lib/NeoPixelBus/src/internal/NeoBusChannel.h index 5fdb131bf..c9cb9e64c 100644 --- a/lib/NeoPixelBus/src/internal/NeoBusChannel.h +++ b/lib/NeoPixelBus/src/internal/NeoBusChannel.h @@ -12,7 +12,7 @@ enum NeoBusChannel NeoBusChannel_0, NeoBusChannel_1, -#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C2) && !defined(CONFIG_IDF_TARGET_ESP32C6) +#if !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C2) && !defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(CONFIG_IDF_TARGET_ESP32C61) NeoBusChannel_2, @@ -35,7 +35,7 @@ enum NeoBusChannel NeoBusChannel_7, #endif // !defined(CONFIG_IDF_TARGET_ESP32S2) -#endif // !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C2) && !defined(CONFIG_IDF_TARGET_ESP32C6) +#endif #endif // ARDUINO_ARCH_ESP32 diff --git a/lib/NeoPixelBus/src/internal/NeoEsp32I2sMethod.h b/lib/NeoPixelBus/src/internal/NeoEsp32I2sMethod.h index 2afe6d5aa..374014261 100644 --- a/lib/NeoPixelBus/src/internal/NeoEsp32I2sMethod.h +++ b/lib/NeoPixelBus/src/internal/NeoEsp32I2sMethod.h @@ -28,7 +28,7 @@ License along with NeoPixel. If not, see #if ESP_IDF_VERSION_MAJOR <= 4 // ESP32C3 I2S is not supported yet due to significant changes to interface -#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) +#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32C3) extern "C" { @@ -305,7 +305,7 @@ typedef NeoEsp32I2sMethodBase NeoEsp32I2s0400KbpsInvertedMethod; typedef NeoEsp32I2sMethodBase NeoEsp32I2s0Apa106InvertedMethod; -#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) +#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32C3) // (I2S_NUM_MAX == 2) typedef NeoEsp32I2sMethodBase NeoEsp32I2s1Ws2812xMethod; @@ -347,7 +347,7 @@ typedef NeoEsp32I2sMethodBase -#if defined(ARDUINO_ARCH_ESP32) && ESP_IDF_VERSION_MAJOR < 5 && !defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(CONFIG_IDF_TARGET_ESP32C2) +#if defined(ARDUINO_ARCH_ESP32) && ESP_IDF_VERSION_MAJOR < 5 && !defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(CONFIG_IDF_TARGET_ESP32C61) && !defined(CONFIG_IDF_TARGET_ESP32C2) #include "NeoSettings.h" #include "NeoBusChannel.h" #include "NeoEsp32RmtMethod.h" diff --git a/lib/NeoPixelBus/src/internal/NeoEsp32RmtMethod.h b/lib/NeoPixelBus/src/internal/NeoEsp32RmtMethod.h index 3cc86d19a..4490ee3eb 100644 --- a/lib/NeoPixelBus/src/internal/NeoEsp32RmtMethod.h +++ b/lib/NeoPixelBus/src/internal/NeoEsp32RmtMethod.h @@ -29,7 +29,7 @@ License along with NeoPixel. If not, see #pragma once -#if defined(ARDUINO_ARCH_ESP32) && ESP_IDF_VERSION_MAJOR < 5 && !defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(CONFIG_IDF_TARGET_ESP32C2) +#if defined(ARDUINO_ARCH_ESP32) && ESP_IDF_VERSION_MAJOR < 5 && !defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(CONFIG_IDF_TARGET_ESP32C61) && !defined(CONFIG_IDF_TARGET_ESP32C2) /* General Reference documentation for the APIs used in this implementation LOW LEVEL: (what is actually used) @@ -454,7 +454,7 @@ public: const static rmt_channel_t RmtChannelNumber = RMT_CHANNEL_3; }; -#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C6) +#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(CONFIG_IDF_TARGET_ESP32C61) class NeoEsp32RmtChannel4 { @@ -658,7 +658,7 @@ typedef NeoEsp32RmtMethodBase NeoEs typedef NeoEsp32RmtMethodBase NeoEsp32Rmt1800KbpsMethod; typedef NeoEsp32RmtMethodBase NeoEsp32Rmt1400KbpsMethod; -#if !defined(CONFIG_IDF_TARGET_ESP32C3) +#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C5) typedef NeoEsp32RmtMethodBase NeoEsp32Rmt2Ws2811Method; typedef NeoEsp32RmtMethodBase NeoEsp32Rmt2Ws2812xMethod; @@ -765,7 +765,7 @@ typedef NeoEsp32RmtMethodBase NeoEsp32Rmt1800KbpsInvertedMethod; typedef NeoEsp32RmtMethodBase NeoEsp32Rmt1400KbpsInvertedMethod; -#if !defined(CONFIG_IDF_TARGET_ESP32C3) +#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C5) typedef NeoEsp32RmtMethodBase NeoEsp32Rmt2Ws2811InvertedMethod; typedef NeoEsp32RmtMethodBase NeoEsp32Rmt2Ws2812xInvertedMethod; @@ -839,13 +839,13 @@ typedef NeoEsp32RmtMethodBase NeoEs typedef NeoEsp32RmtMethodBase NeoEsp32Rmt1800KbpsMethod; typedef NeoEsp32RmtMethodBase NeoEsp32Rmt1400KbpsMethod; -#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C6) +#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(CONFIG_IDF_TARGET_ESP32C61) typedef NeoEsp32RmtMethodBase NeoEsp32Rmt2Ws2811Method; typedef NeoEsp32RmtMethodBase NeoEsp32Rmt2Ws2812xMethod; @@ -653,7 +653,7 @@ typedef NeoEsp32RmtMethodBase NeoE typedef NeoEsp32RmtMethodBase NeoEsp32Rmt7400KbpsMethod; #endif // !defined(CONFIG_IDF_TARGET_ESP32S2) -#endif // !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C3) +#endif // !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32C3) // inverted typedef NeoEsp32RmtMethodBase NeoEsp32RmtNWs2811InvertedMethod; @@ -689,7 +689,7 @@ typedef NeoEsp32RmtMethodBase NeoEsp32Rmt1800KbpsInvertedMethod; typedef NeoEsp32RmtMethodBase NeoEsp32Rmt1400KbpsInvertedMethod; -#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C6) +#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(CONFIG_IDF_TARGET_ESP32C61) typedef NeoEsp32RmtMethodBase NeoEsp32Rmt2Ws2811InvertedMethod; typedef NeoEsp32RmtMethodBase NeoEsp32Rmt2Ws2812xInvertedMethod; @@ -763,13 +763,13 @@ typedef NeoEsp32RmtMethodBase= 5 -#if (defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C2) || defined(CONFIG_IDF_TARGET_ESP32C6)) && !defined(HSPI_HOST) +#if (defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C2) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32C61)) && !defined(HSPI_HOST) // HSPI_HOST depreciated in C3 #define HSPI_HOST SPI2_HOST #endif diff --git a/lib/NeoPixelBus/src/internal/NeoEspBitBangMethod.cpp b/lib/NeoPixelBus/src/internal/NeoEspBitBangMethod.cpp index 01499fe13..6ca22c84f 100644 --- a/lib/NeoPixelBus/src/internal/NeoEspBitBangMethod.cpp +++ b/lib/NeoPixelBus/src/internal/NeoEspBitBangMethod.cpp @@ -29,7 +29,7 @@ License along with NeoPixel. If not, see #include // ESP32C3 I2S is not supported yet -#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C2) && !defined(CONFIG_IDF_TARGET_ESP32C6) +#if !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C2) && !defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(CONFIG_IDF_TARGET_ESP32C61) && !defined(CONFIG_IDF_TARGET_ESP32P4) #if !defined(ARDUINO_ARCH_ESP8266) #include "soc/gpio_periph.h" #endif @@ -74,7 +74,7 @@ void IRAM_ATTR NeoEspBitBangBase_send_pixels(uint8_t* pixels, uint8_t* end, uint // reset pin start #if defined(ARDUINO_ARCH_ESP32) - GPIO.out_w1tc = pinRegister; + GPIO.out_w1ts = pinRegister; #else GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, pinRegister); #endif diff --git a/lib/NeoPixelBus/src/internal/NeoEspBitBangMethod.h b/lib/NeoPixelBus/src/internal/NeoEspBitBangMethod.h index 44f865488..63068f974 100644 --- a/lib/NeoPixelBus/src/internal/NeoEspBitBangMethod.h +++ b/lib/NeoPixelBus/src/internal/NeoEspBitBangMethod.h @@ -29,7 +29,7 @@ License along with NeoPixel. If not, see #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) // ESP32C3 I2S is not supported yet -#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C2) && !defined(CONFIG_IDF_TARGET_ESP32C6) +#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C2) && !defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(CONFIG_IDF_TARGET_ESP32C61) #if defined(ARDUINO_ARCH_ESP8266) #include diff --git a/lib/PZEM-004T-v30-master/PZEM004Tv30.cpp b/lib/PZEM-004T-v30-master/PZEM004Tv30.cpp index fd834487f..13c500f40 100644 --- a/lib/PZEM-004T-v30-master/PZEM004Tv30.cpp +++ b/lib/PZEM-004T-v30-master/PZEM004Tv30.cpp @@ -14,6 +14,10 @@ #define REG_PF 0x0008 #define REG_ALARM 0x0009 +#define REG_CURRENT 0x0001 +#define REG_HVALARM 0x0006 +#define REG_LVALARM 0x0007 + #define CMD_RHR 0x03 #define CMD_RIR 0X04 #define CMD_WSR 0x06 @@ -24,6 +28,9 @@ #define WREG_ALARM_THR 0x0001 #define WREG_ADDR 0x0002 +#define WREG_HV_ALARM_THR 0x0000 +#define WREG_LV_ALARM_THR 0x0001 + #define UPDATE_TIME 200 #define RESPONSE_SIZE 32 @@ -84,6 +91,23 @@ PZEM004Tv30::~PZEM004Tv30() // delete this->_serial; } + +void PZEM004Tv30::setModel(PZEM_model model) { + if (model == _model) { + return; + } + _model = model; + switch (_model) { + case PZEM_model::PZEM004Tv30: + _expectedResponse = PZEM004Tv30_EXPECTED_RESPONSE; + _requestRegisters = PZEM004Tv30_REQUEST_REGISTERS; + break; + case PZEM_model::PZEM017Tv1: + _expectedResponse = PZEM017v1_EXPECTED_RESPONSE; + _requestRegisters = PZEM017v1_REQUEST_REGISTERS; + break; + } +} /*! * PZEM004Tv30::voltage * * Get line voltage in Volts * * @return current L-N volage*/ @@ -222,28 +246,97 @@ uint8_t PZEM004Tv30::getAddress() * Set power alarm threshold in watts * * @param[in] watts Alamr theshold * * @return success*/ -bool PZEM004Tv30::setPowerAlarm(uint16_t watts) -{ - if (watts > 25000){ // Sanitych check - watts = 25000; - } +// bool PZEM004Tv30::setPowerAlarm(uint16_t watts) +// { +// if (watts > 25000){ // Sanitych check +// watts = 25000; +// } - // Write the watts threshold to the Alarm register - if(!sendCmd8(CMD_WSR, WREG_ALARM_THR, watts, true)) - return false; - return true; -} +// // Write the watts threshold to the Alarm register +// if(!sendCmd8(CMD_WSR, WREG_ALARM_THR, watts, true)) +// return false; +// return true; +// } /*! * PZEM004Tv30::getPowerAlarm * * Is the power alarm set * * * @return arlam triggerd*/ -bool PZEM004Tv30::getPowerAlarm() -{ - if(!updateValues()) // Update vales if necessary - return NAN; // Update did not work, return NAN +// bool PZEM004Tv30::getPowerAlarm() +// { +// if(!updateValues()) // Update vales if necessary +// return NAN; // Update did not work, return NAN + +// return _currentValues.alarms != 0x0000; +// } + + /*! + * PZEM004Tv30::setHighVoltAlarm + * Set HV alarm threshold in volts + * @param[in] volt Alarm theshold + * @return success */ +// bool PZEM004Tv30::setHighvoltAlarm(uint16_t volts) +// { +// if (volts < 500){ // Sanity check +// volts = 500; +// } + +// if (volts > 34999){ // Sanity check +// volts = 34999; +// } + +// // Write the volts threshold to the alarm register +// if(!sendCmd8(CMD_WSR, WREG_HV_ALARM_THR, volts, true)) +// return false; + +// return true; +// } + +/*! + * PZEM004Tv30::setLowVoltAlarm + * Set LV alarm threshold in volts + * @param[in] volt Alarm theshold + * @return success */ +// bool PZEM004Tv30::setLowvoltAlarm(uint16_t volts) +// { +// if (volts < 100){ // Sanity check +// volts = 100; +// } + +// if (volts > 34999){ // Sanity check +// volts = 34999; +// } + +// // Write the volts threshold to the alarm register +// if(!sendCmd8(CMD_WSR, WREG_LV_ALARM_THR, volts, true)) +// return false; + +// return true; +// } + +/*! + * PZEM004Tv30::isHighVoltAlarmOn GET + * Is the HV alarm set + * @return alarm triggerd*/ +// bool PZEM004Tv30::isHighvoltAlarmOn() +// { +// if(!updateValues()) // Update vales if necessary +// return NAN; // Update did not work, return NAN + +// return _currentValues.HVAlarms != 0x0000; +// } + +/*! + * PZEM004Tv30::isLowVoltAlarmOn GET + * Is the LV alarm set + * @return alarm triggerd*/ +// bool PZEM004Tv30::isLowvoltAlarmOn() +// { +// if(!updateValues()) // Update vales if necessary +// return NAN; // Update did not work, return NAN + +// return _currentValues.LVAlarms != 0x0000; +// } - return _currentValues.alarms != 0x0000; -} /*! * PZEM004Tv30::init * * initialization common to all consturctors * @@ -266,52 +359,75 @@ void PZEM004Tv30::init(uint8_t addr){ * @return success*/ bool PZEM004Tv30::updateValues() { - //static uint8_t buffer[] = {0x00, CMD_RIR, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00}; - static uint8_t response[25]; - // If we read before the update time limit, do not update if(_lastRead + UPDATE_TIME > millis()){ return true; } - // Read 10 registers starting at 0x00 (no check) - sendCmd8(CMD_RIR, 0x00, 0x0A, false); + // Read registers starting at 0x00 (no check) + sendCmd8(CMD_RIR, 0x00, _requestRegisters, false); - if(recieve(response, 25) != 25){ // Something went wrong + if(recieve(_response, _expectedResponse) != _expectedResponse){ // Something went wrong return false; } // Update the current values - _currentValues.voltage = ((uint32_t)response[3] << 8 | // Raw voltage in 0.1V - (uint32_t)response[4])/10.0; + switch(_model) { + case PZEM_model::PZEM004Tv30: + _currentValues.voltage = ((uint32_t)_response[3] << 8 | // Raw voltage in 0.1V + (uint32_t)_response[4]) / 10.0; - _currentValues.current = ((uint32_t)response[5] << 8 | // Raw current in 0.001A - (uint32_t)response[6] | - (uint32_t)response[7] << 24 | - (uint32_t)response[8] << 16) / 1000.0; + _currentValues.current = ((uint32_t)_response[5] << 8 | // Raw current in 0.001A + (uint32_t)_response[6] | + (uint32_t)_response[7] << 24 | + (uint32_t)_response[8] << 16) / 1000.0; - _currentValues.power = ((uint32_t)response[9] << 8 | // Raw power in 0.1W - (uint32_t)response[10] | - (uint32_t)response[11] << 24 | - (uint32_t)response[12] << 16) / 10.0; + _currentValues.power = ((uint32_t)_response[9] << 8 | // Raw power in 0.1W + (uint32_t)_response[10] | + (uint32_t)_response[11] << 24 | + (uint32_t)_response[12] << 16) / 10.0; - _currentValues.energy = ((uint32_t)response[13] << 8 | // Raw Energy in 1Wh - (uint32_t)response[14] | - (uint32_t)response[15] << 24 | - (uint32_t)response[16] << 16) / 1000.0; + _currentValues.energy = ((uint32_t)_response[13] << 8 | // Raw Energy in 1Wh + (uint32_t)_response[14] | + (uint32_t)_response[15] << 24 | + (uint32_t)_response[16] << 16) / 1000.0; - _currentValues.frequeny =((uint32_t)response[17] << 8 | // Raw Frequency in 0.1Hz - (uint32_t)response[18]) / 10.0; + _currentValues.frequeny =((uint32_t)_response[17] << 8 | // Raw Frequency in 0.1Hz + (uint32_t)_response[18]) / 10.0; - _currentValues.pf = ((uint32_t)response[19] << 8 | // Raw pf in 0.01 - (uint32_t)response[20])/100.0; + _currentValues.pf = ((uint32_t)_response[19] << 8 | // Raw pf in 0.01 + (uint32_t)_response[20]) / 100.0; - _currentValues.alarms = ((uint32_t)response[21] << 8 | // Raw alarm value - (uint32_t)response[22]); + _currentValues.alarms = ((uint32_t)_response[21] << 8 | // Raw alarm value + (uint32_t)_response[22]); + break; + case PZEM_model::PZEM017Tv1: + _currentValues.voltage = ((uint32_t)_response[3] << 8 | // Raw voltage in 0.01V + (uint32_t)_response[4]) / 100.0; + _currentValues.current = ((uint32_t)_response[5] << 8 | // Raw voltage in 0.01A + (uint32_t)_response[6]) / 100.0; + + _currentValues.power = ((uint32_t)_response[7] << 8 | // Raw power in 0.1W + (uint32_t)_response[8] | + (uint32_t)_response[9] << 24 | + (uint32_t)_response[10] << 16) / 10.0; + + _currentValues.energy = ((uint32_t)_response[11] << 8 | // Raw Energy in 1Wh + (uint32_t)_response[12] | + (uint32_t)_response[13] << 24 | + (uint32_t)_response[14] << 16) / 1000.0; + + _currentValues.HVAlarms = ((uint32_t)_response[15] << 8 | // Raw alarm value + (uint32_t)_response[16]); + + _currentValues.LVAlarms = ((uint32_t)_response[17] << 8 | // Raw alarm value + (uint32_t)_response[18]); + break; + } // Record current time as _lastRead _lastRead = millis(); diff --git a/lib/PZEM-004T-v30-master/PZEM004Tv30.h b/lib/PZEM-004T-v30-master/PZEM004Tv30.h index 6579b0418..c5a65e9c3 100644 --- a/lib/PZEM-004T-v30-master/PZEM004Tv30.h +++ b/lib/PZEM-004T-v30-master/PZEM004Tv30.h @@ -28,9 +28,20 @@ #include #endif +#define PZEM004Tv30_EXPECTED_RESPONSE 25 +#define PZEM004Tv30_REQUEST_REGISTERS 10 + +#define PZEM017v1_EXPECTED_RESPONSE 21 +#define PZEM017v1_REQUEST_REGISTERS 8 #define PZEM_DEFAULT_ADDR 0xF8 +#define PZEM_MAX_EXPECTED_RESPONSE 25 // PZEM004v30 needs this buffer size, adjust for models that need a bigger buffer + +enum class PZEM_model : uint8_t { + PZEM004Tv30 = 0, // Buffer size 25, request 10 registers + PZEM017Tv1 = 1, // Buffer size 21, request 8 registers +}; class PZEM004Tv30 { @@ -40,6 +51,7 @@ public: #endif ~PZEM004Tv30(); + void setModel(PZEM_model model); // Sets some communication buffer params float voltage(); float current(); @@ -52,8 +64,14 @@ public: bool setAddress(uint8_t addr); uint8_t getAddress(); - bool setPowerAlarm(uint16_t watts); - bool getPowerAlarm(); + // Unused methods: + // bool setPowerAlarm(uint16_t watts); + // bool getPowerAlarm(); + + // bool setHighvoltAlarm(uint16_t volts); //moja uprava + // bool setLowvoltAlarm(uint16_t volts); //moja uprava + // bool isHighvoltAlarmOn(); //upravil som + // bool isLowvoltAlarmOn(); //upravil som bool resetEnergy(); void init(uint8_t addr); // Init common to all constructors @@ -64,15 +82,22 @@ private: bool _isSoft; // Is serial interface software uint8_t _addr; // Device address + PZEM_model _model = PZEM_model::PZEM004Tv30; // Current default + + uint8_t _response[PZEM_MAX_EXPECTED_RESPONSE]; + uint8_t _expectedResponse = PZEM004Tv30_EXPECTED_RESPONSE; + uint8_t _requestRegisters = PZEM004Tv30_REQUEST_REGISTERS; struct { float voltage; float current; float power; float energy; - float frequeny; - float pf; - uint16_t alarms; + float frequeny; // PZEM004 + float pf; // PZEM004 + uint16_t alarms; // PZEM004 + uint16_t HVAlarms; //upravil som PZEM017 + uint16_t LVAlarms; //upravil sOM PZEM017 } _currentValues; // Measured values uint64_t _lastRead; // Last time values were updated diff --git a/lib/SDM_Energy_Meter/SDM.cpp b/lib/SDM_Energy_Meter/SDM.cpp index 8da79aebe..f98b8e2aa 100644 --- a/lib/SDM_Energy_Meter/SDM.cpp +++ b/lib/SDM_Energy_Meter/SDM.cpp @@ -104,19 +104,20 @@ void SDM::startReadVal(uint16_t reg, uint8_t node, uint8_t functionCode) { uint16_t SDM::readValReady(uint8_t node, uint8_t functionCode) { uint16_t readErr = SDM_ERR_NO_ERROR; - if (sdmSer.available() < FRAMESIZE && ((millis() - resptime) < msturnaround)) + int available = sdmSer.available(); + if (available < FRAMESIZE && ((millis() - resptime) < msturnaround)) { return SDM_ERR_STILL_WAITING; } - while (sdmSer.available() < FRAMESIZE) { + while (available < FRAMESIZE) { if ((millis() - resptime) > msturnaround) { readErr = SDM_ERR_TIMEOUT; //err debug (4) - if (sdmSer.available() == 5) { - for(int n=0; n<5; n++) { - sdmarr[n] = sdmSer.read(); - } + if (available == 5) { + memset(sdmarr, 0, NR_ELEMENTS(sdmarr)); + sdmSer.read(sdmarr, available); + if (validChecksum(sdmarr, 5)) { readErr = sdmarr[2]; } @@ -124,15 +125,15 @@ uint16_t SDM::readValReady(uint8_t node, uint8_t functionCode) { break; } delay(1); + available = sdmSer.available(); } if (readErr == SDM_ERR_NO_ERROR) { //if no timeout... - if (sdmSer.available() >= FRAMESIZE) { + if (available >= FRAMESIZE) { - for(int n=0; n= b, else 0 */ +static inline uint32_t ge_ct(const uint32_t *a, const uint32_t *b) { + uint32_t gt = 0, eq = 1; + for (int i = WORDS - 1; i >= 0; i--) { + uint32_t ai = a[i], bi = b[i]; + uint32_t gt_i = (ai > bi); + uint32_t lt_i = (ai < bi); + gt |= (eq & gt_i); + eq &= ~(gt_i | lt_i); + } + return gt | eq; +} + +static inline void add_mod(uint32_t *d, const uint32_t *a, const uint32_t *b) { + uint64_t c = 0; + for (int i = 0; i < WORDS; i++) { + c = (uint64_t)a[i] + b[i] + (c >> 32); + d[i] = (uint32_t)c; + } + uint32_t need_sub = (uint32_t)(c >> 32); + need_sub |= ge_ct(d, P_LE); + uint32_t borrow = 0, tmp[WORDS]; + for (int i = 0; i < WORDS; i++) { + uint64_t t = (uint64_t)d[i] - P_LE[i] - borrow; + tmp[i] = (uint32_t)t; + borrow = (uint32_t)(t >> 63); + } + for (int i = 0; i < WORDS; i++) d[i] = need_sub ? tmp[i] : d[i]; +} + +static inline void sub_mod(uint32_t *d, const uint32_t *a, const uint32_t *b) { + uint32_t borrow = 0; + for (int i = 0; i < WORDS; i++) { + uint64_t t = (uint64_t)a[i] - b[i] - borrow; + d[i] = (uint32_t)t; + borrow = (uint32_t)(t >> 63); + } + uint64_t c = 0, tmp[WORDS]; + for (int i = 0; i < WORDS; i++) { + c = (uint64_t)d[i] + P_LE[i] + (c >> 32); + tmp[i] = (uint32_t)c; + } + for (int i = 0; i < WORDS; i++) d[i] = borrow ? tmp[i] : d[i]; +} + +static inline void field_mul(uint32_t *dst, const uint32_t *a, const uint32_t *b) { + ets_bigint_enable(); + ets_bigint_modmult(a, b, P_LE, MPRIME, RR_LE, WORDS); + ets_bigint_wait_finish(); + ets_bigint_getz(dst, WORDS); + ets_bigint_disable(); +} + +static inline void field_sqr(uint32_t *dst, const uint32_t *a) { + field_mul(dst, a, a); +} + +/* Fermat inversion: a^(p-2) in normal domain */ +static void field_inv(uint32_t *out, const uint32_t *a) { + static const uint32_t EXP_P_MINUS_2[WORDS] = { + 0xFFFFFFEB, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x7FFFFFFF + }; + uint32_t res[WORDS], base[WORDS]; + zclear(res); res[0] = 1; + zcopy(base, a); + for (int wi = WORDS - 1; wi >= 0; wi--) { + uint32_t w = EXP_P_MINUS_2[wi]; + for (int b = 31; b >= 0; b--) { + field_sqr(res, res); + if ((w >> b) & 1U) field_mul(res, res, base); + } + } + zcopy(out, res); +} + +/* Conditional swap */ +static inline void cswap(uint32_t *a, uint32_t *b, uint32_t ctl) { + uint32_t mask = -ctl; + for (int i = 0; i < WORDS; i++) { + uint32_t t = (a[i] ^ b[i]) & mask; + a[i] ^= t; b[i] ^= t; + } +} + +/* ---------- X25519 ladder (normal domain) ---------- */ + +static const unsigned char GEN[] PROGMEM = { + 0x09, 0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, + 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 +}; + +static const unsigned char ORDER[] PROGMEM = { + 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +static const unsigned char * +api_generator(int curve, size_t *len) +{ + (void)curve; + *len = 32; + return GEN; +} + +static const unsigned char * +api_order(int curve, size_t *len) +{ + (void)curve; + *len = 32; + return ORDER; +} + +static size_t +api_xoff(int curve, size_t *len) +{ + (void)curve; + *len = 32; + return 0; +} + +static uint32_t +api_mul(unsigned char *G, size_t Glen, + const unsigned char *kb, size_t kblen, int curve) +{ + (void)curve; + + if (Glen != 32 || kblen > 32) { + return 0; + } + + /* Clamp scalar per RFC 7748 */ + unsigned char k[32]; + memset(k, 0, 32 - kblen); + memcpy(k + (32 - kblen), kb, kblen); + k[31] &= 0xF8; + k[0] &= 0x7F; + k[0] |= 0x40; + + /* Load u and clear high bit per RFC 7748 */ + unsigned char u_bytes[32]; + memcpy(u_bytes, G, 32); + u_bytes[31] &= 0x7F; + + uint32_t x1[WORDS], x2[WORDS], z2[WORDS], x3[WORDS], z3[WORDS]; + br_range_dec32le(x1, WORDS, u_bytes); + + /* Initialize: + * (x2:z2) = (1:0) + * (x3:z3) = (u:1) + */ + zclear(z2); + zclear(x2); x2[0] = 1; + zcopy(x3, x1); + zclear(z3); z3[0] = 1; + + uint32_t a[WORDS], aa[WORDS], b[WORDS], bb[WORDS]; + uint32_t c[WORDS], d[WORDS], e[WORDS], da[WORDS], cb[WORDS]; + uint32_t t[WORDS]; + + uint32_t swap = 0; + for (int i = 254; i >= 0; i--) { + uint32_t kt = (k[31 - (i >> 3)] >> (i & 7)) & 1U; + swap ^= kt; + cswap(x2, x3, swap); + cswap(z2, z3, swap); + swap = kt; + + /* Ladder step */ + add_mod(a, x2, z2); /* a = x2 + z2 */ + sub_mod(b, x2, z2); /* b = x2 - z2 */ + field_sqr(aa, a); /* aa = a^2 */ + field_sqr(bb, b); /* bb = b^2 */ + sub_mod(e, aa, bb); /* e = aa - bb */ + + add_mod(c, x3, z3); /* c = x3 + z3 */ + sub_mod(d, x3, z3); /* d = x3 - z3 */ + field_mul(da, d, a); /* da = d * a */ + field_mul(cb, c, b); /* cb = c * b */ + + add_mod(x3, da, cb); /* x3 = (da + cb)^2 */ + field_sqr(x3, x3); + sub_mod(z3, da, cb); /* z3 = (da - cb)^2 * x1 */ + field_sqr(z3, z3); + field_mul(z3, z3, x1); + + field_mul(x2, aa, bb); /* x2 = aa * bb */ + + /* z2 = e * (aa + A24 * e) */ + field_mul(t, A24_LE, e); /* t = A24 * e */ + add_mod(t, t, aa); /* t = aa + A24*e */ + field_mul(z2, e, t); /* z2 = e * t */ + } + + cswap(x2, x3, swap); + cswap(z2, z3, swap); + + /* u = x2 / z2 */ + uint32_t z2i[WORDS], unorm[WORDS]; + field_inv(z2i, z2); + field_mul(unorm, x2, z2i); + + /* Final reduction if needed and serialize */ + if (ge_ct(unorm, P_LE)) { + sub_mod(unorm, unorm, P_LE); + } + br_range_enc32le(G, unorm, WORDS); + return 1; +} + +static size_t +api_mulgen(unsigned char *R, + const unsigned char *x, size_t xlen, int curve) +{ + const unsigned char *G0; + size_t Glen; + + G0 = api_generator(curve, &Glen); + memcpy_P(R, G0, Glen); + api_mul(R, Glen, x, xlen, curve); + return Glen; +} + +static uint32_t +api_muladd(unsigned char *A, const unsigned char *B, size_t len, + const unsigned char *x, size_t xlen, + const unsigned char *y, size_t ylen, int curve) +{ + (void)A; (void)B; (void)len; (void)x; (void)xlen; (void)y; (void)ylen; (void)curve; + /* Not applicable for Curve25519 (no ECDSA). */ + return 0; +} + +/* see bearssl_ec.h */ +const br_ec_impl br_ec_c25519_m15 PROGMEM = { + (uint32_t)0x20000000, + &api_generator, + &api_order, + &api_xoff, + &api_mul, + &api_mulgen, + &api_muladd +}; + +#endif /* SOC_MPI_SUPPORTED */ +#endif /* ESP_PLATFORM && !ESP8266 */ +#endif /* USE_SHA_ROM */ diff --git a/lib/lib_ssl/bearssl-esp8266/src/ec/_ec_p256_m15.c b/lib/lib_ssl/bearssl-esp8266/src/ec/_ec_p256_m15.c new file mode 100644 index 000000000..69d4f519f --- /dev/null +++ b/lib/lib_ssl/bearssl-esp8266/src/ec/_ec_p256_m15.c @@ -0,0 +1,468 @@ +/* + * _ec_p256_m15.c — BearSSL P-256 implementation using ESP32 ROM-backed Montgomery arithmetic + * + * This file provides a fast elliptic curve implementation for secp256r1 (P-256), + * leveraging the ESP32's ROM bigint accelerator for modular multiplication. + * + * Key features: + * - Field arithmetic in normal domain using Montgomery-backed multiply/square. + * - Jacobian point representation with full group law (point add/double). + * - Scalar multiplication via double-and-add, supporting arbitrary base points. + * - Conversion between affine and Jacobian coordinates. + * - Compact encoding/decoding of uncompressed points (04 || X || Y). + * - Fully compatible with BearSSL's ec_impl API. + * + * All field elements are stored as 8×32-bit little-endian limbs. + * Internal operations avoid heap allocation and use fixed-size buffers. + * + * Requires: ESP32 platform with SOC_MPI_SUPPORTED enabled. + * + * Author: Christian Baars + */ + +#if defined(USE_SHA_ROM) +#if defined(ESP_PLATFORM) && !defined(ESP8266) && !defined(CONFIG_IDF_TARGET_ESP32) + +#if __has_include("soc/sha_caps.h") +# include "soc/sha_caps.h" +#elif __has_include("soc/soc_caps.h") +# include "soc/soc_caps.h" +#else +# error "No ESP capability header found" +#endif + +#if SOC_MPI_SUPPORTED + +#include +#include "rom/bigint.h" +#include "t_inner.h" + +#define WORDS 8 + +/* ESP32 ROM Montgomery parameters (little-endian).*/ +static const uint32_t P_LE[8] = { + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, + 0x00000000, 0x00000000, 0x00000001, 0xFFFFFFFF +}; + +static const uint32_t RR_LE[8] = { + 0x03000000, 0x00000000, 0xFFFFFFFF, 0xFBFFFFFF, + 0xFEFFFFFF, 0xFFFFFFFF, 0xFDFFFFFF, 0x04000000 +}; + +/* -p^{-1} mod 2^32 */ +static const uint32_t MPRIME = 0x00000001; + +/* Factor to convert ROM Montgomery output back to normal domain (8 limbs) */ +static const uint32_t CINV2_LE[8] = { + 0xB15F7DC9, 0x21BC7192, 0xF82DEBEB, 0xF2086906, + 0x8AD3BB54, 0xE34453E4, 0xB2B4EF16, 0x5FF55809 +}; + +/* Generator point G in little-endian 32-bit limbs (LSW first) */ +static const uint32_t Gx[WORDS] = { + 0xD898C296, 0xF4A13945, 0x2DEB33A0, 0x77037D81, + 0x63A440F2, 0xF8BCE6E5, 0xE12C4247, 0x6B17D1F2 +}; + +static const uint32_t Gy[WORDS] = { + 0x37BF51F5, 0xCBB64068, 0x6B315ECE, 0x2BCE3357, + 0x7C0F9E16, 0x8EE7EB4A, 0xFE1A7F9B, 0x4FE342E2 +}; + +typedef struct { + uint32_t X[WORDS]; + uint32_t Y[WORDS]; + uint32_t Z[WORDS]; +} p256_pt; + +/* ---------- small utilities ---------- */ + +static inline void zclear(uint32_t *dst) { + memset(dst, 0, WORDS * sizeof(uint32_t)); +} + +static inline void zcopy(uint32_t *dst, const uint32_t *src) { + memcpy(dst, src, WORDS * sizeof(uint32_t)); +} + +static inline int is_zero(const uint32_t *a) { + uint32_t acc = 0; + for (int i = 0; i < WORDS; i++) acc |= a[i]; + return acc == 0; +} + +/* big-endian bytes -> internal little-endian limbs (reverse word order) */ +static void be32_to_le32(const uint8_t *src, uint32_t *dst) { + for (int i = 0; i < WORDS; i++) { + int j = WORDS - 1 - i; + dst[i] = ((uint32_t)src[4*j] << 24) + | ((uint32_t)src[4*j + 1] << 16) + | ((uint32_t)src[4*j + 2] << 8) + | ((uint32_t)src[4*j + 3]); + } +} + +/* internal little-endian limbs -> big-endian bytes (reverse word order) */ +static void le32_to_be32(const uint32_t *src, uint8_t *dst) { + for (int i = 0; i < WORDS; i++) { + int j = WORDS - 1 - i; + dst[4*j] = (uint8_t)(src[i] >> 24); + dst[4*j + 1] = (uint8_t)(src[i] >> 16); + dst[4*j + 2] = (uint8_t)(src[i] >> 8); + dst[4*j + 3] = (uint8_t)(src[i]); + } +} + +/* ---------- field arithmetic modulo p (normal domain) ---------- */ + +static inline const uint32_t *Pmod(void) { return P_LE; } + +static int ge_mod_p(const uint32_t *a) { + const uint32_t *P = Pmod(); + for (int i = WORDS - 1; i >= 0; i--) { + if (a[i] > P[i]) return 1; + if (a[i] < P[i]) return 0; + } + return 1; /* equal */ +} + +static void field_add_mod(uint32_t *dst, const uint32_t *a, const uint32_t *b) { + const uint32_t *P = Pmod(); + uint64_t carry = 0; + for (int i = 0; i < WORDS; i++) { + uint64_t sum = (uint64_t)a[i] + b[i] + carry; + dst[i] = (uint32_t)sum; + carry = sum >> 32; + } + if (carry || ge_mod_p(dst)) { + uint64_t borrow = 0; + for (int i = 0; i < WORDS; i++) { + uint64_t diff = (uint64_t)dst[i] - P[i] - borrow; + dst[i] = (uint32_t)diff; + borrow = (diff >> 63) & 1; + } + } +} + +static void field_sub_mod(uint32_t *dst, const uint32_t *a, const uint32_t *b) { + const uint32_t *P = Pmod(); + uint64_t borrow = 0; + for (int i = 0; i < WORDS; i++) { + uint64_t diff = (uint64_t)a[i] - b[i] - borrow; + dst[i] = (uint32_t)diff; + borrow = (diff >> 63) & 1; + } + if (borrow) { + uint64_t carry = 0; + for (int i = 0; i < WORDS; i++) { + uint64_t sum = (uint64_t)dst[i] + P[i] + carry; + dst[i] = (uint32_t)sum; + carry = sum >> 32; + } + } +} + +/* ROM-backed modular multiply returning normal-domain result (8 limbs) */ +static void rom_field_mul(uint32_t *dst, const uint32_t *a, const uint32_t *b) { + uint32_t tmp[WORDS]; + + ets_bigint_enable(); + + /* Montgomery multiply in ROM (returns Montgomery residue) */ + ets_bigint_modmult(a, b, P_LE, MPRIME, RR_LE, WORDS); + ets_bigint_wait_finish(); + ets_bigint_getz(tmp, WORDS); + + /* Convert out of Montgomery domain using the proven CINV2_LE */ + ets_bigint_modmult(tmp, CINV2_LE, P_LE, MPRIME, RR_LE, WORDS); + ets_bigint_wait_finish(); + ets_bigint_getz(dst, WORDS); + + ets_bigint_disable(); +} + +static inline void field_mul(uint32_t *dst, const uint32_t *a, const uint32_t *b) { + rom_field_mul(dst, a, b); +} + +static inline void field_sqr(uint32_t *dst, const uint32_t *a) { + rom_field_mul(dst, a, a); +} + +/* Square-and-multiply exponentiation for p-2 (normal domain throughout) */ +static void field_inv(uint32_t *out, const uint32_t *a) { + static const uint32_t EXP_P_MINUS_2[WORDS] = { + 0xFFFFFFFD, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, + 0x00000000, 0x00000000, 0x00000001, 0xFFFFFFFF + }; + uint32_t res[WORDS], base[WORDS]; + + zclear(res); res[0] = 1; /* res = 1 */ + zcopy(base, a); /* base = a */ + + for (int wi = WORDS - 1; wi >= 0; wi--) { + uint32_t w = EXP_P_MINUS_2[wi]; + for (int b = 31; b >= 0; b--) { + field_sqr(res, res); + if ((w >> b) & 1U) { + field_mul(res, res, base); + } + } + } + zcopy(out, res); +} + +/* ---------- point utilities ---------- */ + +static inline void load_generator(p256_pt *Pp) { + zcopy(Pp->X, Gx); + zcopy(Pp->Y, Gy); + zclear(Pp->Z); + Pp->Z[0] = 1; +} + +static void to_affine(p256_pt *Pp) { + uint32_t zi[WORDS], zi2[WORDS], xi[WORDS], yi[WORDS]; + uint32_t z0 = 0; + for (int i = 0; i < WORDS; i++) z0 |= Pp->Z[i]; + if (z0 == 0) { + zclear(Pp->X); zclear(Pp->Y); zclear(Pp->Z); + return; + } + field_inv(zi, Pp->Z); + field_sqr(zi2, zi); + field_mul(xi, Pp->X, zi2); + field_mul(yi, Pp->Y, zi2); + field_mul(yi, yi, zi); + zcopy(Pp->X, xi); + zcopy(Pp->Y, yi); + zclear(Pp->Z); Pp->Z[0] = 1; +} + +/* ---------- group law (Jacobian, a = -3) ---------- */ + +__attribute__((noinline)) +static void p256_point_double(p256_pt *Q) { + if (is_zero(Q->Y)) { zclear(Q->X); zclear(Q->Y); zclear(Q->Z); return; } + + uint32_t Z2[WORDS], M[WORDS], Mtmp[WORDS], S[WORDS], T1[WORDS], T2[WORDS], X3[WORDS], Y3[WORDS]; + + /* Z2 = Z^2 */ + field_sqr(Z2, Q->Z); + + /* T1 = X - Z^2 ; T2 = X + Z^2 */ + field_sub_mod(T1, Q->X, Z2); + field_add_mod(T2, Q->X, Z2); + + /* M = (X - Z^2) * (X + Z^2) */ + field_mul(M, T1, T2); + + /* M = 3 * M */ + zcopy(Mtmp, M); + field_add_mod(M, M, M); /* 2*M */ + field_add_mod(M, M, Mtmp); /* 3*M */ + + /* S = 4 * X * Y^2 */ + field_sqr(S, Q->Y); /* Y^2 */ + field_mul(S, S, Q->X); /* X*Y^2 */ + field_add_mod(S, S, S); /* 2*X*Y^2 */ + field_add_mod(S, S, S); /* 4*X*Y^2 */ + + /* X3 = M^2 - 2*S */ + field_sqr(X3, M); + field_sub_mod(X3, X3, S); + field_sub_mod(X3, X3, S); + + /* Y3 = M*(S - X3) - 8*Y^4 */ + field_sub_mod(Y3, S, X3); + field_mul(Y3, Y3, M); + + field_sqr(T1, Q->Y); /* Y^2 */ + field_sqr(T1, T1); /* Y^4 */ + field_add_mod(T1, T1, T1); /* 2*Y^4 */ + field_add_mod(T1, T1, T1); /* 4*Y^4 */ + field_add_mod(T1, T1, T1); /* 8*Y^4 */ + + field_sub_mod(Y3, Y3, T1); + + /* Z3 = 2*Y*Z */ + field_mul(Q->Z, Q->Y, Q->Z); + field_add_mod(Q->Z, Q->Z, Q->Z); + + zcopy(Q->X, X3); + zcopy(Q->Y, Y3); +} + +__attribute__((noinline)) +static void p256_point_add(p256_pt *R, const p256_pt *Pp, const p256_pt *Qp) { + if (is_zero(Pp->Z)) { zcopy(R->X, Qp->X); zcopy(R->Y, Qp->Y); zcopy(R->Z, Qp->Z); return; } + if (is_zero(Qp->Z)) { zcopy(R->X, Pp->X); zcopy(R->Y, Pp->Y); zcopy(R->Z, Pp->Z); return; } + + uint32_t Z1Z1[WORDS], Z2Z2[WORDS], U1[WORDS], U2[WORDS]; + uint32_t S1[WORDS], S2[WORDS], H[WORDS], RR[WORDS]; + uint32_t H2[WORDS], H3[WORDS], U1H2[WORDS], X3[WORDS], Y3[WORDS], Z3[WORDS], t[WORDS]; + + field_sqr(Z1Z1, Pp->Z); + field_sqr(Z2Z2, Qp->Z); + + field_mul(U1, Pp->X, Z2Z2); + field_mul(U2, Qp->X, Z1Z1); + + field_mul(t, Qp->Z, Z2Z2); /* Z2^3 */ + field_mul(S1, Pp->Y, t); + + field_mul(t, Pp->Z, Z1Z1); /* Z1^3 */ + field_mul(S2, Qp->Y, t); + + field_sub_mod(H, U2, U1); + field_sub_mod(RR, S2, S1); + + if (is_zero(H)) { + if (is_zero(RR)) { + p256_pt D = *Pp; + p256_point_double(&D); + zcopy(R->X, D.X); + zcopy(R->Y, D.Y); + zcopy(R->Z, D.Z); + } else { + zclear(R->X); zclear(R->Y); zclear(R->Z); /* infinity */ + } + return; + } + + field_sqr(H2, H); + field_mul(H3, H, H2); + field_mul(U1H2, U1, H2); + + field_sqr(X3, RR); + field_sub_mod(X3, X3, H3); + field_sub_mod(X3, X3, U1H2); + field_sub_mod(X3, X3, U1H2); /* -2*U1H2 */ + + field_sub_mod(Y3, U1H2, X3); + field_mul(Y3, Y3, RR); + + field_mul(t, S1, H3); + field_sub_mod(Y3, Y3, t); + + field_mul(t, Pp->Z, Qp->Z); + field_mul(Z3, t, H); + + zcopy(R->X, X3); + zcopy(R->Y, Y3); + zcopy(R->Z, Z3); +} + +/* ---------- shared scalar multiply helpers (reduce duplication) ---------- */ + +static void scalar_mul_point(p256_pt *R, const p256_pt *Base, const uint8_t *k, size_t klen) { + zclear(R->X); zclear(R->Y); zclear(R->Z); /* R = O */ + for (size_t bi = 0; bi < klen * 8; bi++) { + p256_point_double(R); + if ((k[bi >> 3] >> (7 - (bi & 7))) & 1) { + p256_point_add(R, R, Base); + } + } +} + +/* Load uncompressed point (04 || X || Y) into Jacobian with Z=1 */ +static int load_point_uncompressed(p256_pt *Pp, const unsigned char *buf, size_t len) { + if (len != 65 || buf[0] != 0x04) return 0; + be32_to_le32(buf + 1, Pp->X); + be32_to_le32(buf + 33, Pp->Y); + zclear(Pp->Z); Pp->Z[0] = 1; + return 1; +} + +static void store_point_uncompressed(unsigned char *buf, const p256_pt *Pp) { + buf[0] = 0x04; + le32_to_be32(Pp->X, buf + 1); + le32_to_be32(Pp->Y, buf + 33); +} + +/* ---------- BearSSL ec_impl API ---------- */ + +static const unsigned char *api_generator(int curve, size_t *len) { + (void)curve; + *len = br_secp256r1.generator_len; + return br_secp256r1.generator; +} + +static const unsigned char *api_order(int curve, size_t *len) { + (void)curve; + *len = br_secp256r1.order_len; + return br_secp256r1.order; +} + +static size_t api_xoff(int curve, size_t *len) { + (void)curve; + *len = 32; + return 1; +} + +static uint32_t api_mul(unsigned char *G, size_t Glen, + const unsigned char *x, size_t xlen, + int curve) { + (void)curve; + p256_pt Pp, R; + if (!load_point_uncompressed(&Pp, G, Glen)) return 0; + + scalar_mul_point(&R, &Pp, x, xlen); + to_affine(&R); + store_point_uncompressed(G, &R); + return 1; +} + +static size_t api_mulgen(unsigned char *Rbuf, + const unsigned char *x, size_t xlen, + int curve) { + (void)curve; + p256_pt Gp, R; + load_generator(&Gp); + + scalar_mul_point(&R, &Gp, x, xlen); + to_affine(&R); + store_point_uncompressed(Rbuf, &R); + return 65; +} + +static uint32_t api_muladd(unsigned char *A, const unsigned char *B, + size_t Glen, + const unsigned char *x, size_t xlen, + const unsigned char *y, size_t ylen, + int curve) { + (void)curve; + p256_pt Pp, Qp, R, T; + + if (!load_point_uncompressed(&Pp, A, Glen)) return 0; + + scalar_mul_point(&R, &Pp, x, xlen); + + if (B) { + if (!load_point_uncompressed(&Qp, B, Glen)) return 0; + } else { + load_generator(&Qp); + } + + scalar_mul_point(&T, &Qp, y, ylen); + p256_point_add(&R, &R, &T); + + to_affine(&R); + store_point_uncompressed(A, &R); + return 1; +} + +const br_ec_impl br_ec_p256_m15 PROGMEM = { + (uint32_t)0x00800000, + &api_generator, + &api_order, + &api_xoff, + &api_mul, + &api_mulgen, + &api_muladd +}; + +#endif // SOC_MPI_SUPPORTED +#endif // defined(ESP_PLATFORM) && !defined(ESP8266) +#endif // USE_SHA_ROM \ No newline at end of file diff --git a/lib/lib_ssl/bearssl-esp8266/src/ec/ec_c25519_m15.c b/lib/lib_ssl/bearssl-esp8266/src/ec/ec_c25519_m15.c index 0d1041472..115d51a63 100644 --- a/lib/lib_ssl/bearssl-esp8266/src/ec/ec_c25519_m15.c +++ b/lib/lib_ssl/bearssl-esp8266/src/ec/ec_c25519_m15.c @@ -1475,4 +1475,4 @@ const br_ec_impl br_ec_c25519_m15 PROGMEM = { &api_mul, &api_mulgen, &api_muladd -}; +}; \ No newline at end of file diff --git a/lib/lib_ssl/bearssl-esp8266/src/hash/_sha_hal_idf5x.c b/lib/lib_ssl/bearssl-esp8266/src/hash/_sha_hal_idf5x.c new file mode 100644 index 000000000..1fc124f35 --- /dev/null +++ b/lib/lib_ssl/bearssl-esp8266/src/hash/_sha_hal_idf5x.c @@ -0,0 +1,754 @@ +/* + * _sha_hal_idf5x.c — BearSSL drop‑in with ESP32 HAL SHA acceleration + * + * This file provides hardware-accelerated implementations of BearSSL-compatible + * hash functions (SHA-1, SHA-224, SHA-256, SHA-384, SHA-512) using the ESP-IDF 5.x + * SHA HAL. It replaces the software digest core with direct access to the ESP32's + * SHA engine, preserving BearSSL's context structure and API semantics. + * + * Each hash context uses a minimal layout: + * - val[] holds the midstate as a raw little-endian byte image. + * - buf[] holds the partial block (64 or 128 bytes). + * - count tracks the total input length in bytes. + * + * Endianness is preserved across state() and set_state() calls, allowing seamless + * serialization and restoration of midstate snapshots. All conversions for hardware + * interaction are localized to update() and out(). + * + * This module is designed for drop-in replacement with no changes to BearSSL clients. + * It supports multihash, midstate injection, and digest resumption. + * + * Author: Christian Baars + */ + +#include "t_inner.h" + +#if defined(USE_SHA_ROM) +#if defined(ESP_PLATFORM) && !defined(ESP8266) + +#include + +#include "freertos/FreeRTOS.h" + +#if __has_include("soc/sha_caps.h") +# include "soc/sha_caps.h" +#elif __has_include("soc/soc_caps.h") +# include "soc/soc_caps.h" +#else +# error "No SHA capability header found" +#endif +#if SOC_SHA_SUPPORT_RESUME + +#if __has_include("hal/sha_ll.h") +# include "hal/sha_ll.h" +# define HAVE_SHA_LL 1 +#else +# define HAVE_SHA_LL 0 +#endif + + +#define HAVE_HAL_SHA1 (SOC_SHA_SUPPORT_SHA1) +#define HAVE_HAL_SHA224 (SOC_SHA_SUPPORT_SHA224) +#define HAVE_HAL_SHA256 (SOC_SHA_SUPPORT_SHA256) +#define HAVE_HAL_SHA384 (SOC_SHA_SUPPORT_SHA384) +#define HAVE_HAL_SHA512 (SOC_SHA_SUPPORT_SHA512) + +static portMUX_TYPE s_sha_mux = portMUX_INITIALIZER_UNLOCKED; +#define SHA_ENTER() {portENTER_CRITICAL(&s_sha_mux); int __DECLARE_RCC_ATOMIC_ENV; sha_ll_enable_bus_clock(true); sha_ll_reset_register();} +#define SHA_EXIT() {portEXIT_CRITICAL(&s_sha_mux); int __DECLARE_RCC_ATOMIC_ENV; sha_ll_enable_bus_clock(false);} +#define SHA_WAIT() {while (sha_ll_busy()) { } } + +/* ================================================================ + * SHA-1 (HAL path, no save/restore) - needs char buf[112]; + * ================================================================ */ +#if HAVE_HAL_SHA1 + +static void __attribute__((noinline)) +sha_hal_process_block(void *state_buf, const void *blk, + esp_sha_type type, size_t digest_words, + size_t block_words, bool first) +{ + SHA_ENTER(); + if (!first) { + sha_ll_write_digest(type, state_buf, digest_words); + } + sha_ll_fill_text_block(blk, block_words); + if (first) { + sha_ll_start_block(type); + } else { + sha_ll_continue_block(type); + } + SHA_WAIT(); + sha_ll_read_digest(type, state_buf, digest_words); + SHA_EXIT(); +} + +static const uint8_t S1_IV_BYTES[20] PROGMEM = { + 0x67,0x45,0x23,0x01, 0xEF,0xCD,0xAB,0x89, + 0x98,0xBA,0xDC,0xFE, 0x10,0x32,0x54,0x76, + 0xC3,0xD2,0xE1,0xF0 +}; + +void br_sha1_init(br_sha1_context *cc) +{ + cc->vtable = &br_sha1_vtable; + cc->count = 0; + memset(cc->buf, 0, sizeof cc->buf); + /* Store IV bytes exactly as given (LE words) into cc->val */ + memcpy(cc->val, S1_IV_BYTES, sizeof S1_IV_BYTES); +} + +void br_sha1_update(br_sha1_context *cc, const void *data, size_t len) +{ + const uint8_t *src = data; + size_t used = (size_t)(cc->count & 63U); + + if (!len) return; + cc->count += len; + + if (used) { + size_t take = 64 - used; + if (take > len) take = len; + memcpy(cc->buf + used, src, take); + src += take; + len -= take; + used += take; + if (used == 64) { + sha_hal_process_block(cc->val, cc->buf, SHA1, 5, 16, cc->count == 64); + used = 0; + } + } + while (len >= 64) { + sha_hal_process_block(cc->val, src, SHA1, 5, 16, + (cc->count - len == 0) && !used); + src += 64; + len -= 64; + } + if (len) { + memcpy(cc->buf, src, len); + } +} + +void br_sha1_out(const br_sha1_context *cc, void *out) +{ + br_sha1_context ctx = *cc; + size_t used = (size_t)(ctx.count & 63U); + uint64_t bit_len = (uint64_t)ctx.count << 3; + + ctx.buf[used++] = 0x80; + if (used > 56) { + memset(ctx.buf + used, 0, 64 - used); + sha_hal_process_block(ctx.val, ctx.buf, SHA1, 5, 16, ctx.count <= 64); + used = 0; + } + memset(ctx.buf + used, 0, 56 - used); + br_enc64be(ctx.buf + 56, bit_len); + sha_hal_process_block(ctx.val, ctx.buf, SHA1, 5, 16, ctx.count <= 64); + memcpy(out, ctx.val, 20); +} + +uint64_t br_sha1_state(const br_sha1_context *cc, void *dst) +{ + /* Export raw 20-byte LE midstate image exactly as stored */ + memcpy(dst, cc->val, 20); + return cc->count; +} + +void br_sha1_set_state(br_sha1_context *cc, const void *src, uint64_t count) +{ + if (!(cc->vtable == &br_sha1_vtable)) { + cc->vtable = &br_sha1_vtable; + } + /* Import raw 20-byte LE midstate image exactly as provided */ + memcpy(cc->val, src, 20); + cc->count = count; + memset(cc->buf, 0, sizeof cc->buf); +} + +const br_hash_class br_sha1_vtable PROGMEM = { + sizeof(br_sha1_context), + BR_HASHDESC_ID(br_sha1_ID) + | BR_HASHDESC_OUT(20) + | BR_HASHDESC_LBLEN(6) + | BR_HASHDESC_MD_PADDING + | BR_HASHDESC_MD_PADDING_BE + | BR_HASHDESC_STATE(32), + (void (*)(const br_hash_class **)) &br_sha1_init, + (void (*)(const br_hash_class **, const void *, size_t)) &br_sha1_update, + (void (*)(const br_hash_class *const *, void *)) &br_sha1_out, + (uint64_t (*)(const br_hash_class *const *, void *)) &br_sha1_state, + (void (*)(const br_hash_class **, const void *, uint64_t)) &br_sha1_set_state +}; + +#endif /* HAVE_HAL_SHA1 */ + +/* ================================================================ + * SHA-224 (HAL path, no save/restore) + * ================================================================ */ +#if HAVE_HAL_SHA224 + +/* SHA-224 IV as 32-bit words (FIPS 180-4) */ +static const uint32_t S224_IV_WORDS[8] = { + 0xc1059ed8U, 0x367cd507U, 0x3070dd17U, 0xf70e5939U, + 0xffc00b31U, 0x68581511U, 0x64f98fa7U, 0xbefa4fa4U +}; + +void br_sha224_init(br_sha224_context *cc) { + cc->vtable = &br_sha224_vtable; + cc->count = 0; + memset(cc->buf, 0, sizeof cc->buf); + /* Internal state is LE; encode IV directly in LE form into cc->val */ + br_range_enc32le(cc->val, S224_IV_WORDS, 8); +} + +/* Reuse SHA-256 out, then truncate to 28 bytes */ +void br_sha224_out(const br_sha224_context *cc, void *out) { + uint8_t full[32]; + br_sha256_out((const br_sha256_context *)cc, full); + memcpy(out, full, 28); +} + +const br_hash_class br_sha224_vtable PROGMEM = { + sizeof(br_sha224_context), + BR_HASHDESC_ID(br_sha224_ID) + | BR_HASHDESC_OUT(28) + | BR_HASHDESC_LBLEN(6) + | BR_HASHDESC_MD_PADDING + | BR_HASHDESC_MD_PADDING_BE + | BR_HASHDESC_STATE(32), + (void (*)(const br_hash_class **)) &br_sha224_init, + (void (*)(const br_hash_class **, const void *, size_t)) &br_sha224_update, + (void (*)(const br_hash_class *const *, void *)) &br_sha224_out, + (uint64_t (*)(const br_hash_class *const *, void *)) &br_sha224_state, + (void (*)(const br_hash_class **, const void *, uint64_t)) &br_sha224_set_state +}; + +#endif /* HAVE_HAL_SHA224 */ + +/* ================================================================ + * SHA-256 (HAL path, save/restore) + * ================================================================ */ +#if HAVE_HAL_SHA256 && defined(SOC_SHA_SUPPORT_RESUME) + +/* Fixed SHA-256 IV in big-endian bytes (spec-defined) */ +static const uint8_t S256_IV_BYTES[32] PROGMEM = { + 0x6a, 0x09, 0xe6, 0x67, 0xbb, 0x67, 0xae, 0x85, + 0x3c, 0x6e, 0xf3, 0x72, 0xa5, 0x4f, 0xf5, 0x3a, + 0x51, 0x0e, 0x52, 0x7f, 0x9b, 0x05, 0x68, 0x8c, + 0x1f, 0x83, 0xd9, 0xab, 0x5b, 0xe0, 0xcd, 0x19 +}; + +/* Maintain the partial block in cc->buf[] (64 bytes) */ +static inline void s256_update_partial(br_sha256_context *cc, + const uint8_t *data_ptr, + size_t data_len, + size_t prev_partial_len) +{ + size_t new_partial_len = (prev_partial_len + data_len) & 63U; + + if (new_partial_len == 0) { + /* Exact block boundary: clear partial buffer */ + memset(cc->buf, 0, sizeof cc->buf); + return; + } + + if (data_len >= new_partial_len) { + /* Tail entirely from current input */ + memcpy(cc->buf, data_ptr + (data_len - new_partial_len), new_partial_len); + } else { + /* Need some bytes from previous partial tail */ + size_t need_prev = new_partial_len - data_len; + if (need_prev && need_prev <= prev_partial_len) { + memmove(cc->buf, cc->buf + (prev_partial_len - need_prev), need_prev); + } else if (need_prev) { + memset(cc->buf, 0, need_prev); + } + memcpy(cc->buf + need_prev, data_ptr, data_len); + } + + if (new_partial_len < sizeof cc->buf) { + memset(cc->buf + new_partial_len, 0, sizeof cc->buf - new_partial_len); + } +} + +void br_sha256_init(br_sha256_context *cc) +{ + cc->vtable = &br_sha256_vtable; + cc->count = 0; + memset(cc->buf, 0, sizeof cc->buf); + /* Keep midstate bytes exactly as IV (BE byte image), as in the original */ + memcpy(cc->val, S256_IV_BYTES, 32); +} + +void br_sha224_update(br_sha224_context *cc, const void *data, size_t len) +{ + if (!len) return; + + /* Embedded mode selection by vtable pointer (no desc, no tags) */ + const int mode = (cc->vtable == &br_sha256_vtable) ? SHA2_256 : SHA2_224; + + const uint8_t *data_ptr = (const uint8_t *)data; + const uint8_t *orig_ptr = data_ptr; + size_t orig_len = len; + + size_t prev_partial_len = (size_t)(cc->count & 63U); + bool starting_from_iv = (cc->count == 0) && (prev_partial_len == 0); + size_t stitch_take = 0; + if (prev_partial_len && len >= (64 - prev_partial_len)) + stitch_take = 64 - prev_partial_len; + + if (stitch_take || len >= 64) { + size_t block_count_in_run = 0; + + SHA_ENTER(); + + /* Complete the partial block (stitch) if needed */ + if (stitch_take) { + uint32_t midstate_words[8]; + /* Original semantics: decode state from LE words before seeding HW */ + br_range_dec32le(midstate_words, 8, cc->val); + sha_ll_load(mode); + sha_ll_write_digest(mode, midstate_words, 8); + + uint8_t first_block[64]; + memcpy(first_block, cc->buf, prev_partial_len); + memcpy(first_block + prev_partial_len, data_ptr, stitch_take); + + sha_ll_fill_text_block(first_block, 16); + sha_ll_continue_block(mode); + SHA_WAIT(); + + data_ptr += stitch_take; + len -= stitch_take; + block_count_in_run = 1; + } + + /* Process full 64-byte blocks */ + if (len >= 64) { + if (starting_from_iv && stitch_take == 0) { + /* Start from IV directly */ + sha_ll_fill_text_block(data_ptr, 16); + sha_ll_start_block(mode); + SHA_WAIT(); + data_ptr += 64; + len -= 64; + block_count_in_run = 1; + } else if (block_count_in_run == 0) { + /* Seed from current midstate once before the run */ + uint32_t midstate_words2[8]; + br_range_dec32le(midstate_words2, 8, cc->val); + sha_ll_load(mode); + sha_ll_write_digest(mode, midstate_words2, 8); + } + +#if defined(CONFIG_IDF_TARGET_ARCH_RISCV) /* RISC-V: enforce 8-block (512B) window with checkpointing */ + while (len >= 64) { + sha_ll_fill_text_block(data_ptr, 16); + sha_ll_continue_block(mode); + SHA_WAIT(); + + data_ptr += 64; + len -= 64; + block_count_in_run++; + + if (block_count_in_run == 8 && len >= 64) { + uint32_t midstate_words3[8]; + sha_ll_load(mode); + sha_ll_read_digest(mode, midstate_words3, 8); + sha_ll_load(mode); + sha_ll_write_digest(mode, midstate_words3, 8); + block_count_in_run = 0; + } + } +#else /* Xtensa: simple per-block processing, no extra checkpointing */ + while (len >= 64) { + sha_ll_fill_text_block(data_ptr, 16); + sha_ll_continue_block(mode); + SHA_WAIT(); + data_ptr += 64; + len -= 64; + } +#endif + } + + /* Commit midstate back; keep the same on-wire format as before: LE words in cc->val */ + { + uint32_t tmp_words[8]; + sha_ll_load(mode); + SHA_WAIT(); + sha_ll_read_digest(mode, tmp_words, 8); + br_range_enc32le(cc->val, tmp_words, 8); + } + SHA_EXIT(); + } + + /* Bookkeeping and partial buffer maintenance */ + cc->count += orig_len; + s256_update_partial((br_sha256_context *)cc, orig_ptr, orig_len, prev_partial_len); +} + +void br_sha256_out(const br_sha256_context *cc, void *out) +{ + uint8_t saved_partial[sizeof cc->buf]; + uint64_t saved_count = cc->count; + uint8_t saved_state[32]; + + memcpy(saved_partial, cc->buf, sizeof cc->buf); + memcpy(saved_state, cc->val, 32); + + uint8_t partial_len = (uint8_t)(saved_count & 63U); + const int mode = (cc->vtable == &br_sha256_vtable) ? SHA2_256 : SHA2_224; + + bool midstate_is_iv = (memcmp(saved_state, S256_IV_BYTES, 32) == 0); + bool no_full_blocks_done = (saved_count < 64); + bool must_start_from_iv = midstate_is_iv && no_full_blocks_done; + + SHA_ENTER(); + + if (!must_start_from_iv) { + uint32_t midstate_words[8]; + /* Original semantics: state stored as LE words in cc->val */ + br_range_dec32le(midstate_words, 8, saved_state); + sha_ll_load(mode); + sha_ll_write_digest(mode, midstate_words, 8); + } + + uint8_t final_blocks[128]; + memcpy(final_blocks, saved_partial, partial_len); + final_blocks[partial_len] = 0x80; + + size_t zero_len = (partial_len < 56) ? (56 - partial_len - 1) + : (120 - partial_len - 1); + if (zero_len) { + memset(final_blocks + partial_len + 1, 0, zero_len); + } + + uint64_t bit_length = saved_count * 8; + size_t len_pos = partial_len + 1 + zero_len; + for (int i = 0; i < 8; i++) { + final_blocks[len_pos + i] = (uint8_t)(bit_length >> (56 - 8 * i)); + } + size_t total_final_len = len_pos + 8; + + if (total_final_len == 64) { + sha_ll_fill_text_block(final_blocks, 16); + if (must_start_from_iv) + sha_ll_start_block(mode); + else + sha_ll_continue_block(mode); + SHA_WAIT(); + } else { + sha_ll_fill_text_block(final_blocks, 16); + if (must_start_from_iv) + sha_ll_start_block(mode); + else + sha_ll_continue_block(mode); + SHA_WAIT(); + sha_ll_fill_text_block(final_blocks + 64, 16); + sha_ll_continue_block(mode); + SHA_WAIT(); + } + + { + uint32_t tmp_words[8]; + sha_ll_load(mode); + sha_ll_read_digest(mode, tmp_words, 8); + SHA_EXIT(); + /* Final digest serialization remains LE as in your original */ + br_range_enc32le(out, tmp_words, 8); + } + + /* Non-destructive out(): restore snapshot */ + memcpy(((br_sha256_context *)cc)->buf, saved_partial, sizeof cc->buf); + ((br_sha256_context *)cc)->count = saved_count; + /* cc->val was never modified in out() */ +} + +uint64_t br_sha224_state(const br_sha256_context *cc, void *dst) +{ + /* Export midstate bytes exactly as stored (LE words image), unchanged */ + memcpy(dst, cc->val, 32); + return cc->count; +} + +void br_sha224_set_state(br_sha256_context *cc, const void *src, uint64_t count) +{ + if( !(cc->vtable == &br_sha256_vtable) && !(cc->vtable == &br_sha224_vtable)){ + cc->vtable = &br_sha256_vtable; /* safety for multihash fresh gen context */ + } + /* Reset partial, set count, and import state bytes exactly as provided */ + memset(cc->buf, 0, sizeof cc->buf); + memcpy(cc->val, src, 32); + cc->count = count; +} + +const br_hash_class br_sha256_vtable PROGMEM = { + sizeof(br_sha256_context), + BR_HASHDESC_ID(br_sha256_ID) + | BR_HASHDESC_OUT(32) + | BR_HASHDESC_STATE(32) + | BR_HASHDESC_LBLEN(6) + | BR_HASHDESC_MD_PADDING + | BR_HASHDESC_MD_PADDING_BE, + (void (*)(const br_hash_class **))&br_sha256_init, + (void (*)(const br_hash_class **, + const void *, size_t))&br_sha256_update, + (void (*)(const br_hash_class *const *, void *))&br_sha256_out, + (uint64_t (*)(const br_hash_class *const *, void *))&br_sha256_state, + (void (*)(const br_hash_class **, const void *, uint64_t)) + &br_sha256_set_state +}; + +#endif /* HAVE_HAL_SHA256 && SOC_SHA_SUPPORT_RESUME */ + +/* ================================================================ + * SHA-384 (HAL path, no save/restore) + * ================================================================ */ +#if HAVE_HAL_SHA384 + +static const uint64_t S384_IV_WORDS[8] = { + 0xcbbb9d5dc1059ed8ULL, 0x629a292a367cd507ULL, + 0x9159015a3070dd17ULL, 0x152fecd8f70e5939ULL, + 0x67332667ffc00b31ULL, 0x8eb44a8768581511ULL, + 0xdb0c2e0d64f98fa7ULL, 0x47b5481dbefa4fa4ULL +}; + +void br_sha384_init(br_sha384_context *cc) { + cc->vtable = &br_sha384_vtable; + cc->count = 0; + memset(cc->buf, 0, sizeof cc->buf); + /* Internal state is LE; encode IV directly in LE form into cc->val */ + br_range_enc64le(cc->val, S384_IV_WORDS, 8); +} + +void br_sha384_update(br_sha384_context *cc, const void *data, size_t len) { + if (!len) return; + const uint8_t *src = (const uint8_t *)data; + size_t used = (size_t)(cc->count & 127U); + uint64_t prior_count = cc->count; + cc->count += len; + + if (used) { + size_t take = 128 - used; + if (take > len) take = len; + memcpy(cc->buf + used, src, take); + src += take; + len -= take; + used += take; + if (used == 128) { + uint64_t st_be[8]; + br_range_dec64le(st_be, 8, cc->val); + br_range_enc64be(st_be, st_be, 8); + SHA_ENTER(); + sha_ll_load(SHA2_512); + sha_ll_write_digest(SHA2_512, st_be, 16); + sha_ll_fill_text_block(cc->buf, 32); + sha_ll_continue_block(SHA2_512); + SHA_WAIT(); + sha_ll_read_digest(SHA2_512, st_be, 16); + SHA_EXIT(); + br_range_dec64be(st_be, 8, st_be); + br_range_enc64le(cc->val, st_be, 8); + prior_count += 128; + used = 0; + } + } + + if (len >= 128) { + uint64_t st_be[8]; + br_range_dec64le(st_be, 8, cc->val); + br_range_enc64be(st_be, st_be, 8); + bool first = (prior_count == 0); + SHA_ENTER(); + sha_ll_load(SHA2_512); + sha_ll_write_digest(SHA2_512, st_be, 16); + while (len >= 128) { + sha_ll_fill_text_block(src, 32); + if (first) { + sha_ll_start_block(SHA2_512); + first = false; + } else { + sha_ll_continue_block(SHA2_512); + } + SHA_WAIT(); + src += 128; + len -= 128; + } + sha_ll_read_digest(SHA2_512, st_be, 16); + SHA_EXIT(); + br_range_dec64be(st_be, 8, st_be); + br_range_enc64le(cc->val, st_be, 8); + } + + if (len) memcpy(cc->buf, src, len); +} + +void br_sha384_out(const br_sha384_context *cc, void *out) { + br_sha384_context ctx = *cc; + size_t used = (size_t)(ctx.count & 127U); + uint64_t bit_hi = (uint64_t)(ctx.count >> 61); + uint64_t bit_lo = (uint64_t)(ctx.count << 3); + + ctx.buf[used++] = 0x80; + if (used > 112) { + memset(ctx.buf + used, 0, 128 - used); + uint64_t st_be[8]; + br_range_dec64le(st_be, 8, ctx.val); + br_range_enc64be(st_be, st_be, 8); + SHA_ENTER(); + sha_ll_load(SHA2_512); + sha_ll_write_digest(SHA2_512, st_be, 16); + sha_ll_fill_text_block(ctx.buf, 32); + sha_ll_continue_block(SHA2_512); + SHA_WAIT(); + sha_ll_read_digest(SHA2_512, st_be, 16); + SHA_EXIT(); + br_range_dec64be(st_be, 8, st_be); + br_range_enc64le(ctx.val, st_be, 8); + used = 0; + } + + memset(ctx.buf + used, 0, 112 - used); + br_enc64be(ctx.buf + 112, bit_hi); + br_enc64be(ctx.buf + 120, bit_lo); + + uint64_t st_be2[8]; + br_range_dec64le(st_be2, 8, ctx.val); + br_range_enc64be(st_be2, st_be2, 8); + SHA_ENTER(); + sha_ll_load(SHA2_512); + sha_ll_write_digest(SHA2_512, st_be2, 16); + sha_ll_fill_text_block(ctx.buf, 32); + sha_ll_continue_block(SHA2_512); + SHA_WAIT(); + sha_ll_read_digest(SHA2_512, st_be2, 16); + SHA_EXIT(); + + br_range_dec64be(st_be2, 8, st_be2); + br_range_enc64be(out, st_be2, 6); +} + +uint64_t br_sha384_state(const br_sha384_context *cc, void *dst) { + /* Export raw 64-byte LE midstate image exactly as stored */ + memcpy(dst, cc->val, 64); + return cc->count; +} + +void br_sha384_set_state(br_sha384_context *cc, const void *src, uint64_t count) { + if (!(cc->vtable == &br_sha384_vtable)) { + cc->vtable = &br_sha384_vtable; + } + /* Import raw 64-byte LE midstate image exactly as provided */ + memcpy(cc->val, src, 64); + cc->count = count; + memset(cc->buf, 0, sizeof cc->buf); +} + +const br_hash_class br_sha384_vtable PROGMEM = { + sizeof(br_sha384_context), + BR_HASHDESC_ID(br_sha384_ID) + | BR_HASHDESC_OUT(48) + | BR_HASHDESC_STATE(64) + | BR_HASHDESC_LBLEN(7) + | BR_HASHDESC_MD_PADDING + | BR_HASHDESC_MD_PADDING_BE + | BR_HASHDESC_MD_PADDING_128, + (void (*)(const br_hash_class **)) &br_sha384_init, + (void (*)(const br_hash_class **, const void *, size_t)) &br_sha384_update, + (void (*)(const br_hash_class *const *, void *)) &br_sha384_out, + (uint64_t (*)(const br_hash_class *const *, void *)) &br_sha384_state, + (void (*)(const br_hash_class **, const void *, uint64_t)) &br_sha384_set_state +}; + +#endif /* HAVE_HAL_SHA384 */ + +/* ================================================================ + * SHA-512 (HAL path, no save/restore) + * Uses SHA-384 update/state/set_state + * ================================================================ */ +#if HAVE_HAL_SHA512 + +static const uint64_t S512_IV_WORDS[8] = { + 0x6a09e667f3bcc908ULL, 0xbb67ae8584caa73bULL, + 0x3c6ef372fe94f82bULL, 0xa54ff53a5f1d36f1ULL, + 0x510e527fade682d1ULL, 0x9b05688c2b3e6c1fULL, + 0x1f83d9abfb41bd6bULL, 0x5be0cd19137e2179ULL +}; + +void br_sha512_init(br_sha512_context *cc) { + cc->vtable = &br_sha512_vtable; + cc->count = 0; + memset(cc->buf, 0, sizeof cc->buf); + /* Internal state is LE; encode IV directly in LE form into cc->val */ + br_range_enc64le(cc->val, S512_IV_WORDS, 8); +} + +void br_sha512_out(const br_sha512_context *cc, void *out) { + br_sha512_context ctx = *cc; + size_t used = (size_t)(ctx.count & 127U); + uint64_t bit_hi = (uint64_t)(ctx.count >> 61); + uint64_t bit_lo = (uint64_t)(ctx.count << 3); + + ctx.buf[used++] = 0x80; + if (used > 112) { + memset(ctx.buf + used, 0, 128 - used); + uint64_t st_be[8]; + br_range_dec64le(st_be, 8, ctx.val); + br_range_enc64be(st_be, st_be, 8); + SHA_ENTER(); + sha_ll_load(SHA2_512); + sha_ll_write_digest(SHA2_512, st_be, 16); + sha_ll_fill_text_block(ctx.buf, 32); + sha_ll_continue_block(SHA2_512); + SHA_WAIT(); + sha_ll_read_digest(SHA2_512, st_be, 16); + SHA_EXIT(); + br_range_dec64be(st_be, 8, st_be); + br_range_enc64le(ctx.val, st_be, 8); + used = 0; + } + + memset(ctx.buf + used, 0, 112 - used); + br_enc64be(ctx.buf + 112, bit_hi); + br_enc64be(ctx.buf + 120, bit_lo); + + uint64_t st_be2[8]; + br_range_dec64le(st_be2, 8, ctx.val); + br_range_enc64be(st_be2, st_be2, 8); + SHA_ENTER(); + sha_ll_load(SHA2_512); + sha_ll_write_digest(SHA2_512, st_be2, 16); + sha_ll_fill_text_block(ctx.buf, 32); + sha_ll_continue_block(SHA2_512); + SHA_WAIT(); + sha_ll_read_digest(SHA2_512, st_be2, 16); + SHA_EXIT(); + + /* Output digest in big‑endian bytes as BearSSL expects */ + br_range_dec64be(st_be2, 8, st_be2); + br_range_enc64be(out, st_be2, 8); +} + +const br_hash_class br_sha512_vtable PROGMEM = { + sizeof(br_sha512_context), + BR_HASHDESC_ID(br_sha512_ID) + | BR_HASHDESC_OUT(64) + | BR_HASHDESC_STATE(64) + | BR_HASHDESC_LBLEN(7) + | BR_HASHDESC_MD_PADDING + | BR_HASHDESC_MD_PADDING_BE + | BR_HASHDESC_MD_PADDING_128, + (void (*)(const br_hash_class **)) &br_sha512_init, + (void (*)(const br_hash_class **, const void *, size_t)) &br_sha384_update, /* reuse SHA-384 update */ + (void (*)(const br_hash_class *const *, void *)) &br_sha512_out, + (uint64_t (*)(const br_hash_class *const *, void *)) &br_sha512_state, + (void (*)(const br_hash_class **, const void *, uint64_t)) &br_sha512_set_state +}; + +#endif /* HAVE_HAL_SHA512 */ + +#else +/* ===== ESP8266 - leave it unchanged ===== */ +#endif // SOC_SHA_SUPPORT_RESUME +#endif // defined(ESP_PLATFORM) && !defined(ESP8266) +#endif //USE_SHA_ROM diff --git a/lib/lib_ssl/bearssl-esp8266/src/hash/md5.c b/lib/lib_ssl/bearssl-esp8266/src/hash/md5.c index d954e6d3e..f38ee6ccb 100644 --- a/lib/lib_ssl/bearssl-esp8266/src/hash/md5.c +++ b/lib/lib_ssl/bearssl-esp8266/src/hash/md5.c @@ -1,7 +1,7 @@ /* * Copyright (c) 2016 Thomas Pornin * - * Permission is hereby granted, free of charge, to any person obtaining + * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, @@ -9,12 +9,12 @@ * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * - * The above copyright notice and this permission notice shall be + * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN @@ -22,7 +22,6 @@ * SOFTWARE. */ -#include #include "t_inner.h" #define F(B, C, D) ((((C) ^ (D)) & (B)) ^ (D)) diff --git a/lib/lib_ssl/bearssl-esp8266/src/hash/sha2small.c b/lib/lib_ssl/bearssl-esp8266/src/hash/sha2small.c index 8c640efc8..b4b12b13d 100644 --- a/lib/lib_ssl/bearssl-esp8266/src/hash/sha2small.c +++ b/lib/lib_ssl/bearssl-esp8266/src/hash/sha2small.c @@ -1,7 +1,7 @@ /* * Copyright (c) 2016 Thomas Pornin * - * Permission is hereby granted, free of charge, to any person obtaining + * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, @@ -9,12 +9,12 @@ * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * - * The above copyright notice and this permission notice shall be + * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN diff --git a/lib/lib_ssl/bearssl-esp8266/src/int/i15_fmont.c b/lib/lib_ssl/bearssl-esp8266/src/int/i15_fmont.c index a562d6852..8bf73a59f 100644 --- a/lib/lib_ssl/bearssl-esp8266/src/int/i15_fmont.c +++ b/lib/lib_ssl/bearssl-esp8266/src/int/i15_fmont.c @@ -1,7 +1,7 @@ /* * Copyright (c) 2017 Thomas Pornin * - * Permission is hereby granted, free of charge, to any person obtaining + * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, @@ -9,12 +9,12 @@ * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * - * The above copyright notice and this permission notice shall be + * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN diff --git a/lib/lib_ssl/bearssl-esp8266/src/int/i15_moddiv.c b/lib/lib_ssl/bearssl-esp8266/src/int/i15_moddiv.c index c3b32d2ce..d265a7d90 100644 --- a/lib/lib_ssl/bearssl-esp8266/src/int/i15_moddiv.c +++ b/lib/lib_ssl/bearssl-esp8266/src/int/i15_moddiv.c @@ -1,7 +1,7 @@ /* * Copyright (c) 2018 Thomas Pornin * - * Permission is hereby granted, free of charge, to any person obtaining + * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, @@ -9,12 +9,12 @@ * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * - * The above copyright notice and this permission notice shall be + * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN diff --git a/lib/lib_ssl/bearssl-esp8266/src/int/i15_mulacc.c b/lib/lib_ssl/bearssl-esp8266/src/int/i15_mulacc.c index db33c069c..ee4bb157d 100644 --- a/lib/lib_ssl/bearssl-esp8266/src/int/i15_mulacc.c +++ b/lib/lib_ssl/bearssl-esp8266/src/int/i15_mulacc.c @@ -1,7 +1,7 @@ /* * Copyright (c) 2017 Thomas Pornin * - * Permission is hereby granted, free of charge, to any person obtaining + * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, @@ -9,12 +9,12 @@ * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * - * The above copyright notice and this permission notice shall be + * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN diff --git a/lib/lib_ssl/bearssl-esp8266/src/rand/sysrng.c b/lib/lib_ssl/bearssl-esp8266/src/rand/sysrng.c index 2a8347736..8fa4f3403 100644 --- a/lib/lib_ssl/bearssl-esp8266/src/rand/sysrng.c +++ b/lib/lib_ssl/bearssl-esp8266/src/rand/sysrng.c @@ -25,10 +25,6 @@ #define BR_ENABLE_INTRINSICS 1 #include "t_inner.h" -#if BR_USE_GETENTROPY -#include -#endif - #if BR_USE_URANDOM #include #include @@ -42,9 +38,6 @@ #pragma comment(lib, "advapi32") #endif -/* - * Seeder that uses the RDRAND opcodes (on x86 CPU). - */ #if BR_RDRAND BR_TARGETS_X86_UP BR_TARGET("rdrnd") @@ -64,24 +57,9 @@ seeder_rdrand(const br_prng_class **ctx) * * Intel recommends trying at least 10 times in case of * failure. - * - * AMD bug: there are reports that some AMD processors - * have a bug that makes them fail silently after a - * suspend/resume cycle, in which case RDRAND will report - * a success but always return 0xFFFFFFFF. - * see: https://bugzilla.kernel.org/show_bug.cgi?id=85911 - * - * As a mitigation, if the 32-bit value is 0 or -1, then - * it is considered a failure and tried again. This should - * reliably detect the buggy case, at least. This also - * implies that the selected seed values can never be - * 0x00000000 or 0xFFFFFFFF, which is not a problem since - * we are generating a seed for a PRNG, and we overdo it - * a bit (we generate 32 bytes of randomness, and 256 bits - * of entropy are really overkill). */ for (j = 0; j < 10; j ++) { - if (_rdrand32_step(&x) && x != 0 && x != (uint32_t)-1) { + if (_rdrand32_step(&x)) { goto next_word; } } @@ -102,11 +80,9 @@ rdrand_supported(void) */ return br_cpuid(0, 0, 0x40000000, 0); } + #endif -/* - * Seeder that uses /dev/urandom (on Unix-like systems). - */ #if BR_USE_URANDOM static int seeder_urandom(const br_prng_class **ctx) @@ -140,32 +116,6 @@ seeder_urandom(const br_prng_class **ctx) } #endif -/* - * Seeder that uses getentropy() (backed by getrandom() on some systems, - * e.g. Linux). On failure, it will use the /dev/urandom seeder (if - * enabled). - */ -#if BR_USE_GETENTROPY -static int -seeder_getentropy(const br_prng_class **ctx) -{ - unsigned char tmp[32]; - - if (getentropy(tmp, sizeof tmp) == 0) { - (*ctx)->update(ctx, tmp, sizeof tmp); - return 1; - } -#if BR_USE_URANDOM - return seeder_urandom(ctx); -#else - return 0; -#endif -} -#endif - -/* - * Seeder that uses CryptGenRandom() (on Windows). - */ #if BR_USE_WIN32_RAND static int seeder_win32(const br_prng_class **ctx) @@ -221,56 +171,12 @@ seeder_esp8266(const br_prng_class **ctx) } (*ctx)->update(ctx, tmp, sizeof tmp); + return 1; } #endif #endif -#ifdef BR_USE_PICO_RAND -extern uint32_t __picoRand(); -static int -seeder_pico(const br_prng_class **ctx) -{ - uint32_t tmp[32 / sizeof(uint32_t)]; - size_t i; - - for (i=0; iupdate(ctx, tmp, sizeof tmp); - return 1; -} - -#endif - -/* - * An aggregate seeder that uses RDRAND, and falls back to an OS-provided - * source if RDRAND fails. - */ -#if BR_RDRAND && (BR_USE_GETENTROPY || BR_USE_URANDOM || BR_USE_WIN32_RAND) -static int -seeder_rdrand_with_fallback(const br_prng_class **ctx) -{ - if (!seeder_rdrand(ctx)) { -#if BR_USE_PICO - return seeder_pico(ctx); -#elif BR_USE_GETENTROPY - return seeder_getentropy(ctx); -#elif BR_USE_URANDOM - return seeder_urandom(ctx); -#elif BR_USE_WIN32_RAND - return seeder_win32(ctx); -#elif BR_USE_ESP8266_RAND - return seeder_esp8266(ctx); -#else -#error "macro selection has gone wrong" -#endif - } - return 1; -} -#endif - /* see bearssl_rand.h */ br_prng_seeder @@ -281,24 +187,10 @@ br_prng_seeder_system(const char **name) if (name != NULL) { *name = "rdrand"; } -#if BR_USE_GETENTROPY || BR_USE_URANDOM || BR_USE_WIN32_RAND - return &seeder_rdrand_with_fallback; -#else return &seeder_rdrand; -#endif } #endif -#if BR_USE_PICO_RAND - if (name != NULL) { - *name = "pico"; - } - return &seeder_pico; -#elif BR_USE_GETENTROPY - if (name != NULL) { - *name = "getentropy"; - } - return &seeder_getentropy; -#elif BR_USE_URANDOM +#if BR_USE_URANDOM if (name != NULL) { *name = "urandom"; } diff --git a/lib/lib_ssl/bearssl-esp8266/src/rsa/rsa_i15_pub.c b/lib/lib_ssl/bearssl-esp8266/src/rsa/rsa_i15_pub.c index 662b00585..2fa78312b 100644 --- a/lib/lib_ssl/bearssl-esp8266/src/rsa/rsa_i15_pub.c +++ b/lib/lib_ssl/bearssl-esp8266/src/rsa/rsa_i15_pub.c @@ -1,7 +1,7 @@ /* * Copyright (c) 2017 Thomas Pornin * - * Permission is hereby granted, free of charge, to any person obtaining + * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, @@ -9,12 +9,12 @@ * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * - * The above copyright notice and this permission notice shall be + * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN diff --git a/lib/lib_ssl/bearssl-esp8266/src/ssl/ssl_ccert_single_ec.c b/lib/lib_ssl/bearssl-esp8266/src/ssl/ssl_ccert_single_ec.c index 599cda305..7e9530cd0 100644 --- a/lib/lib_ssl/bearssl-esp8266/src/ssl/ssl_ccert_single_ec.c +++ b/lib/lib_ssl/bearssl-esp8266/src/ssl/ssl_ccert_single_ec.c @@ -69,7 +69,6 @@ cc_choose(const br_ssl_client_certificate_class **pctx, choices->hash_id = -1; choices->chain = zc->chain; choices->chain_len = zc->chain_len; - return; } } diff --git a/lib/lib_ssl/bearssl-esp8266/src/ssl/ssl_hs_client.c b/lib/lib_ssl/bearssl-esp8266/src/ssl/ssl_hs_client.c index 9d2b7444e..3f883a17b 100644 --- a/lib/lib_ssl/bearssl-esp8266/src/ssl/ssl_hs_client.c +++ b/lib/lib_ssl/bearssl-esp8266/src/ssl/ssl_hs_client.c @@ -395,9 +395,6 @@ make_client_sign(br_ssl_client_context *ctx) ctx->eng.pad, sizeof ctx->eng.pad); } -/* State machine should be squeezed for size, not performance critical */ -#pragma GCC optimize ("Os") - static const unsigned char t0_datablock[] PROGMEM = { @@ -1202,7 +1199,7 @@ br_ssl_hs_client_run(void *t0ctx) break; case 27: { /* co */ - T0_CO(); + T0_CO(); } break; case 28: { @@ -1330,12 +1327,12 @@ br_ssl_hs_client_run(void *t0ctx) break; case 37: { /* drop */ - (void)T0_POP(); + (void)T0_POP(); } break; case 38: { /* dup */ - T0_PUSH(T0_PEEK(0)); + T0_PUSH(T0_PEEK(0)); } break; case 39: { @@ -1511,7 +1508,7 @@ br_ssl_hs_client_run(void *t0ctx) break; case 56: { /* over */ - T0_PUSH(T0_PEEK(1)); + T0_PUSH(T0_PEEK(1)); } break; case 57: { @@ -1652,7 +1649,7 @@ br_ssl_hs_client_run(void *t0ctx) break; case 69: { /* swap */ - T0_SWAP(); + T0_SWAP(); } break; case 70: { diff --git a/lib/lib_ssl/bearssl-esp8266/src/ssl/ssl_hs_server.c b/lib/lib_ssl/bearssl-esp8266/src/ssl/ssl_hs_server.c index 80aedf593..9bce99645 100644 --- a/lib/lib_ssl/bearssl-esp8266/src/ssl/ssl_hs_server.c +++ b/lib/lib_ssl/bearssl-esp8266/src/ssl/ssl_hs_server.c @@ -426,9 +426,6 @@ verify_CV_sig(br_ssl_server_context *ctx, size_t sig_len) return 0; } -/* State machine should be squeezed for size, not performance critical */ -#pragma GCC optimize ("Os") - static const unsigned char t0_datablock[] PROGMEM = { @@ -1067,7 +1064,7 @@ br_ssl_hs_server_run(void *t0ctx) break; case 10: { /* -rot */ - T0_NROT(); + T0_NROT(); } break; case 11: { @@ -1241,7 +1238,7 @@ br_ssl_hs_server_run(void *t0ctx) break; case 27: { /* co */ - T0_CO(); + T0_CO(); } break; case 28: { @@ -1405,12 +1402,12 @@ br_ssl_hs_server_run(void *t0ctx) break; case 41: { /* drop */ - (void)T0_POP(); + (void)T0_POP(); } break; case 42: { /* dup */ - T0_PUSH(T0_PEEK(0)); + T0_PUSH(T0_PEEK(0)); } break; case 43: { @@ -1547,12 +1544,12 @@ br_ssl_hs_server_run(void *t0ctx) break; case 58: { /* over */ - T0_PUSH(T0_PEEK(1)); + T0_PUSH(T0_PEEK(1)); } break; case 59: { /* pick */ - T0_PICK(T0_POP()); + T0_PICK(T0_POP()); } break; case 60: { @@ -1693,7 +1690,7 @@ br_ssl_hs_server_run(void *t0ctx) break; case 71: { /* swap */ - T0_SWAP(); + T0_SWAP(); } break; case 72: { diff --git a/lib/lib_ssl/bearssl-esp8266/src/t_bearssl_x509.h b/lib/lib_ssl/bearssl-esp8266/src/t_bearssl_x509.h index 1fae8a56f..d5a6b6554 100644 --- a/lib/lib_ssl/bearssl-esp8266/src/t_bearssl_x509.h +++ b/lib/lib_ssl/bearssl-esp8266/src/t_bearssl_x509.h @@ -625,52 +625,6 @@ typedef struct { } br_name_element; -/** - * \brief Callback for validity date checks. - * - * The function receives as parameter an arbitrary user-provided context, - * and the notBefore and notAfter dates specified in an X.509 certificate, - * both expressed as a number of days and a number of seconds: - * - * - Days are counted in a proleptic Gregorian calendar since - * January 1st, 0 AD. Year "0 AD" is the one that preceded "1 AD"; - * it is also traditionally known as "1 BC". - * - * - Seconds are counted since midnight, from 0 to 86400 (a count of - * 86400 is possible only if a leap second happened). - * - * Each date and time is understood in the UTC time zone. The "Unix - * Epoch" (January 1st, 1970, 00:00 UTC) corresponds to days=719528 and - * seconds=0; the "Windows Epoch" (January 1st, 1601, 00:00 UTC) is - * days=584754, seconds=0. - * - * This function must return -1 if the current date is strictly before - * the "notBefore" time, or +1 if the current date is strictly after the - * "notAfter" time. If neither condition holds, then the function returns - * 0, which means that the current date falls within the validity range of - * the certificate. If the function returns a value distinct from -1, 0 - * and +1, then this is interpreted as an unavailability of the current - * time, which normally ends the validation process with a - * `BR_ERR_X509_TIME_UNKNOWN` error. - * - * During path validation, this callback will be invoked for each - * considered X.509 certificate. Validation fails if any of the calls - * returns a non-zero value. - * - * The context value is an abritrary pointer set by the caller when - * configuring this callback. - * - * \param tctx context pointer. - * \param not_before_days notBefore date (days since Jan 1st, 0 AD). - * \param not_before_seconds notBefore time (seconds, at most 86400). - * \param not_after_days notAfter date (days since Jan 1st, 0 AD). - * \param not_after_seconds notAfter time (seconds, at most 86400). - * \return -1, 0 or +1. - */ -typedef int (*br_x509_time_check)(void *tctx, - uint32_t not_before_days, uint32_t not_before_seconds, - uint32_t not_after_days, uint32_t not_after_seconds); - /** * \brief The "minimal" X.509 engine structure. * @@ -693,8 +647,8 @@ typedef struct { uint32_t *rp; const unsigned char *ip; } cpu; - uint32_t dp_stack[31]; - uint32_t rp_stack[31]; + uint32_t dp_stack[32]; + uint32_t rp_stack[32]; int err; /* Server name to match with the SAN / CN of the EE certificate. */ @@ -776,12 +730,6 @@ typedef struct { br_name_element *name_elts; size_t num_name_elts; - /* - * Callback function (and context) to get the current date. - */ - void *itime_ctx; - br_x509_time_check itime; - /* * Public key cryptography implementations (signature verification). */ @@ -963,28 +911,6 @@ br_x509_minimal_set_time(br_x509_minimal_context *ctx, { ctx->days = days; ctx->seconds = seconds; - ctx->itime = 0; -} - -/** - * \brief Set the validity range callback function for the X.509 - * "minimal" engine. - * - * The provided function will be invoked to check whether the validation - * date is within the validity range for a given X.509 certificate; a - * call will be issued for each considered certificate. The provided - * context pointer (itime_ctx) will be passed as first parameter to the - * callback. - * - * \param tctx context for callback invocation. - * \param cb callback function. - */ -static inline void -br_x509_minimal_set_time_callback(br_x509_minimal_context *ctx, - void *itime_ctx, br_x509_time_check itime) -{ - ctx->itime_ctx = itime_ctx; - ctx->itime = itime; } /** diff --git a/lib/lib_ssl/bearssl-esp8266/src/t_config.h b/lib/lib_ssl/bearssl-esp8266/src/t_config.h index be8e840cd..2b0e47b64 100644 --- a/lib/lib_ssl/bearssl-esp8266/src/t_config.h +++ b/lib/lib_ssl/bearssl-esp8266/src/t_config.h @@ -1,7 +1,7 @@ /* * Copyright (c) 2016 Thomas Pornin * - * Permission is hereby granted, free of charge, to any person obtaining + * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, @@ -9,12 +9,12 @@ * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * - * The above copyright notice and this permission notice shall be + * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN @@ -111,27 +111,9 @@ #define BR_RDRAND 1 */ -/* - * When BR_USE_GETENTROPY is enabled, the SSL engine will use the - * getentropy() function to obtain quality randomness for seeding its - * internal PRNG. On Linux and FreeBSD, getentropy() is implemented by - * the standard library with the system call getrandom(); on OpenBSD, - * getentropy() is the system call, and there is no getrandom() wrapper, - * hence the use of the getentropy() function for maximum portability. - * - * If the getentropy() call fails, and BR_USE_URANDOM is not explicitly - * disabled, then /dev/urandom will be used as a fallback mechanism. On - * FreeBSD and OpenBSD, this does not change much, since /dev/urandom - * will block if not enough entropy has been obtained since last boot. - * On Linux, /dev/urandom might not block, which can be troublesome in - * early boot stages, which is why getentropy() is preferred. - * -#define BR_USE_GETENTROPY 1 - */ - /* * When BR_USE_URANDOM is enabled, the SSL engine will use /dev/urandom - * to automatically obtain quality randomness for seeding its internal + * to automatically obtain quality randomness for seedings its internal * PRNG. * #define BR_USE_URANDOM 1 @@ -147,7 +129,7 @@ /* * When BR_USE_WIN32_RAND is enabled, the SSL engine will use the Win32 * (CryptoAPI) functions (CryptAcquireContext(), CryptGenRandom()...) to - * automatically obtain quality randomness for seeding its internal PRNG. + * automatically obtain quality randomness for seedings its internal PRNG. * * Note: if both BR_USE_URANDOM and BR_USE_WIN32_RAND are defined, the * former takes precedence. diff --git a/lib/lib_ssl/bearssl-esp8266/src/t_inner.h b/lib/lib_ssl/bearssl-esp8266/src/t_inner.h index 27f030108..de08990e5 100644 --- a/lib/lib_ssl/bearssl-esp8266/src/t_inner.h +++ b/lib/lib_ssl/bearssl-esp8266/src/t_inner.h @@ -1,7 +1,7 @@ /* * Copyright (c) 2016 Thomas Pornin * - * Permission is hereby granted, free of charge, to any person obtaining + * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, @@ -9,12 +9,12 @@ * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * - * The above copyright notice and this permission notice shall be + * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN diff --git a/lib/lib_ssl/bearssl-esp8266/src/x509/pkey_decoder.c b/lib/lib_ssl/bearssl-esp8266/src/x509/pkey_decoder.c index c234bc11b..84fa057aa 100644 --- a/lib/lib_ssl/bearssl-esp8266/src/x509/pkey_decoder.c +++ b/lib/lib_ssl/bearssl-esp8266/src/x509/pkey_decoder.c @@ -100,9 +100,6 @@ br_pkey_decoder_push(br_pkey_decoder_context *ctx, br_pkey_decoder_run(&ctx->cpu); } -/* State machine should be squeezed for size, not performance critical */ -#pragma GCC optimize ("Os") - static const unsigned char t0_datablock[] PROGMEM = { @@ -386,7 +383,7 @@ br_pkey_decoder_run(void *t0ctx) break; case 9: { /* -rot */ - T0_NROT(); + T0_NROT(); } break; case 10: { @@ -454,17 +451,17 @@ br_pkey_decoder_run(void *t0ctx) break; case 17: { /* co */ - T0_CO(); + T0_CO(); } break; case 18: { /* drop */ - (void)T0_POP(); + (void)T0_POP(); } break; case 19: { /* dup */ - T0_PUSH(T0_PEEK(0)); + T0_PUSH(T0_PEEK(0)); } break; case 20: { @@ -501,7 +498,7 @@ br_pkey_decoder_run(void *t0ctx) break; case 23: { /* over */ - T0_PUSH(T0_PEEK(1)); + T0_PUSH(T0_PEEK(1)); } break; case 24: { @@ -537,7 +534,7 @@ br_pkey_decoder_run(void *t0ctx) break; case 26: { /* rot */ - T0_ROT(); + T0_ROT(); } break; case 27: { @@ -574,7 +571,7 @@ br_pkey_decoder_run(void *t0ctx) break; case 30: { /* swap */ - T0_SWAP(); + T0_SWAP(); } break; } diff --git a/lib/lib_ssl/bearssl-esp8266/src/x509/skey_decoder.c b/lib/lib_ssl/bearssl-esp8266/src/x509/skey_decoder.c index 02bc8d714..47adab0eb 100644 --- a/lib/lib_ssl/bearssl-esp8266/src/x509/skey_decoder.c +++ b/lib/lib_ssl/bearssl-esp8266/src/x509/skey_decoder.c @@ -100,9 +100,6 @@ br_skey_decoder_push(br_skey_decoder_context *ctx, br_skey_decoder_run(&ctx->cpu); } -/* State machine should be squeezed for size, not performance critical */ -#pragma GCC optimize ("Os") - static const unsigned char t0_datablock[] PROGMEM = { @@ -411,7 +408,7 @@ br_skey_decoder_run(void *t0ctx) break; case 9: { /* -rot */ - T0_NROT(); + T0_NROT(); } break; case 10: { @@ -488,17 +485,17 @@ br_skey_decoder_run(void *t0ctx) break; case 18: { /* co */ - T0_CO(); + T0_CO(); } break; case 19: { /* drop */ - (void)T0_POP(); + (void)T0_POP(); } break; case 20: { /* dup */ - T0_PUSH(T0_PEEK(0)); + T0_PUSH(T0_PEEK(0)); } break; case 21: { @@ -543,7 +540,7 @@ br_skey_decoder_run(void *t0ctx) break; case 25: { /* over */ - T0_PUSH(T0_PEEK(1)); + T0_PUSH(T0_PEEK(1)); } break; case 26: { @@ -579,7 +576,7 @@ br_skey_decoder_run(void *t0ctx) break; case 28: { /* rot */ - T0_ROT(); + T0_ROT(); } break; case 29: { @@ -632,7 +629,7 @@ br_skey_decoder_run(void *t0ctx) break; case 32: { /* swap */ - T0_SWAP(); + T0_SWAP(); } break; case 33: { diff --git a/lib/lib_ssl/bearssl-esp8266/src/x509/x509_decoder.c b/lib/lib_ssl/bearssl-esp8266/src/x509/x509_decoder.c index 9d2c22d94..b738755f5 100644 --- a/lib/lib_ssl/bearssl-esp8266/src/x509/x509_decoder.c +++ b/lib/lib_ssl/bearssl-esp8266/src/x509/x509_decoder.c @@ -113,9 +113,6 @@ br_x509_decoder_push(br_x509_decoder_context *ctx, br_x509_decoder_run(&ctx->cpu); } -/* State machine should be squeezed for size, not performance critical */ -#pragma GCC optimize ("Os") - static const unsigned char t0_datablock[] PROGMEM = { @@ -523,7 +520,7 @@ br_x509_decoder_run(void *t0ctx) break; case 11: { /* -rot */ - T0_NROT(); + T0_NROT(); } break; case 12: { @@ -618,7 +615,7 @@ br_x509_decoder_run(void *t0ctx) break; case 22: { /* co */ - T0_CO(); + T0_CO(); } break; case 23: { @@ -656,12 +653,12 @@ br_x509_decoder_run(void *t0ctx) break; case 26: { /* drop */ - (void)T0_POP(); + (void)T0_POP(); } break; case 27: { /* dup */ - T0_PUSH(T0_PEEK(0)); + T0_PUSH(T0_PEEK(0)); } break; case 28: { @@ -707,7 +704,7 @@ br_x509_decoder_run(void *t0ctx) break; case 32: { /* over */ - T0_PUSH(T0_PEEK(1)); + T0_PUSH(T0_PEEK(1)); } break; case 33: { @@ -756,7 +753,7 @@ br_x509_decoder_run(void *t0ctx) break; case 35: { /* rot */ - T0_ROT(); + T0_ROT(); } break; case 36: { @@ -777,7 +774,7 @@ br_x509_decoder_run(void *t0ctx) break; case 38: { /* swap */ - T0_SWAP(); + T0_SWAP(); } break; } diff --git a/lib/lib_ssl/bearssl-esp8266/src/x509/x509_minimal.c b/lib/lib_ssl/bearssl-esp8266/src/x509/x509_minimal.c index 4e506a5a2..0d92e7b25 100644 --- a/lib/lib_ssl/bearssl-esp8266/src/x509/x509_minimal.c +++ b/lib/lib_ssl/bearssl-esp8266/src/x509/x509_minimal.c @@ -489,8 +489,6 @@ static int check_single_trust_anchor_CA(br_x509_minimal_context *ctx, return 0; } -/* State machine should be squeezed for size, not performance critical */ -#pragma GCC optimize ("Os") @@ -1088,7 +1086,7 @@ br_x509_minimal_run(void *t0ctx) break; case 11: { /* -rot */ - T0_NROT(); + T0_NROT(); } break; case 12: { @@ -1271,43 +1269,35 @@ br_x509_minimal_run(void *t0ctx) uint32_t nas = T0_POP(); uint32_t nad = T0_POP(); int r; - if (CTX->itime != 0) { - r = CTX->itime(CTX->itime_ctx, nbd, nbs, nad, nas); - if (r < -1 || r > 1) { - CTX->err = BR_ERR_X509_TIME_UNKNOWN; - T0_CO(); - } - } else { - uint32_t vd = CTX->days; - uint32_t vs = CTX->seconds; - if (vd == 0 && vs == 0) { + uint32_t vd = CTX->days; + uint32_t vs = CTX->seconds; + if (vd == 0 && vs == 0) { #if BR_USE_UNIX_TIME - time_t x = time(NULL); + time_t x = time(NULL); - vd = (uint32_t)(x / 86400) + 719528; - vs = (uint32_t)(x % 86400); + vd = (uint32_t)(x / 86400) + 719528; + vs = (uint32_t)(x % 86400); #elif BR_USE_WIN32_TIME - FILETIME ft; - uint64_t x; + FILETIME ft; + uint64_t x; - GetSystemTimeAsFileTime(&ft); - x = ((uint64_t)ft.dwHighDateTime << 32) - + (uint64_t)ft.dwLowDateTime; - x = (x / 10000000); - vd = (uint32_t)(x / 86400) + 584754; - vs = (uint32_t)(x % 86400); + GetSystemTimeAsFileTime(&ft); + x = ((uint64_t)ft.dwHighDateTime << 32) + + (uint64_t)ft.dwLowDateTime; + x = (x / 10000000); + vd = (uint32_t)(x / 86400) + 584754; + vs = (uint32_t)(x % 86400); #else - CTX->err = BR_ERR_X509_TIME_UNKNOWN; + CTX->err = BR_ERR_X509_TIME_UNKNOWN; T0_CO(); #endif - } - if (vd < nbd || (vd == nbd && vs < nbs)) { - r = -1; - } else if (vd > nad || (vd == nad && vs > nas)) { - r = 1; - } else { - r = 0; - } + } + if (vd < nbd || (vd == nbd && vs < nbs)) { + r = -1; + } else if (vd > nad || (vd == nad && vs > nas)) { + r = 1; + } else { + r = 0; } T0_PUSHi(r); @@ -1315,7 +1305,7 @@ br_x509_minimal_run(void *t0ctx) break; case 26: { /* co */ - T0_CO(); + T0_CO(); } break; case 27: { @@ -1463,12 +1453,12 @@ br_x509_minimal_run(void *t0ctx) break; case 37: { /* drop */ - (void)T0_POP(); + (void)T0_POP(); } break; case 38: { /* dup */ - T0_PUSH(T0_PEEK(0)); + T0_PUSH(T0_PEEK(0)); } break; case 39: { @@ -1609,7 +1599,7 @@ br_x509_minimal_run(void *t0ctx) break; case 48: { /* over */ - T0_PUSH(T0_PEEK(1)); + T0_PUSH(T0_PEEK(1)); } break; case 49: { @@ -1659,7 +1649,7 @@ br_x509_minimal_run(void *t0ctx) break; case 51: { /* rot */ - T0_ROT(); + T0_ROT(); } break; case 52: { @@ -1711,7 +1701,7 @@ br_x509_minimal_run(void *t0ctx) break; case 58: { /* swap */ - T0_SWAP(); + T0_SWAP(); } break; case 59: { @@ -1785,5 +1775,3 @@ verify_signature(br_x509_minimal_context *ctx, const br_x509_pkey *pk) return BR_ERR_X509_UNSUPPORTED; } } - - diff --git a/lib/lib_ssl/tls_mini/src/WiFiClientSecureLightBearSSL.cpp b/lib/lib_ssl/tls_mini/src/WiFiClientSecureLightBearSSL.cpp old mode 100644 new mode 100755 index ce552977a..06cc2bf2b --- a/lib/lib_ssl/tls_mini/src/WiFiClientSecureLightBearSSL.cpp +++ b/lib/lib_ssl/tls_mini/src/WiFiClientSecureLightBearSSL.cpp @@ -54,7 +54,9 @@ #include "c_types.h" #endif -#include +#if __has_include("core_version.h") // ESP32 Stage has no core_version.h file. Disable include via PlatformIO Option +#include // Arduino_Esp8266 version information (ARDUINO_ESP8266_RELEASE and ARDUINO_ESP8266_RELEASE_2_7_1) +#endif // ESP32_STAGE #undef DEBUG_TLS #ifdef DEBUG_TLS @@ -75,6 +77,9 @@ void _Log_heap_size(const char *msg) { #define LOG_HEAP_SIZE(a) #endif +// get UTC time from Tasmota +//extern uint32_t UtcTime(void); +//extern uint32_t CfgTime(void); #ifdef ESP8266 // Stack thunk is not needed with ESP32 // Stack thunked versions of calls @@ -191,12 +196,13 @@ void WiFiClientSecure_light::_clear() { _eng = nullptr; _iobuf_in = nullptr; _iobuf_out = nullptr; - setBufferSizes(1024, 1024); // reasonable minimum + setBufferSizes(2048, 2048); // reasonable minimum _handshake_done = false; _last_error = 0; _recvapp_buf = nullptr; _recvapp_len = 0; - _insecure = false; // set to true when calling setPubKeyFingerprint() + _insecure = true; // set to true when calling setPubKeyFingerprint() + _rsa_only = false; // for now we disable ECDSA by default _fingerprint_any = true; // by default accept all fingerprints _fingerprint1 = nullptr; _fingerprint2 = nullptr; @@ -298,16 +304,6 @@ void WiFiClientSecure_light::flush(void) { #ifdef ESP32 -int WiFiClientSecure_light::connect(IPAddress ip, uint16_t port) -{ - return connect(ip, port, _timeout); -} - -int WiFiClientSecure_light::connect(const char* name, uint16_t port) -{ - return connect(name, port, _timeout); -} - int WiFiClientSecure_light::connect(IPAddress ip, uint16_t port, int32_t timeout) { DEBUG_BSSL("connect(%s,%d)", ip.toString().c_str(), port); @@ -316,7 +312,9 @@ int WiFiClientSecure_light::connect(IPAddress ip, uint16_t port, int32_t timeout setLastError(ERR_TCP_CONNECT); return 0; } - return _connectSSL(_domain.isEmpty() ? nullptr : _domain.c_str()); + bool success = _connectSSL(_domain.isEmpty() ? nullptr : _domain.c_str()); + if (!success) { stop(); } + return success; } #else // ESP32 int WiFiClientSecure_light::connect(IPAddress ip, uint16_t port) { @@ -326,7 +324,9 @@ int WiFiClientSecure_light::connect(IPAddress ip, uint16_t port) { setLastError(ERR_TCP_CONNECT); return 0; } - return _connectSSL(_domain.isEmpty() ? nullptr : _domain.c_str()); + bool success = _connectSSL(_domain.isEmpty() ? nullptr : _domain.c_str()); + if (!success) { stop(); } + return success; } #endif @@ -335,7 +335,7 @@ int WiFiClientSecure_light::connect(const char* name, uint16_t port, int32_t tim DEBUG_BSSL("connect(%s,%d)\n", name, port); IPAddress remote_addr; clearLastError(); - if (WiFi.hostByName(name, remote_addr) != 1) { + if (!WiFi.hostByName(name, remote_addr)) { DEBUG_BSSL("connect: Name loopup failure\n"); setLastError(ERR_CANT_RESOLVE_IP); return 0; @@ -354,7 +354,7 @@ int WiFiClientSecure_light::connect(const char* name, uint16_t port) { DEBUG_BSSL("connect(%s,%d)\n", name, port); IPAddress remote_addr; clearLastError(); - if (WiFi.hostByName(name, remote_addr, 1000) != 1) { + if (!WiFi.hostByName(name, remote_addr)) { DEBUG_BSSL("connect: Name loopup failure\n"); setLastError(ERR_CANT_RESOLVE_IP); return 0; @@ -587,6 +587,7 @@ int WiFiClientSecure_light::_run_until(unsigned target, bool blocking) { if (((int32_t)(millis() - (t + this->_loopTimeout)) >= 0)){ DEBUG_BSSL("_run_until: Timeout\n"); + setLastError(ERR_TLS_TIMEOUT); return -1; } @@ -606,72 +607,66 @@ int WiFiClientSecure_light::_run_until(unsigned target, bool blocking) { } #endif - /* - If there is some record data to send, do it. This takes - precedence over everything else. - */ - if (state & BR_SSL_SENDREC) { - unsigned char *buf; - size_t len; - int wlen; + /* + If there is some record data to send, do it. This takes + precedence over everything else. + */ + if (state & BR_SSL_SENDREC) { + unsigned char *buf; + size_t len; + int wlen; - buf = br_ssl_engine_sendrec_buf(_eng, &len); - wlen = WiFiClient::write(buf, len); - if (wlen < 0) { - /* - If we received a close_notify and we - still send something, then we have our - own response close_notify to send, and - the peer is allowed by RFC 5246 not to - wait for it. - */ - if (!_eng->shutdown_recv) { -// br_ssl_engine_fail(_eng, BR_ERR_IO); - } - return -1; - } - if (wlen > 0) { - br_ssl_engine_sendrec_ack(_eng, wlen); - } + buf = br_ssl_engine_sendrec_buf(_eng, &len); + wlen = WiFiClient::write(buf, len); + if (wlen <= 0) { + /* + If we received a close_notify and we + still send something, then we have our + own response close_notify to send, and + the peer is allowed by RFC 5246 not to + wait for it. + */ + return -1; + } + if (wlen > 0) { + br_ssl_engine_sendrec_ack(_eng, wlen); + } no_work = 0; - continue; - } + continue; + } - /* - If we reached our target, then we are finished. - */ - if (state & target) { - return 0; - } - - /* - If some application data must be read, and we did not - exit, then this means that we are trying to write data, - and that's not possible until the application data is - read. This may happen if using a shared in/out buffer, - and the underlying protocol is not strictly half-duplex. - This is unrecoverable here, so we report an error. - */ - if (state & BR_SSL_RECVAPP) { - DEBUG_BSSL("_run_until: Fatal protocol state\n"); - return -1; - } - - /* - If we reached that point, then either we are trying - to read data and there is some, or the engine is stuck - until a new record is obtained. - */ - if (state & BR_SSL_RECVREC) { - if (WiFiClient::available()) { + /* + If we reached our target, then we are finished. + */ + if (state & target) { + return 0; + } + /* + If some application data must be read, and we did not + exit, then this means that we are trying to write data, + and that's not possible until the application data is + read. This may happen if using a shared in/out buffer, + and the underlying protocol is not strictly half-duplex. + This is unrecoverable here, so we report an error. + */ + if (state & BR_SSL_RECVAPP) { + DEBUG_BSSL("_run_until: Fatal protocol state\n"); + return -1; + } + /* + If we reached that point, then either we are trying + to read data and there is some, or the engine is stuck + until a new record is obtained. + */ + if (state & BR_SSL_RECVREC) { + if (WiFiClient::available()) { unsigned char *buf; size_t len; int rlen; buf = br_ssl_engine_recvrec_buf(_eng, &len); - rlen = WiFiClient::read(buf, len); + rlen = WiFiClient::read(buf, len); if (rlen < 0) { -// br_ssl_engine_fail(_eng, BR_ERR_IO); return -1; } if (rlen > 0) { @@ -680,16 +675,15 @@ int WiFiClientSecure_light::_run_until(unsigned target, bool blocking) { no_work = 0; continue; } - } - - /* - We can reach that point if the target RECVAPP, and - the state contains SENDAPP only. This may happen with - a shared in/out buffer. In that case, we must flush - the buffered data to "make room" for a new incoming - record. - */ - br_ssl_engine_flush(_eng, 0); + } + /* + We can reach that point if the target RECVAPP, and + the state contains SENDAPP only. This may happen with + a shared in/out buffer. In that case, we must flush + the buffered data to "make room" for a new incoming + record. + */ + br_ssl_engine_flush(_eng, 0); no_work++; // We didn't actually advance here } @@ -792,59 +786,69 @@ extern "C" { xc->done_cert = true; // first cert already processed } -// **** Start patch Castellucci -/* - static void pubkeyfingerprint_pubkey_fingerprint(br_sha1_context *shactx, br_rsa_public_key rsakey) { - br_sha1_init(shactx); - br_sha1_update(shactx, "ssh-rsa", 7); // tag - br_sha1_update(shactx, rsakey.e, rsakey.elen); // exponent - br_sha1_update(shactx, rsakey.n, rsakey.nlen); // modulus - } -*/ - // If `compat` id false, adds a u32be length prefixed value to the sha1 state. - // If `compat` is true, the length will be omitted for compatibility with - // data from older versions of Tasmota. - static void sha1_update_len(br_sha1_context *shactx, const void *msg, uint32_t len, bool compat) { + // Add a data element with a u32be length prefix to the sha1 state. + static void sha1_update_len(br_sha1_context *shactx, const void *msg, uint32_t len) { uint8_t buf[] = {0, 0, 0, 0}; - if (!compat) { - buf[0] = (len >> 24) & 0xff; - buf[1] = (len >> 16) & 0xff; - buf[2] = (len >> 8) & 0xff; - buf[3] = (len >> 0) & 0xff; - br_sha1_update(shactx, buf, 4); // length - } + buf[0] = (len >> 24) & 0xff; + buf[1] = (len >> 16) & 0xff; + buf[2] = (len >> 8) & 0xff; + buf[3] = (len >> 0) & 0xff; + br_sha1_update(shactx, buf, 4); // length + br_sha1_update(shactx, msg, len); // message } - // Update the received fingerprint based on the certificate's public key. - // If `compat` is true, an insecure version of the fingerprint will be - // calcualted for compatibility with older versions of Tasmota. Normally, - // `compat` should be false. - static void pubkeyfingerprint_pubkey_fingerprint(br_x509_pubkeyfingerprint_context *xc, bool compat) { - br_rsa_public_key rsakey = xc->ctx.pkey.key.rsa; + // Calculate the received fingerprint based on the certificate's public key. + // The public exponent and modulus are length prefixed to avoid security + // vulnerabilities related to ambiguous serialization. Without this, an + // attacker can generate alternative public keys which result in the same + // fingerprint, but are trivial to crack. This works because RSA keys can be + // created with more than two primes, and most numbers, even large ones, can + // be easily factored. + static void pubkeyfingerprint_pubkey_fingerprint(br_x509_pubkeyfingerprint_context *xc) { + if (xc->ctx.pkey.key_type == BR_KEYTYPE_RSA) { + br_rsa_public_key rsakey = xc->ctx.pkey.key.rsa; - br_sha1_context shactx; + br_sha1_context shactx; - br_sha1_init(&shactx); + br_sha1_init(&shactx); - sha1_update_len(&shactx, "ssh-rsa", 7, compat); // tag - sha1_update_len(&shactx, rsakey.e, rsakey.elen, compat); // exponent - sha1_update_len(&shactx, rsakey.n, rsakey.nlen, compat); // modulus + // The tag string doesn't really matter, but it should differ depending on + // key type. For RSA it's a fixed string. + sha1_update_len(&shactx, "ssh-rsa", 7); // tag + sha1_update_len(&shactx, rsakey.e, rsakey.elen); // exponent + sha1_update_len(&shactx, rsakey.n, rsakey.nlen); // modulus - br_sha1_out(&shactx, xc->pubkey_recv_fingerprint); // copy to fingerprint + br_sha1_out(&shactx, xc->pubkey_recv_fingerprint); // copy to fingerprint + } + #ifndef ESP8266 + else if (xc->ctx.pkey.key_type == BR_KEYTYPE_EC) { + br_ec_public_key eckey = xc->ctx.pkey.key.ec; + + br_sha1_context shactx; + + br_sha1_init(&shactx); + + // The tag string doesn't really matter, but it should differ depending on + // key type. For ECDSA it's a fixed string. + sha1_update_len(&shactx, "ecdsa", 5); // tag + int32_t curve = htonl(eckey.curve); + sha1_update_len(&shactx, &curve, 4); // curve id as int32be + sha1_update_len(&shactx, eckey.q, eckey.qlen); // public point + } + #endif + else { + // We don't support anything else, so just set the fingerprint to all zeros. + memset(xc->pubkey_recv_fingerprint, 0, 20); + } } -// **** End patch Castellucci // Callback when complete chain has been parsed. // Return 0 on validation success, !0 on validation error static unsigned pubkeyfingerprint_end_chain(const br_x509_class **ctx) { br_x509_pubkeyfingerprint_context *xc = (br_x509_pubkeyfingerprint_context *)ctx; - // set fingerprint status byte to zero - // FIXME: find a better way to pass this information - xc->pubkey_recv_fingerprint[20] = 0; - // Try matching using the the new fingerprint algorithm - pubkeyfingerprint_pubkey_fingerprint(xc, false); + pubkeyfingerprint_pubkey_fingerprint(xc); if (!xc->fingerprint_all) { if (0 == memcmp_P(xc->pubkey_recv_fingerprint, xc->fingerprint1, 20)) { return 0; @@ -857,7 +861,6 @@ extern "C" { // Default (no validation at all) or no errors in prior checks = success. return 0; } -// **** End patch Castellucci } // Return the public key from the validator (set by x509_minimal) @@ -894,20 +897,39 @@ extern "C" { ctx->fingerprint_all = fingerprint_all; } +#ifdef ESP8266 // We limit to a single cipher to reduce footprint // we reference it, don't put in PROGMEM static const uint16_t suites[] = { BR_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 }; +#else + // add more flexibility on ESP32 + static const uint16_t suites[] = { + BR_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + BR_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + }; + static const uint16_t suites_RSA_ONLY[] = { + BR_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + }; +#endif // Default initializion for our SSL clients - static void br_ssl_client_base_init(br_ssl_client_context *cc) { + static void br_ssl_client_base_init(br_ssl_client_context *cc, bool _rsa_only) { br_ssl_client_zero(cc); // forbid SSL renegotiation, as we free the Private Key after handshake br_ssl_engine_add_flags(&cc->eng, BR_OPT_NO_RENEGOTIATION); br_ssl_engine_set_versions(&cc->eng, BR_TLS12, BR_TLS12); +#ifdef ESP8266 br_ssl_engine_set_suites(&cc->eng, suites, (sizeof suites) / (sizeof suites[0])); +#else + if (_rsa_only) { + br_ssl_engine_set_suites(&cc->eng, suites_RSA_ONLY, (sizeof suites_RSA_ONLY) / (sizeof suites_RSA_ONLY[0])); + } else { + br_ssl_engine_set_suites(&cc->eng, suites, (sizeof suites) / (sizeof suites[0])); + } +#endif br_ssl_client_set_default_rsapub(cc); br_ssl_engine_set_default_rsavrfy(&cc->eng); @@ -921,7 +943,10 @@ extern "C" { br_ssl_engine_set_ghash(&cc->eng, &br_ghash_ctmul32); // we support only P256 EC curve for AWS IoT, no EC curve for Letsencrypt unless forced - br_ssl_engine_set_ec(&cc->eng, &br_ec_p256_m15); // TODO + br_ssl_engine_set_ec(&cc->eng, &br_ec_p256_m15); +#ifdef ESP32 + br_ssl_engine_set_ecdsa(&cc->eng, &br_ecdsa_i15_vrfy_asn1); +#endif } } @@ -935,8 +960,6 @@ bool WiFiClientSecure_light::_connectSSL(const char* hostName) { LOG_HEAP_SIZE("_connectSSL.start"); - bool OOM_error_occured = true; - do { // used to exit on Out of Memory error and keep all cleanup code at the same place // ============================================================ // allocate Thunk stack, move to alternate stack and initialize @@ -954,7 +977,7 @@ bool WiFiClientSecure_light::_connectSSL(const char* hostName) { _ctx_present = true; _eng = &_sc->eng; // Allocation/deallocation taken care of by the _sc shared_ptr - br_ssl_client_base_init(_sc.get()); + br_ssl_client_base_init(_sc.get(), _rsa_only); if (_alpn_names && _alpn_num > 0) { br_ssl_engine_set_protocol_names(_eng, _alpn_names, _alpn_num); } @@ -976,6 +999,9 @@ bool WiFiClientSecure_light::_connectSSL(const char* hostName) { br_x509_minimal_init(x509_minimal, &br_sha256_vtable, _ta_P, _ta_size); br_x509_minimal_set_rsa(x509_minimal, br_ssl_engine_get_rsavrfy(_eng)); br_x509_minimal_set_hash(x509_minimal, br_sha256_ID, &br_sha256_vtable); +#ifdef ESP32 + br_x509_minimal_set_ecdsa(x509_minimal, &br_ec_all_m15, &br_ecdsa_i15_vrfy_asn1); +#endif // ESP32 br_ssl_engine_set_x509(_eng, &x509_minimal->vtable); uint32_t now = UtcTime(); uint32_t cfg_time = CfgTime(); @@ -989,23 +1015,20 @@ bool WiFiClientSecure_light::_connectSSL(const char* hostName) { br_ssl_engine_set_buffers_bidi(_eng, _iobuf_in.get(), _iobuf_in_size, _iobuf_out.get(), _iobuf_out_size); // ============================================================ - // allocate Private key if needed, only if USE_MQTT_AWS_IOT + // allocate Private key if needed, only if USE_MQTT_CLIENT_CERT LOG_HEAP_SIZE("_connectSSL before PrivKey allocation"); - #ifdef USE_MQTT_AWS_IOT + #if defined(USE_MQTT_CLIENT_CERT) // ============================================================ - // Set the EC Private Key, only USE_MQTT_AWS_IOT + // Set the EC Private Key, only USE_MQTT_CLIENT_CERT // limited to P256 curve br_ssl_client_set_single_ec(_sc.get(), _chain_P, 1, _sk_ec_P, _allowed_usages, _cert_issuer_key_type, &br_ec_p256_m15, br_ecdsa_sign_asn1_get_default()); - #endif // USE_MQTT_AWS_IOT + #endif // USE_MQTT_CLIENT_CERT // ============================================================ // Start TLS connection, ALL - if (!br_ssl_client_reset(_sc.get(), hostName, 0)) { - OOM_error_occured = false; - break; - } + if (!br_ssl_client_reset(_sc.get(), hostName, 0)) break; auto ret = _wait_for_handshake(); #ifdef DEBUG_ESP_SSL @@ -1031,10 +1054,8 @@ bool WiFiClientSecure_light::_connectSSL(const char* hostName) { // ============================================================ // if we arrived here, this means we had an OOM error, cleaning up - if (OOM_error_occured) { - setLastError(ERR_OOM); - DEBUG_BSSL("_connectSSL: Out of memory\n"); - } + setLastError(ERR_OOM); + DEBUG_BSSL("_connectSSL: Out of memory\n"); #ifdef ESP8266 stack_thunk_light_del_ref(); #endif diff --git a/lib/lib_ssl/tls_mini/src/WiFiClientSecureLightBearSSL.h b/lib/lib_ssl/tls_mini/src/WiFiClientSecureLightBearSSL.h old mode 100644 new mode 100755 index c8555b957..f464fcda3 --- a/lib/lib_ssl/tls_mini/src/WiFiClientSecureLightBearSSL.h +++ b/lib/lib_ssl/tls_mini/src/WiFiClientSecureLightBearSSL.h @@ -20,7 +20,9 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#include +#if __has_include("core_version.h") // ESP32 Stage has no core_version.h file. Disable include via PlatformIO Option +#include // Arduino_Esp8266 version information (ARDUINO_ESP8266_RELEASE and ARDUINO_ESP8266_RELEASE_2_7_1) +#endif // ESP32_STAGE #ifndef wificlientlightbearssl_h #define wificlientlightbearssl_h @@ -29,27 +31,24 @@ #include "NetworkClient.h" #include - namespace BearSSL { class WiFiClientSecure_light : public NetworkClient { public: - typedef std::function UtcTime_fcn; - typedef std::function CfgTime_fcn; - WiFiClientSecure_light(int recv, int xmit); ~WiFiClientSecure_light() override; + // Set function pointers to call to get UTC time or build time. + typedef std::function UtcTime_fcn; + typedef std::function CfgTime_fcn; void setUtcTime_fcn(UtcTime_fcn fcn) { _UtcTime = fcn; } void setCfgTime_fcn(CfgTime_fcn fcn) { _CfgTime = fcn; } void allocateBuffers(void); #ifdef ESP32 // the method to override in ESP32 has timeout argument default #define WIFI_CLIENT_DEF_CONN_TIMEOUT_MS (3000) - int connect(IPAddress ip, uint16_t port) override; - int connect(const char* name, uint16_t port) override; - int connect(IPAddress ip, uint16_t port, int32_t timeout); - int connect(const char* name, uint16_t port, int32_t timeout); + int connect(IPAddress ip, uint16_t port, int32_t timeout = 3000) override; + int connect(const char* name, uint16_t port, int32_t timeout = 3000) override; #else int connect(IPAddress ip, uint16_t port) override; int connect(const char* name, uint16_t port) override; @@ -93,6 +92,10 @@ class WiFiClientSecure_light : public NetworkClient { _fingerprint2 = f2; _fingerprint_any = f_any; _insecure = true; + _rsa_only = true; // if fingerprint, we limit to RSA only + } + void setRSAOnly(bool rsa_only) { + _rsa_only = rsa_only; } const uint8_t * getRecvPubKeyFingerprint(void) { return _recv_fingerprint; @@ -124,6 +127,9 @@ class WiFiClientSecure_light : public NetworkClient { return br_ssl_engine_last_error(_eng); } } + int32_t getLastCipherSuite(void) { + return _eng->session.cipher_suite; + } inline void setLastError(int32_t err) { _last_error = err; } @@ -135,13 +141,16 @@ class WiFiClientSecure_light : public NetworkClient { } void setInsecure(); + void setECDSA(bool ecdsa) { + _rsa_only = !ecdsa; + }; void setDomainName(const char * domain) { _domain = domain; } private: - uint32_t _loopTimeout=5000; + uint32_t _loopTimeout=10000; void _clear(); bool _ctx_present; std::shared_ptr _sc; @@ -158,14 +167,10 @@ class WiFiClientSecure_light : public NetworkClient { bool _fingerprint_any; // accept all fingerprints bool _insecure; // force fingerprint + bool _rsa_only; // restrict to RSA only key exchange (no ECDSA - enabled to force RSA fingerprints) const uint8_t *_fingerprint1; // fingerprint1 to be checked against const uint8_t *_fingerprint2; // fingerprint2 to be checked against -// **** Start patch Castellucci -/* uint8_t _recv_fingerprint[20]; // fingerprint received -*/ - uint8_t _recv_fingerprint[21]; // fingerprint received -// **** End patch Castellucci unsigned char *_recvapp_buf; size_t _recvapp_len; @@ -207,7 +212,8 @@ class WiFiClientSecure_light : public NetworkClient { #define ERR_CANT_RESOLVE_IP -1001 #define ERR_TCP_CONNECT -1002 // #define ERR_MISSING_EC_KEY -1003 // deprecated, AWS IoT is not called if the private key is not present -#define ERR_MISSING_CA -1004 +// #define ERR_MISSING_CA -1004 // deprecated +#define ERR_TLS_TIMEOUT -1005 // For reference, BearSSL error codes: // #define BR_ERR_OK 0 diff --git a/lib/pubsubclient3/.github/doxygen-header.html b/lib/pubsubclient3/.github/doxygen-header.html new file mode 100644 index 000000000..fbe29e8a2 --- /dev/null +++ b/lib/pubsubclient3/.github/doxygen-header.html @@ -0,0 +1,77 @@ + + + + + + + + +$projectname: $title +$title + + + + + + + + + + + + +$treeview +$search +$mathjax +$darkmode + +$extrastylesheet + + + +
+ + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + +
+
$projectname $projectnumber
+ +
$projectbrief
+ + +
$searchbox
$searchbox
+
+ + diff --git a/lib/pubsubclient3/.github/doxygen-update-version.sh b/lib/pubsubclient3/.github/doxygen-update-version.sh new file mode 100644 index 000000000..a2ec04181 --- /dev/null +++ b/lib/pubsubclient3/.github/doxygen-update-version.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# Update the Doxyfile PROJECT_NUMBER with the version from library.properties +# Exit immediately if a command exits with a non-zero status. +set -e +# Make sure we are inside the github workspace +cd $GITHUB_WORKSPACE +version=`grep "version=" library.properties | cut -d "=" -f 2-` +echo "Update current version in Doxyfile: $version" +sed -i "s/PROJECT_NUMBER.*/PROJECT_NUMBER = \"v$version\"/" Doxyfile diff --git a/lib/pubsubclient3/.github/workflows/build.yml b/lib/pubsubclient3/.github/workflows/build.yml index f8c919060..110a8f0ec 100644 --- a/lib/pubsubclient3/.github/workflows/build.yml +++ b/lib/pubsubclient3/.github/workflows/build.yml @@ -3,6 +3,10 @@ name: build on: push: branches: [ "master" ] + paths: + - 'examples/**' + - 'src/**' + - 'tests/**' pull_request: branches: [ "master" ] diff --git a/lib/pubsubclient3/.github/workflows/gh-pages.yml b/lib/pubsubclient3/.github/workflows/gh-pages.yml new file mode 100644 index 000000000..6eed1ea16 --- /dev/null +++ b/lib/pubsubclient3/.github/workflows/gh-pages.yml @@ -0,0 +1,65 @@ +# This workflow generates Doxygen API documentation and deploys it to GitHub Pages. +# I didn't manage to build a Jekyll site and deploy the content to GitHub Pages. +# So now I use a static index.html file in the gh-pages branch. +# It runs on pushes to the master branch and can also be triggered manually. +name: Generate Doxygen API and deploy content to GitHub Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: ["master"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: write + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Single deploy job since we're just deploying + deploy: + if: github.repository == 'hmueller01/pubsubclient3' + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Update Doxyfile PROJECT_NUMBER + run: bash .github/doxygen-update-version.sh + - name: Doxygenize + #uses: langroodi/doxygenize@v1.7.1 # waiting for update ... + uses: langroodi/doxygenize@6e920681c5d838e9a1b5cd273b814d1e4023b63d + #uses: hmueller01/doxygenize@update-alpine-linux+patches + with: + # Doxygen configuration file path + doxygenconf: ./Doxyfile + # Generated HTML documents output folder + htmloutput: ./site/api + # GitHub Pages branch name + ghpagesbranch: gh-pages + # GitHub Pages directory path + ghpagesdir: ./site/api + # - name: List site contents + # run: | + # find ./site + - name: Setup Pages + uses: actions/configure-pages@v5 + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + #path: '.' # Upload entire repository + path: './site' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/lib/pubsubclient3/.gitignore b/lib/pubsubclient3/.gitignore index a42cc406e..9ffb670e2 100644 --- a/lib/pubsubclient3/.gitignore +++ b/lib/pubsubclient3/.gitignore @@ -3,3 +3,4 @@ tests/bin .piolibdeps .clang_complete .gcc-flags.json +site/api diff --git a/lib/pubsubclient3/Doxyfile b/lib/pubsubclient3/Doxyfile new file mode 100644 index 000000000..8e0e065b8 --- /dev/null +++ b/lib/pubsubclient3/Doxyfile @@ -0,0 +1,39 @@ +# Minimal Doxyfile for Doxygen 1.13.2 + +# Project related configuration options +PROJECT_NAME = "PubSubClient3" +PROJECT_NUMBER = "automatically updated by doxygen-update-version.sh" +PROJECT_BRIEF = "Located at GitHub" +OUTPUT_DIRECTORY = ./site +INPUT = ./src +RECURSIVE = YES + +# Build related configuration options +EXTRACT_ALL = YES +EXTRACT_PRIVATE = NO +EXTRACT_STATIC = NO +EXCLUDE_SYMBOLS = CHECK_STRING_LENGTH + +# HTML output +GENERATE_HTML = YES +HTML_OUTPUT = api +HTML_COLORSTYLE = AUTO_LIGHT +HTML_HEADER = .github/doxygen-header.html + +# LaTeX output (disable if not needed) +GENERATE_LATEX = NO + +# Source code browsing +SOURCE_BROWSER = YES + +# Warnings +WARNINGS = YES + +# Dot/Graphviz support (disable if not needed) +HAVE_DOT = NO + +# File patterns (optional, default covers most C/C++ files) +FILE_PATTERNS = *.h *.cpp *.c *.hpp + +# Language +OUTPUT_LANGUAGE = English diff --git a/lib/pubsubclient3/README.md b/lib/pubsubclient3/README.md index 8233d8e11..f285bee6e 100644 --- a/lib/pubsubclient3/README.md +++ b/lib/pubsubclient3/README.md @@ -24,12 +24,19 @@ I appreciate every contribution to this library. The library comes with a number of example sketches. See File > Examples > PubSubClient within the Arduino application. -Full API documentation is available here: https://pubsubclient.knolleary.net +Full API documentation is available here: https://hmueller01.github.io/pubsubclient3/api ## Limitations - The client is based on the [MQTT Version 3.1.1 specification](https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html) with some limitations. - - It can only publish QoS 0 messages. It can subscribe at QoS 0 or QoS 1. + - It can publish at QoS 0, 1 or 2. + + **WARNING:** No retransmission is supported to keep the library as much memory friendly as possible. + + Note: Without retransmission support, the publish QoS is only meaningful when the broker sends your + message to a subscriber, supposing that the subscriber subscribes with a QoS greater then or equal to + the publish QoS. Consider that MQTT runs over TCP, so retransmission isn't really required in most cases, especially when publishing to the broker. + - It can subscribe at QoS 0 or QoS 1. - The maximum message size, including header, is **256 bytes** by default. This is configurable via `MQTT_MAX_PACKET_SIZE` in `PubSubClient.h` or can be changed by calling `PubSubClient::setBufferSize(size)`. diff --git a/lib/pubsubclient3/examples/mqtt_auth/mqtt_auth.ino b/lib/pubsubclient3/examples/mqtt_auth/mqtt_auth.ino index cdeb7959d..5d5d95ce4 100644 --- a/lib/pubsubclient3/examples/mqtt_auth/mqtt_auth.ino +++ b/lib/pubsubclient3/examples/mqtt_auth/mqtt_auth.ino @@ -16,7 +16,7 @@ byte mac[] = {0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED}; IPAddress ip(172, 16, 0, 100); IPAddress server(172, 16, 0, 2); -void callback(char* topic, uint8_t* payload, size_t length) { +void callback(char* topic, uint8_t* payload, size_t plength) { // handle message arrived } diff --git a/lib/pubsubclient3/examples/mqtt_basic/mqtt_basic.ino b/lib/pubsubclient3/examples/mqtt_basic/mqtt_basic.ino index f790aab6a..b38716899 100644 --- a/lib/pubsubclient3/examples/mqtt_basic/mqtt_basic.ino +++ b/lib/pubsubclient3/examples/mqtt_basic/mqtt_basic.ino @@ -21,11 +21,11 @@ byte mac[] = {0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED}; IPAddress ip(172, 16, 0, 100); IPAddress server(172, 16, 0, 2); -void callback(char* topic, uint8_t* payload, size_t length) { +void callback(char* topic, uint8_t* payload, size_t plength) { Serial.print("Message arrived ["); Serial.print(topic); Serial.print("] "); - for (size_t i = 0; i < length; i++) { + for (size_t i = 0; i < plength; i++) { Serial.print((char)payload[i]); } Serial.println(); diff --git a/lib/pubsubclient3/examples/mqtt_esp8266/mqtt_esp8266.ino b/lib/pubsubclient3/examples/mqtt_esp8266/mqtt_esp8266.ino index b41192ab4..9ffe0e725 100644 --- a/lib/pubsubclient3/examples/mqtt_esp8266/mqtt_esp8266.ino +++ b/lib/pubsubclient3/examples/mqtt_esp8266/mqtt_esp8266.ino @@ -66,11 +66,11 @@ void setup_wifi() { Serial.println(WiFi.localIP()); } -void callback(char* topic, uint8_t* payload, size_t length) { +void callback(char* topic, uint8_t* payload, size_t plength) { Serial.print("Message arrived ["); Serial.print(topic); Serial.print("] "); - for (size_t i = 0; i < length; i++) { + for (size_t i = 0; i < plength; i++) { Serial.print((char)payload[i]); } Serial.println(); diff --git a/lib/pubsubclient3/examples/mqtt_large_message/mqtt_large_message.ino b/lib/pubsubclient3/examples/mqtt_large_message/mqtt_large_message.ino index 48049c3e9..3fc23c8c5 100644 --- a/lib/pubsubclient3/examples/mqtt_large_message/mqtt_large_message.ino +++ b/lib/pubsubclient3/examples/mqtt_large_message/mqtt_large_message.ino @@ -69,11 +69,11 @@ void setup_wifi() { Serial.println(WiFi.localIP()); } -void callback(char* topic, uint8_t* payload, size_t length) { +void callback(char* topic, uint8_t* payload, size_t plength) { Serial.print("Message arrived ["); Serial.print(topic); Serial.print("] "); - for (size_t i = 0; i < length; i++) { + for (size_t i = 0; i < plength; i++) { Serial.print((char)payload[i]); } Serial.println(); diff --git a/lib/pubsubclient3/examples/mqtt_publish_in_callback/mqtt_publish_in_callback.ino b/lib/pubsubclient3/examples/mqtt_publish_in_callback/mqtt_publish_in_callback.ino index 733bea3f5..f30159706 100644 --- a/lib/pubsubclient3/examples/mqtt_publish_in_callback/mqtt_publish_in_callback.ino +++ b/lib/pubsubclient3/examples/mqtt_publish_in_callback/mqtt_publish_in_callback.ino @@ -23,22 +23,22 @@ IPAddress ip(172, 16, 0, 100); IPAddress server(172, 16, 0, 2); // Callback function header -void callback(char* topic, uint8_t* payload, size_t length); +void callback(char* topic, uint8_t* payload, size_t plength); EthernetClient ethClient; PubSubClient client(server, 1883, callback, ethClient); // Callback function -void callback(char* topic, uint8_t* payload, size_t length) { +void callback(char* topic, uint8_t* payload, size_t plength) { // In order to republish this payload, a copy must be made // as the orignal payload buffer will be overwritten whilst // constructing the PUBLISH packet. // Allocate the correct amount of memory for the payload copy - byte* p = (byte*)malloc(length); + byte* p = (byte*)malloc(plength); // Copy the payload to the new buffer - memcpy(p, payload, length); - client.publish("outTopic", p, length); + memcpy(p, payload, plength); + client.publish("outTopic", p, plength); // Free the memory free(p); } diff --git a/lib/pubsubclient3/examples/mqtt_reconnect_nonblocking/mqtt_reconnect_nonblocking.ino b/lib/pubsubclient3/examples/mqtt_reconnect_nonblocking/mqtt_reconnect_nonblocking.ino index 9d5c60098..d0797419b 100644 --- a/lib/pubsubclient3/examples/mqtt_reconnect_nonblocking/mqtt_reconnect_nonblocking.ino +++ b/lib/pubsubclient3/examples/mqtt_reconnect_nonblocking/mqtt_reconnect_nonblocking.ino @@ -16,7 +16,7 @@ byte mac[] = {0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED}; IPAddress ip(172, 16, 0, 100); IPAddress server(172, 16, 0, 2); -void callback(char* topic, uint8_t* payload, size_t length) { +void callback(char* topic, uint8_t* payload, size_t plength) { // handle message arrived } diff --git a/lib/pubsubclient3/examples/mqtt_stream/mqtt_stream.ino b/lib/pubsubclient3/examples/mqtt_stream/mqtt_stream.ino index 5e620a0e3..54ab20fa0 100644 --- a/lib/pubsubclient3/examples/mqtt_stream/mqtt_stream.ino +++ b/lib/pubsubclient3/examples/mqtt_stream/mqtt_stream.ino @@ -21,11 +21,11 @@ IPAddress server(172, 16, 0, 2); SRAM sram(4, SRAM_1024); -void callback(char* topic, uint8_t* payload, size_t length) { +void callback(char* topic, uint8_t* payload, size_t plength) { sram.seek(1); // do something with the message - for (size_t i = 0; i < length; i++) { + for (size_t i = 0; i < plength; i++) { Serial.write(sram.read()); } Serial.println(); diff --git a/lib/pubsubclient3/src/PubSubClient.cpp b/lib/pubsubclient3/src/PubSubClient.cpp index dd1e5bf94..bdc3673e0 100644 --- a/lib/pubsubclient3/src/PubSubClient.cpp +++ b/lib/pubsubclient3/src/PubSubClient.cpp @@ -1,12 +1,28 @@ -/* - PubSubClient.cpp - A simple client for MQTT. - Nick O'Leary, Holger Mueller - http://knolleary.net - https://github.com/hmueller01/pubsubclient3 -*/ +/** + * @file PubSubClient.cpp + * @brief A simple client for MQTT. + * @author Nicholas O'Leary - http://knolleary.net + * @author Holger Mueller - https://github.com/hmueller01/pubsubclient3 + * @copyright MIT License 2008-2025 + * + * This file is part of the PubSubClient library. + */ #include "PubSubClient.h" +/** + * @brief Macro to check if a string 's' can be safely added to the MQTT buffer. + * + * If either check fails, the client connection is stopped and the function returns false. + * @param l current length in the buffer + * @param s string to check + */ +#define CHECK_STRING_LENGTH(l, s) \ + if ((!s) || (l + 2 + strnlen(s, this->bufferSize) > this->bufferSize)) { \ + _client->stop(); \ + return false; \ + } + PubSubClient::PubSubClient() { setBufferSize(MQTT_MAX_PACKET_SIZE); setKeepAlive(MQTT_KEEPALIVE); @@ -95,20 +111,20 @@ PubSubClient::~PubSubClient() { } bool PubSubClient::connect(const char* id) { - return connect(id, nullptr, nullptr, 0, 0, 0, 0, 1); + return connect(id, nullptr, nullptr, nullptr, MQTT_QOS0, false, nullptr, true); } bool PubSubClient::connect(const char* id, const char* user, const char* pass) { - return connect(id, user, pass, 0, 0, 0, 0, 1); + return connect(id, user, pass, nullptr, MQTT_QOS0, false, nullptr, true); } bool PubSubClient::connect(const char* id, const char* willTopic, uint8_t willQos, bool willRetain, const char* willMessage) { - return connect(id, nullptr, nullptr, willTopic, willQos, willRetain, willMessage, 1); + return connect(id, nullptr, nullptr, willTopic, willQos, willRetain, willMessage, true); } bool PubSubClient::connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, bool willRetain, const char* willMessage) { - return connect(id, user, pass, willTopic, willQos, willRetain, willMessage, 1); + return connect(id, user, pass, willTopic, willQos, willRetain, willMessage, true); } bool PubSubClient::connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, bool willRetain, @@ -157,20 +173,20 @@ bool PubSubClient::connect(const char* id, const char* user, const char* pass, c this->buffer[length++] = keepAlive & 0xFF; CHECK_STRING_LENGTH(length, id) - length = writeString(id, this->buffer, length); + length = writeString(id, this->buffer, length, this->bufferSize); if (willTopic) { CHECK_STRING_LENGTH(length, willTopic) - length = writeString(willTopic, this->buffer, length); + length = writeString(willTopic, this->buffer, length, this->bufferSize); CHECK_STRING_LENGTH(length, willMessage) - length = writeString(willMessage, this->buffer, length); + length = writeString(willMessage, this->buffer, length, this->bufferSize); } if (user) { CHECK_STRING_LENGTH(length, user) - length = writeString(user, this->buffer, length); + length = writeString(user, this->buffer, length, this->bufferSize); if (pass) { CHECK_STRING_LENGTH(length, pass) - length = writeString(pass, this->buffer, length); + length = writeString(pass, this->buffer, length, this->bufferSize); } } @@ -314,8 +330,8 @@ size_t PubSubClient::readPacket(uint8_t* hdrLen) { if (!readByte(this->buffer, &len)) return 0; skip = (this->buffer[*hdrLen + 1] << 8) + this->buffer[*hdrLen + 2]; start = 2; - if (this->buffer[0] & MQTTQOS1) { - // skip message id + if (MQTT_HDR_GET_QOS(this->buffer[0]) > MQTT_QOS0) { + // skip msgId (packet identifier) for QoS 1 and 2 messages skip += 2; } } @@ -355,7 +371,7 @@ bool PubSubClient::handlePacket(uint8_t hdrLen, size_t length) { switch (type) { case MQTTPUBLISH: if (callback) { - // MQTT Publish packet: See section 3.3 MQTT v3.1 protocol specification: + // MQTT Publish packet: See section 3.3 MQTT v3.1.1 protocol specification: // - Header: 1 byte // - Remaining header length: hdrLen bytes, multibyte field (1 .. MQTT_MAX_HEADER_SIZE - 1) // - Topic length: 2 bytes (starts at buffer[hdrLen + 1]) @@ -377,7 +393,7 @@ bool PubSubClient::handlePacket(uint8_t hdrLen, size_t length) { memmove(topic, topic + 1, topicLen); // move topic inside buffer 1 byte to front topic[topicLen] = '\0'; // end the topic as a 'C' string with \x00 - if ((this->buffer[0] & 0x06) == MQTTQOS0) { + if (MQTT_HDR_GET_QOS(this->buffer[0]) == MQTT_QOS0) { // No msgId for QOS == 0 callback(topic, payload, payloadLen); } else { @@ -399,7 +415,37 @@ bool PubSubClient::handlePacket(uint8_t hdrLen, size_t length) { } } break; + case MQTTPUBACK: + // MQTT Publish Acknowledgment (QoS 1 publish received): See section 3.4 MQTT v3.1.1 protocol specification + if (length < 4) { + ERROR_PSC_PRINTF_P("handlePacket(): Received PUBACK packet with length %zu, expected at least 4 bytes\n", length); + return false; + } + // No futher action here, as resending is not supported. + break; + case MQTTPUBREC: + // MQTT Publish Received (QoS 2 publish received, part 1): See section 3.5 MQTT v3.1.1 protocol specification + if (length < 4) { + ERROR_PSC_PRINTF_P("handlePacket(): Received PUBREC packet with length %zu, expected at least 4 bytes\n", length); + return false; + } + // MQTT Publish Release (QoS 2 publish received, part 2): See section 3.6 MQTT v3.1.1 protocol specification + buffer[0] = MQTTPUBREL | 2; // PUBREL with bit 1 set + // bytes 1-3 of PUBREL are the same as of PUBREC + if (_client->write(buffer, 4) == 4) { + lastOutActivity = millis(); + } + break; + case MQTTPUBCOMP: + // MQTT Publish Complete (QoS 2 publish received, part 3): See section 3.7 MQTT v3.1.1 protocol specification + if (length < 4) { + ERROR_PSC_PRINTF_P("handlePacket(): Received PUBCOMP packet with length %zu, expected at least 4 bytes\n", length); + return false; + } + // No futher action here, as resending is not supported. + break; case MQTTPINGREQ: + // MQTT Ping Request: See section 3.12 MQTT v3.1.1 protocol specification this->buffer[0] = MQTTPINGRESP; this->buffer[1] = 0; if (_client->write(this->buffer, 2) == 2) { @@ -456,19 +502,27 @@ bool PubSubClient::loop() { } bool PubSubClient::publish(const char* topic, const char* payload) { - return publish(topic, (const uint8_t*)payload, payload ? strnlen(payload, MQTT_MAX_POSSIBLE_PACKET_SIZE) : 0, false); + return publish(topic, payload, MQTT_QOS0, false); } bool PubSubClient::publish(const char* topic, const char* payload, bool retained) { - return publish(topic, (const uint8_t*)payload, payload ? strnlen(payload, MQTT_MAX_POSSIBLE_PACKET_SIZE) : 0, retained); + return publish(topic, payload, MQTT_QOS0, retained); +} + +bool PubSubClient::publish(const char* topic, const char* payload, uint8_t qos, bool retained) { + return publish(topic, (const uint8_t*)payload, payload ? strnlen(payload, MQTT_MAX_POSSIBLE_PACKET_SIZE) : 0, qos, retained); } bool PubSubClient::publish(const char* topic, const uint8_t* payload, size_t plength) { - return publish(topic, payload, plength, false); + return publish(topic, payload, plength, MQTT_QOS0, false); } bool PubSubClient::publish(const char* topic, const uint8_t* payload, size_t plength, bool retained) { - if (beginPublish(topic, plength, retained)) { + return publish(topic, payload, plength, MQTT_QOS0, retained); +} + +bool PubSubClient::publish(const char* topic, const uint8_t* payload, size_t plength, uint8_t qos, bool retained) { + if (beginPublish(topic, plength, qos, retained)) { size_t rc = write(payload, plength); lastOutActivity = millis(); return endPublish() && (rc == plength); @@ -477,11 +531,19 @@ bool PubSubClient::publish(const char* topic, const uint8_t* payload, size_t ple } bool PubSubClient::publish_P(const char* topic, const char* payload, bool retained) { - return publish_P(topic, (const uint8_t*)payload, payload ? strnlen_P(payload, MQTT_MAX_POSSIBLE_PACKET_SIZE) : 0, retained); + return publish_P(topic, payload, MQTT_QOS0, retained); +} + +bool PubSubClient::publish_P(const char* topic, const char* payload, uint8_t qos, bool retained) { + return publish_P(topic, (const uint8_t*)payload, payload ? strnlen_P(payload, MQTT_MAX_POSSIBLE_PACKET_SIZE) : 0, qos, retained); } bool PubSubClient::publish_P(const char* topic, const uint8_t* payload, size_t plength, bool retained) { - if (beginPublish(topic, plength, retained)) { + return publish_P(topic, payload, plength, MQTT_QOS0, retained); +} + +bool PubSubClient::publish_P(const char* topic, const uint8_t* payload, size_t plength, uint8_t qos, bool retained) { + if (beginPublish(topic, plength, qos, retained)) { size_t rc = 0; for (size_t i = 0; i < plength; i++) { rc += _client->write((uint8_t)pgm_read_byte_near(payload + i)); @@ -493,14 +555,26 @@ bool PubSubClient::publish_P(const char* topic, const uint8_t* payload, size_t p } bool PubSubClient::beginPublish(const char* topic, size_t plength, bool retained) { + return beginPublish(topic, plength, MQTT_QOS0, retained); +} + +bool PubSubClient::beginPublish(const char* topic, size_t plength, uint8_t qos, bool retained) { if (!topic) return false; + if (strlen(topic) == 0) return false; // empty topic is not allowed + if (qos > MQTT_QOS2) { // only valid QoS supported + this->_qos = MQTT_QOS0; // reset QoS to 0, that endPublish() will not send a nextMsgId + ERROR_PSC_PRINTF_P("beginPublish() called with invalid QoS %u\n", qos); + return false; + } + this->_qos = qos; // save the QoS for later endPublish() operation // check if the header and the topic (including 2 length bytes) fit into the buffer if (connected() && MQTT_MAX_HEADER_SIZE + strlen(topic) + 2 <= this->bufferSize) { // first write the topic at the end of the maximal variable header (MQTT_MAX_HEADER_SIZE) to the buffer - size_t topicLen = writeString(topic, this->buffer, MQTT_MAX_HEADER_SIZE) - MQTT_MAX_HEADER_SIZE; + size_t topicLen = writeString(topic, this->buffer, MQTT_MAX_HEADER_SIZE, this->bufferSize) - MQTT_MAX_HEADER_SIZE; // we now know the length of the topic string (lenght + 2 bytes signalling the length) and can build the variable header information - const uint8_t header = MQTTPUBLISH | (retained ? MQTTRETAINED : 0); - uint8_t hdrLen = buildHeader(header, this->buffer, topicLen + plength); + const uint8_t header = MQTTPUBLISH | MQTT_QOS_GET_HDR(qos) | (retained ? MQTTRETAINED : 0); + const size_t nextMsgLen = (qos) ? 2 : 0; // add 2 bytes for the nextMsgId if QoS > 0 + uint8_t hdrLen = buildHeader(header, this->buffer, topicLen + plength + nextMsgLen); if (hdrLen == 0) return false; // exit here in case of header generation failure // as the header length is variable, it starts at MQTT_MAX_HEADER_SIZE - hdrLen (see buildHeader() documentation) size_t rc = _client->write(this->buffer + (MQTT_MAX_HEADER_SIZE - hdrLen), hdrLen + topicLen); @@ -512,7 +586,19 @@ bool PubSubClient::beginPublish(const char* topic, size_t plength, bool retained bool PubSubClient::endPublish() { flushBuffer(); - return connected(); + if (connected()) { + if (this->_qos > MQTT_QOS0) { + // QoS == 1 or 2, send the msgId + uint8_t buf[2]; + writeNextMsgId(buf, 0, 2); + size_t rc = _client->write(buf, 2); + lastOutActivity = millis(); + return (rc == 2); + } + // QoS == 0, no msgId to send + return true; + } + return false; } /** @@ -605,24 +691,10 @@ bool PubSubClient::write(uint8_t header, uint8_t* buf, size_t length) { } /** - * @brief Write an UTF-8 encoded string to the give buffer and position. The string can have a length of 0 to 65535 bytes. The buffer is prefixed with two - * bytes representing the length of the string. See section 1.5.3 of MQTT v3.1.1 protocol specification. - * @note If the string does not fit in the buffer (bufferSize) or is longer than 65535 bytes nothing is written to the buffer and the returned position - * is unchanged. - * - * @param string 'C' string of the data that shall be written in the buffer. - * @param buf Buffer to write the string into. - * @param pos Position in the buffer to write the string. - * @return New position in the buffer (pos + 2 + string length), or pos if a buffer overrun would occur. - */ -size_t PubSubClient::writeString(const char* string, uint8_t* buf, size_t pos) { - return writeString(string, buf, pos, this->bufferSize); -} - -/** - * @brief Write an UTF-8 encoded string to the give buffer and position. The string can have a length of 0 to 65535 bytes. The buffer is prefixed with two - * bytes representing the length of the string. See section 1.5.3 of MQTT v3.1.1 protocol specification. - * @note If the string does not fit in the buffer or is longer than 65535 bytes nothing is written to the buffer and the returned position is unchanged. + * @brief Write an UTF-8 encoded string to the give buffer and position. The string can have a length of 0 to 65535 bytes. The buffer is prefixed with + * two bytes representing the length of the string. See section 1.5.3 of MQTT v3.1.1 protocol specification. + * @note If the string does not fit in the buffer or is longer than 65535 bytes nothing is written to the buffer and the returned position is + * unchanged. * * @param string 'C' string of the data that shall be written in the buffer. * @param buf Buffer to write the string into. @@ -645,6 +717,7 @@ size_t PubSubClient::writeString(const char* string, uint8_t* buf, size_t pos, s return pos; } + size_t PubSubClient::appendBuffer(uint8_t data) { buffer[_bufferWritePos] = data; ++_bufferWritePos; @@ -675,31 +748,46 @@ size_t PubSubClient::flushBuffer() { return rc; } +/** + * @brief Write nextMsgId to the give buffer and position. + * @note If the nextMsgId (2 bytes) does not fit in the buffer nothing is written to the buffer and the returned position is unchanged. + * + * @param buf Buffer to write the nextMsgId into. + * @param pos Position in the buffer to write the nextMsgId. + * @param size Maximal size of the buffer. + * @return New position in the buffer (pos + 2), or pos if a buffer overrun would occur. + */ +size_t PubSubClient::writeNextMsgId(uint8_t* buf, size_t pos, size_t size) { + if (pos + 2 <= size) { + nextMsgId = (++nextMsgId == 0) ? 1 : nextMsgId; // increment msgId (must not be 0, so start at 1) + buf[pos++] = (uint8_t)(nextMsgId >> 8); + buf[pos++] = (uint8_t)(nextMsgId & 0xFF); + } else { + ERROR_PSC_PRINTF_P("writeNextMsgId(): buffer (%zu) does not fit into buf (%zu)\n", pos + 2, size); + } + return pos; +} + bool PubSubClient::subscribe(const char* topic) { - return subscribe(topic, 0); + return subscribe(topic, MQTT_QOS0); } bool PubSubClient::subscribe(const char* topic, uint8_t qos) { if (!topic) return false; - if (qos > 1) return false; // only QoS 0 and 1 supported + if (qos > MQTT_QOS1) return false; // only QoS 0 and 1 supported size_t topicLen = strnlen(topic, this->bufferSize); - if (this->bufferSize < MQTT_MAX_HEADER_SIZE + 2 + 2 + topicLen) { - // Too long: header + nextMsgId (2) + topic length bytes (2) + topicLen + if (this->bufferSize < MQTT_MAX_HEADER_SIZE + 2 + 2 + topicLen + 1) { + // Too long: header + nextMsgId (2) + topic length bytes (2) + topicLen + QoS (1) return false; } if (connected()) { // Leave room in the buffer for header and variable length field uint16_t length = MQTT_MAX_HEADER_SIZE; - nextMsgId++; - if (nextMsgId == 0) { - nextMsgId = 1; - } - this->buffer[length++] = (nextMsgId >> 8); - this->buffer[length++] = (nextMsgId & 0xFF); - length = writeString(topic, this->buffer, length); + length = writeNextMsgId(buffer, length, this->bufferSize); // buffer size is checked before + length = writeString(topic, this->buffer, length, this->bufferSize); this->buffer[length++] = qos; - return write(MQTTSUBSCRIBE | MQTTQOS1, this->buffer, length - MQTT_MAX_HEADER_SIZE); + return write(MQTTSUBSCRIBE | MQTT_QOS_GET_HDR(MQTT_QOS1), this->buffer, length - MQTT_MAX_HEADER_SIZE); } return false; } @@ -714,14 +802,9 @@ bool PubSubClient::unsubscribe(const char* topic) { } if (connected()) { uint16_t length = MQTT_MAX_HEADER_SIZE; - nextMsgId++; - if (nextMsgId == 0) { - nextMsgId = 1; - } - this->buffer[length++] = (nextMsgId >> 8); - this->buffer[length++] = (nextMsgId & 0xFF); - length = writeString(topic, this->buffer, length); - return write(MQTTUNSUBSCRIBE | MQTTQOS1, this->buffer, length - MQTT_MAX_HEADER_SIZE); + length = writeNextMsgId(buffer, length, this->bufferSize); // buffer size is checked before + length = writeString(topic, this->buffer, length, this->bufferSize); + return write(MQTTUNSUBSCRIBE | MQTT_QOS_GET_HDR(MQTT_QOS1), this->buffer, length - MQTT_MAX_HEADER_SIZE); } return false; } diff --git a/lib/pubsubclient3/src/PubSubClient.h b/lib/pubsubclient3/src/PubSubClient.h index 66a0972db..a632c0382 100644 --- a/lib/pubsubclient3/src/PubSubClient.h +++ b/lib/pubsubclient3/src/PubSubClient.h @@ -1,9 +1,12 @@ -/* - PubSubClient.h - A simple client for MQTT. - Nick O'Leary, Holger Mueller - http://knolleary.net - https://github.com/hmueller01/pubsubclient3 -*/ +/** + * @file PubSubClient.h + * @brief A simple client for MQTT. + * @author Nicholas O'Leary - http://knolleary.net + * @author Holger Mueller - https://github.com/hmueller01/pubsubclient3 + * @copyright MIT License 2008-2025 + * + * This file is part of the PubSubClient library. + */ #ifndef PubSubClient_h #define PubSubClient_h @@ -14,20 +17,22 @@ #include "IPAddress.h" #include "Stream.h" -#define MQTT_VERSION_3_1 3 -#define MQTT_VERSION_3_1_1 4 +#define MQTT_VERSION_3_1 3 ///< Defines MQTT 3.1 protocol version, see #MQTT_VERSION +#define MQTT_VERSION_3_1_1 4 ///< Defines MQTT 3.1.1 protocol version, see #MQTT_VERSION //< @note The following #define directives can be used to configure the library. /** - * @brief Sets the version of the MQTT protocol to use. - * @note Default value is MQTT_VERSION_3_1_1 for MQTT 3.1.1. + * @brief Sets the version of the MQTT protocol to use (3.1 or 3.1.1). [#MQTT_VERSION_3_1, #MQTT_VERSION_3_1_1]. + * @note Default value is #MQTT_VERSION_3_1_1 for MQTT 3.1.1. */ #ifndef MQTT_VERSION #define MQTT_VERSION MQTT_VERSION_3_1_1 #endif -// MQTT_MAX_POSSIBLE_PACKET_SIZE : Maximum packet size defined by MQTT protocol. +/** + * @brief Maximum packet size defined by MQTT protocol. + */ #ifndef MQTT_MAX_POSSIBLE_PACKET_SIZE #define MQTT_MAX_POSSIBLE_PACKET_SIZE 268435455 #endif @@ -65,23 +70,32 @@ /** * @brief Sets the maximum number of bytes passed to the network client in each write call. * Some hardware has a limit to how much data can be passed to them in one go, - * such as the Arduino Wifi Shield. + * such as the Arduino Wifi Shield e.g. use 80. * @note Defaults to undefined, which passes the entire packet in each write call. */ -// #define MQTT_MAX_TRANSFER_SIZE 80 +#ifndef MQTT_MAX_TRANSFER_SIZE // just a hack that it gets shown in Doxygen +#define MQTT_MAX_TRANSFER_SIZE 80 +#undef MQTT_MAX_TRANSFER_SIZE +#endif -// Possible values for client.state() -#define MQTT_CONNECTION_TIMEOUT -4 -#define MQTT_CONNECTION_LOST -3 -#define MQTT_CONNECT_FAILED -2 -#define MQTT_DISCONNECTED -1 -#define MQTT_CONNECTED 0 -#define MQTT_CONNECT_BAD_PROTOCOL 1 -#define MQTT_CONNECT_BAD_CLIENT_ID 2 -#define MQTT_CONNECT_UNAVAILABLE 3 -#define MQTT_CONNECT_BAD_CREDENTIALS 4 -#define MQTT_CONNECT_UNAUTHORIZED 5 +/** + * @defgroup group_state state() result + * @brief These values indicate the current PubSubClient::state() of the client. + * @{ + */ +#define MQTT_CONNECTION_TIMEOUT -4 ///< The network connection timed out or server didn't respond within the keepalive time. +#define MQTT_CONNECTION_LOST -3 ///< The network connection was lost/broken. +#define MQTT_CONNECT_FAILED -2 ///< The network connection failed. +#define MQTT_DISCONNECTED -1 ///< The client is disconnected cleanly. +#define MQTT_CONNECTED 0 ///< The client is connected. +#define MQTT_CONNECT_BAD_PROTOCOL 1 ///< The server does not support the requested MQTT version. +#define MQTT_CONNECT_BAD_CLIENT_ID 2 ///< The server rejected the client identifier. +#define MQTT_CONNECT_UNAVAILABLE 3 ///< The server was unable to accept the connection. +#define MQTT_CONNECT_BAD_CREDENTIALS 4 ///< The username or password is not valid. +#define MQTT_CONNECT_UNAUTHORIZED 5 ///< The client is not authorized to connect to the server. +/** @} */ +/// \cond #define MQTTRETAINED 1 // Retained flag in the header #define MQTTCONNECT 1 << 4 // Client request to connect to Server #define MQTTCONNACK 2 << 4 // Connect Acknowledgment @@ -98,31 +112,41 @@ #define MQTTPINGRESP 13 << 4 // PING Response #define MQTTDISCONNECT 14 << 4 // Client is Disconnecting #define MQTTRESERVED 15 << 4 // Reserved +/// \endcond -#define MQTTQOS0 (0 << 1) -#define MQTTQOS1 (1 << 1) -#define MQTTQOS2 (2 << 1) +/** + * @defgroup group_qos QoS levels + * @brief Quality of Service (QoS) levels for MQTT messages. + * @{ + */ +#define MQTT_QOS0 ((uint8_t)0) ///< Quality of Service 0: At most once +#define MQTT_QOS1 ((uint8_t)1) ///< Quality of Service 1: At least once +#define MQTT_QOS2 ((uint8_t)2) ///< Quality of Service 2: Exactly once +/// \cond +#define MQTT_QOS_GET_HDR(qos) (((qos) & 0x03) << 1) // Get QoS header bits from QoS value +#define MQTT_HDR_GET_QOS(header) (((header) & 0x06 ) >> 1) // Get QoS value from MQTT header +/// \endcond +/** @} */ -// Maximum size of fixed header and variable length size header +/// \cond Maximum size of fixed header and variable length size header #define MQTT_MAX_HEADER_SIZE 5 +/// \endcond -#if defined(__has_include) && __has_include() && !defined(NOFUNCTIONAL) -#include +/// \anchor callback /** * @brief Define the signature required by any callback function. - * @note The parameters are TOPIC, PAYLOAD, and LENGTH, respectively. + * @param topic The topic of the message. + * @param payload The payload of the message. + * @param plength The length of the payload. */ -#define MQTT_CALLBACK_SIGNATURE std::function callback +#if defined(__has_include) && __has_include() && !defined(NOFUNCTIONAL) +#include +#define MQTT_CALLBACK_SIGNATURE std::function callback #else -#define MQTT_CALLBACK_SIGNATURE void (*callback)(char*, uint8_t*, size_t) +#define MQTT_CALLBACK_SIGNATURE void (*callback)(char* topic, uint8_t* payload, size_t plength) #endif -#define CHECK_STRING_LENGTH(l, s) \ - if ((!s) || (l + 2 + strnlen(s, this->bufferSize) > this->bufferSize)) { \ - _client->stop(); \ - return false; \ - } - +/// \cond #ifdef DEBUG_ESP_PORT #ifdef DEBUG_PUBSUBCLIENT #define DEBUG_PSC_PRINTF(fmt, ...) DEBUG_ESP_PORT.printf(("PubSubClient: " fmt), ##__VA_ARGS__) @@ -143,6 +167,7 @@ #define ERROR_PSC_PRINTF_P(fmt, ...) #endif #endif +/// \endcond /** * @class PubSubClient @@ -166,6 +191,7 @@ class PubSubClient : public Print { Stream* stream{}; int _state{MQTT_DISCONNECTED}; size_t _bufferWritePos{}; + uint8_t _qos{MQTT_QOS0}; size_t readPacket(uint8_t* hdrLen); bool handlePacket(uint8_t hdrLen, size_t len); @@ -173,8 +199,9 @@ class PubSubClient : public Print { bool readByte(uint8_t* result, size_t* pos); uint8_t buildHeader(uint8_t header, uint8_t* buf, size_t length); bool write(uint8_t header, uint8_t* buf, size_t length); - size_t writeString(const char* string, uint8_t* buf, size_t pos); size_t writeString(const char* string, uint8_t* buf, size_t pos, size_t size); + size_t writeNextMsgId(uint8_t* buf, size_t pos, size_t size); + // Add to buffer and flush if full (only to be used with beginPublish/endPublish)Add commentMore actions size_t appendBuffer(uint8_t data); @@ -184,26 +211,100 @@ class PubSubClient : public Print { public: /** * @brief Creates an uninitialised client instance. - * @note Before it can be used, - * it must be configured using the property setters setClient and setServer. + * @note Before it can be used, it must be configured using the property setters setClient() and setServer(). */ PubSubClient(); /** * @brief Creates a partially initialised client instance. * @param client The network client to use, for example WiFiClient. - * @note Before it can be used, - * it must be configured with the property setter setServer. + * @note Before it can be used, it must be configured with the property setter setServer(). */ PubSubClient(Client& client); + /** + * @brief Creates a fully configured client instance. + * @param addr The address of the server. + * @param port The port to connect to. + * @param client The network client to use, for example WiFiClient. + */ + PubSubClient(IPAddress addr, uint16_t port, Client& client); + + /** + * @brief Creates a fully configured client instance. + * @param addr The address of the server. + * @param port The port to connect to. + * @param client The network client to use, for example WiFiClient. + * @param stream A stream to write received messages to. + */ + PubSubClient(IPAddress addr, uint16_t port, Client& client, Stream& stream); + + /** + * @brief Creates a fully configured client instance. + * @param addr The address of the server. + * @param port The port to connect to. + * @param callback Pointer to a message \ref callback function. + * Called when a message arrives for a subscription created by this client. + * @param client The network client to use, for example WiFiClient. + */ + PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client); + + /** + * @brief Creates a fully configured client instance. + * @param addr The address of the server. + * @param port The port to connect to. + * @param callback Pointer to a message \ref callback function. + * Called when a message arrives for a subscription created by this client. + * @param client The network client to use, for example WiFiClient. + * @param stream A stream to write received messages to. + */ + PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream); + + /** + * @brief Creates a fully configured client instance. + * @param ip The address of the server. + * @param port The port to connect to. + * @param client The network client to use, for example WiFiClient. + */ + PubSubClient(uint8_t* ip, uint16_t port, Client& client); + + /** + * @brief Creates a fully configured client instance. + * @param ip The address of the server. + * @param port The port to connect to. + * @param client The network client to use, for example WiFiClient. + * @param stream A stream to write received messages to. + */ + PubSubClient(uint8_t* ip, uint16_t port, Client& client, Stream& stream); + + /** + * @brief Creates a fully configured client instance. + * @param ip The address of the server. + * @param port The port to connect to. + * @param callback Pointer to a message \ref callback function. + * Called when a message arrives for a subscription created by this client. + * @param client The network client to use, for example WiFiClient. + */ + PubSubClient(uint8_t* ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client); + + /** + * @brief Creates a fully configured client instance. + * @param ip The address of the server. + * @param port The port to connect to. + * @param callback Pointer to a message \ref callback function. + * Called when a message arrives for a subscription created by this client. + * @param client The network client to use, for example WiFiClient. + * @param stream A stream to write received messages to. + */ + PubSubClient(uint8_t* ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream); + /** * @brief Creates a fully configured client instance. * @param domain The address of the server. * @param port The port to connect to. * @param client The network client to use, for example WiFiClient. */ - PubSubClient(IPAddress, uint16_t, Client& client); + PubSubClient(const char* domain, uint16_t port, Client& client); /** * @brief Creates a fully configured client instance. @@ -212,104 +313,28 @@ class PubSubClient : public Print { * @param client The network client to use, for example WiFiClient. * @param stream A stream to write received messages to. */ - PubSubClient(IPAddress, uint16_t, Client& client, Stream&); + PubSubClient(const char* domain, uint16_t port, Client& client, Stream& stream); /** * @brief Creates a fully configured client instance. * @param domain The address of the server. * @param port The port to connect to. - * @param callback Pointer to a message callback function. + * @param callback Pointer to a message \ref callback function. * Called when a message arrives for a subscription created by this client. * @param client The network client to use, for example WiFiClient. */ - PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE, Client& client); + PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client); /** * @brief Creates a fully configured client instance. * @param domain The address of the server. * @param port The port to connect to. - * @param callback Pointer to a message callback function. + * @param callback Pointer to a message \ref callback function. * Called when a message arrives for a subscription created by this client. * @param client The network client to use, for example WiFiClient. * @param stream A stream to write received messages to. */ - PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE, Client& client, Stream&); - - /** - * @brief Creates a fully configured client instance. - * @param domain The address of the server. - * @param port The port to connect to. - * @param client The network client to use, for example WiFiClient. - */ - PubSubClient(uint8_t*, uint16_t, Client& client); - - /** - * @brief Creates a fully configured client instance. - * @param domain The address of the server. - * @param port The port to connect to. - * @param client The network client to use, for example WiFiClient. - * @param stream A stream to write received messages to. - */ - PubSubClient(uint8_t*, uint16_t, Client& client, Stream&); - - /** - * @brief Creates a fully configured client instance. - * @param domain The address of the server. - * @param port The port to connect to. - * @param callback Pointer to a message callback function. - * Called when a message arrives for a subscription created by this client. - * @param client The network client to use, for example WiFiClient. - */ - PubSubClient(uint8_t*, uint16_t, MQTT_CALLBACK_SIGNATURE, Client& client); - - /** - * @brief Creates a fully configured client instance. - * @param domain The address of the server. - * @param port The port to connect to. - * @param callback Pointer to a message callback function. - * Called when a message arrives for a subscription created by this client. - * @param client The network client to use, for example WiFiClient. - * @param stream A stream to write received messages to. - */ - PubSubClient(uint8_t*, uint16_t, MQTT_CALLBACK_SIGNATURE, Client& client, Stream&); - - /** - * @brief Creates a fully configured client instance. - * @param domain The address of the server. - * @param port The port to connect to. - * @param client The network client to use, for example WiFiClient. - */ - PubSubClient(const char*, uint16_t, Client& client); - - /** - * @brief Creates a fully configured client instance. - * @param domain The address of the server. - * @param port The port to connect to. - * @param client The network client to use, for example WiFiClient. - * @param stream A stream to write received messages to. - */ - PubSubClient(const char*, uint16_t, Client& client, Stream&); - - /** - * @brief Creates a fully configured client instance. - * @param domain The address of the server. - * @param port The port to connect to. - * @param callback Pointer to a message callback function. - * Called when a message arrives for a subscription created by this client. - * @param client The network client to use, for example WiFiClient. - */ - PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE, Client& client); - - /** - * @brief Creates a fully configured client instance. - * @param domain The address of the server. - * @param port The port to connect to. - * @param callback Pointer to a message callback function. - * Called when a message arrives for a subscription created by this client. - * @param client The network client to use, for example WiFiClient. - * @param stream A stream to write received messages to. - */ - PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE, Client& client, Stream&); + PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream); /** * @brief Destructor for the PubSubClient class. @@ -319,7 +344,7 @@ class PubSubClient : public Print { /** * @brief Sets the server details. * @param ip The address of the server. - * @param port The port to connect to. + * @param port The port to connect to. * @return The client instance, allowing the function to be chained. */ PubSubClient& setServer(IPAddress ip, uint16_t port); @@ -327,22 +352,22 @@ class PubSubClient : public Print { /** * @brief Sets the server details. * @param ip The address of the server. - * @param port The port to connect to. + * @param port The port to connect to. * @return The client instance, allowing the function to be chained. */ PubSubClient& setServer(uint8_t* ip, uint16_t port); /** * @brief Sets the server details. - * @param ip The address of the server. - * @param port The port to connect to. + * @param domain The address of the server. + * @param port The port to connect to. * @return The client instance, allowing the function to be chained. */ PubSubClient& setServer(const char* domain, uint16_t port); /** * @brief Sets the message callback function. - * @param callback Pointer to a message callback function. + * @param callback Pointer to a message \ref callback function. * Called when a message arrives for a subscription created by this client. * @return The client instance, allowing the function to be chained. */ @@ -398,7 +423,7 @@ class PubSubClient : public Print { size_t getBufferSize(); /** - * @brief Connects the client. + * @brief Connects the client using a clean session without username and password. * @param id The client ID to use when connecting to the server. * @return true If client succeeded in establishing a connection to the broker. * false If client failed to establish a connection to the broker. @@ -406,48 +431,60 @@ class PubSubClient : public Print { bool connect(const char* id); /** - * @brief Connects the client. + * @brief Connects the client using a clean session with username and password. * @param id The client ID to use when connecting to the server. - * @param user The username to use. If NULL, no username or password is used. - * @param pass The password to use. If NULL, no password is used. + * @param user The username to use. + * @param pass The password to use. + * @note If **user** is NULL, no username or password is used. + * @note If **pass** is NULL, no password is used. * @return true If client succeeded in establishing a connection to the broker. * false If client failed to establish a connection to the broker. */ bool connect(const char* id, const char* user, const char* pass); /** - * @brief Connects the client. + * @brief Connects the client using a clean session and will. * @param id The client ID to use when connecting to the server. * @param willTopic The topic to be used by the will message. * @param willQos The quality of service to be used by the will message. [0, 1, 2]. * @param willRetain Publish the will message with the retain flag. + * @param willMessage The message to be used by the will message. + * @note If **willTopic** is NULL, no will message is sent. * @return true If client succeeded in establishing a connection to the broker. * false If client failed to establish a connection to the broker. */ bool connect(const char* id, const char* willTopic, uint8_t willQos, bool willRetain, const char* willMessage); /** - * @brief Connects the client. + * @brief Connects the client using a clean session with username, password and will. * @param id The client ID to use when connecting to the server. - * @param user The username to use. If NULL, no username or password is used. - * @param pass The password to use. If NULL, no password is used. + * @param user The username to use. + * @param pass The password to use. * @param willTopic The topic to be used by the will message. * @param willQos The quality of service to be used by the will message. [0, 1, 2]. * @param willRetain Publish the will message with the retain flag. + * @param willMessage The message to be used by the will message. + * @note If **user** is NULL, no username or password is used. + * @note If **pass** is NULL, no password is used. + * @note If **willTopic** is NULL, no will message is sent. * @return true If client succeeded in establishing a connection to the broker. * false If client failed to establish a connection to the broker. */ bool connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, bool willRetain, const char* willMessage); /** - * @brief Connects the client. + * @brief Connects the client with all possible parameters (user, password, will and session). * @param id The client ID to use when connecting to the server. - * @param user The username to use. If NULL, no username or password is used. - * @param pass The password to use. If NULL, no password is used. + * @param user The username to use. + * @param pass The password to use. * @param willTopic The topic to be used by the will message. * @param willQos The quality of service to be used by the will message. [0, 1, 2]. * @param willRetain Publish the will message with the retain flag. - * @param cleanSession Connect with a clean session. + * @param willMessage The message to be used by the will message. + * @param cleanSession True to connect with a clean session. + * @note If **user** is NULL, no username or password is used. + * @note If **pass** is NULL, no password is used. + * @note If **willTopic** is NULL, no will message is sent. * @return true If client succeeded in establishing a connection to the broker. * false If client failed to establish a connection to the broker. */ @@ -460,7 +497,7 @@ class PubSubClient : public Print { void disconnect(); /** - * @brief Publishes a non retained message to the specified topic. + * @brief Publishes a non retained message to the specified topic using QoS 0. * @param topic The topic to publish to. * @param payload The message to publish. * @return true If the publish succeeded. @@ -469,7 +506,7 @@ class PubSubClient : public Print { bool publish(const char* topic, const char* payload); /** - * @brief Publishes a message to the specified topic. + * @brief Publishes a message to the specified topic using QoS 0. * @param topic The topic to publish to. * @param payload The message to publish. * @param retained Publish the message with the retain flag. @@ -479,7 +516,18 @@ class PubSubClient : public Print { bool publish(const char* topic, const char* payload, bool retained); /** - * @brief Publishes a non retained message to the specified topic. + * @brief Publishes a message to the specified topic. + * @param topic The topic to publish to. + * @param payload The message to publish. + * @param qos The quality of service (\ref group_qos) to publish at. [0, 1, 2]. + * @param retained Publish the message with the retain flag. + * @return true If the publish succeeded. + * false If the publish failed, either connection lost or message too large. + */ + bool publish(const char* topic, const char* payload, uint8_t qos, bool retained); + + /** + * @brief Publishes a non retained message to the specified topic using QoS 0. * @param topic The topic to publish to. * @param payload The message to publish. * @param plength The length of the payload. @@ -489,7 +537,7 @@ class PubSubClient : public Print { bool publish(const char* topic, const uint8_t* payload, size_t plength); /** - * @brief Publishes a message to the specified topic. + * @brief Publishes a message to the specified topic using QoS 0. * @param topic The topic to publish to. * @param payload The message to publish. * @param plength The length of the payload. @@ -500,7 +548,19 @@ class PubSubClient : public Print { bool publish(const char* topic, const uint8_t* payload, size_t plength, bool retained); /** - * @brief Publishes a message stored in PROGMEM to the specified topic. + * @brief Publishes a message to the specified topic. + * @param topic The topic to publish to. + * @param payload The message to publish. + * @param plength The length of the payload. + * @param qos The quality of service (\ref group_qos) to publish at. [0, 1, 2]. + * @param retained Publish the message with the retain flag. + * @return true If the publish succeeded. + * false If the publish failed, either connection lost or message too large. + */ + bool publish(const char* topic, const uint8_t* payload, size_t plength, uint8_t qos, bool retained); + + /** + * @brief Publishes a message stored in PROGMEM to the specified topic using QoS 0. * @param topic The topic to publish to. * @param payload The message to publish. * @param retained Publish the message with the retain flag. @@ -513,6 +573,17 @@ class PubSubClient : public Print { * @brief Publishes a message stored in PROGMEM to the specified topic. * @param topic The topic to publish to. * @param payload The message to publish. + * @param qos The quality of service (\ref group_qos) to publish at. [0, 1, 2]. + * @param retained Publish the message with the retain flag. + * @return true If the publish succeeded. + * false If the publish failed, either connection lost or message too large. + */ + bool publish_P(const char* topic, const char* payload, uint8_t qos, bool retained); + + /** + * @brief Publishes a message stored in PROGMEM to the specified topic using QoS 0. + * @param topic The topic to publish to. + * @param payload The message to publish. * @param plength The length of the payload. * @param retained Publish the message with the retain flag. * @return true If the publish succeeded. @@ -521,7 +592,19 @@ class PubSubClient : public Print { bool publish_P(const char* topic, const uint8_t* payload, size_t plength, bool retained); /** - * @brief Start to publish a message. + * @brief Publishes a message stored in PROGMEM to the specified topic. + * @param topic The topic to publish to. + * @param payload The message to publish. + * @param plength The length of the payload. + * @param qos The quality of service (\ref group_qos) to publish at. [0, 1, 2]. + * @param retained Publish the message with the retain flag. + * @return true If the publish succeeded. + * false If the publish failed, either connection lost or message too large. + */ + bool publish_P(const char* topic, const uint8_t* payload, size_t plength, uint8_t qos, bool retained); + + /** + * @brief Start to publish a message using QoS 0. * This API: * beginPublish(...) * one or more calls to write(...) @@ -536,6 +619,23 @@ class PubSubClient : public Print { */ bool beginPublish(const char* topic, size_t plength, bool retained); + /** + * @brief Start to publish a message. + * This API: + * beginPublish(...) + * one or more calls to write(...) + * endPublish() + * Allows for arbitrarily large payloads to be sent without them having to be copied into + * a new buffer and held in memory at one time. + * @param topic The topic to publish to. + * @param plength The length of the payload. + * @param qos The quality of service (\ref group_qos) to publish at. [0, 1, 2]. + * @param retained Publish the message with the retain flag. + * @return true If the publish succeeded. + * false If the publish failed, either connection lost or message too large. + */ + bool beginPublish(const char* topic, size_t plength, uint8_t qos, bool retained); + /** * @brief Finish sending a message that was started with a call to beginPublish. * @return true If the publish succeeded. @@ -547,23 +647,22 @@ class PubSubClient : public Print { * @brief Writes a single byte as a component of a publish started with a call to beginPublish. * For performance reasons, this will be appended to the internal buffer, * which will be flushed when full or on a call to endPublish(). - * @param byte A byte to write to the publish payload. + * @param data A byte to write to the publish payload. * @return The number of bytes written. */ - virtual size_t write(uint8_t); + virtual size_t write(uint8_t data); /** * @brief Writes an array of bytes as a component of a publish started with a call to beginPublish. * For performance reasons, this will be appended to the internal buffer, - * which will be flushed when full or on a call to endPublish(). - * @param buffer The bytes to write. + * which will be flushed when full or on a call to endPublish(). * @param buffer The bytes to write. * @param size The length of the payload to be sent. * @return The number of bytes written. */ virtual size_t write(const uint8_t* buffer, size_t size); /** - * @brief Subscribes to messages published to the specified topic. + * @brief Subscribes to messages published to the specified topic using QoS 0. * @param topic The topic to subscribe to. * @return true If sending the subscribe succeeded. * false If sending the subscribe failed, either connection lost or message too large. @@ -605,16 +704,7 @@ class PubSubClient : public Print { * @brief Returns the current state of the client. * If a connection attempt fails, this can be used to get more information about the failure. * @note All of the values have corresponding constants defined in PubSubClient.h. - * @return -4 : MQTT_CONNECTION_TIMEOUT - The server didn't respond within the keepalive time. - * -3 : MQTT_CONNECTION_LOST - The network connection was broken. - * -2 : MQTT_CONNECT_FAILED - The network connection failed. - * -1 : MQTT_DISCONNECTED - The client is disconnected cleanly. - * 0 : MQTT_CONNECTED - The client is connected. - * 1 : MQTT_CONNECT_BAD_PROTOCOL - The server doesn't support the requested version of MQTT. - * 2 : MQTT_CONNECT_BAD_CLIENT_ID - The server rejected the client identifier. - * 3 : MQTT_CONNECT_UNAVAILABLE - The server was unable to accept the connection. - * 4 : MQTT_CONNECT_BAD_CREDENTIALS - The username/password were rejected. - * 5 : MQTT_CONNECT_UNAUTHORIZED - The client was not authorized to connect. + * @return See \ref group_state */ int state(); }; diff --git a/lib/pubsubclient3/tests/src/connect_spec.cpp b/lib/pubsubclient3/tests/src/connect_spec.cpp index 75dc819b5..b272e92f5 100644 --- a/lib/pubsubclient3/tests/src/connect_spec.cpp +++ b/lib/pubsubclient3/tests/src/connect_spec.cpp @@ -7,7 +7,7 @@ byte server[] = {172, 16, 0, 2}; // function declarations -void callback(char* topic, uint8_t* payload, size_t length); +void callback(char* topic, uint8_t* payload, size_t plength); int test_connect_fails_no_network(); int test_connect_fails_on_no_response(); int test_connect_properly_formatted(); @@ -23,7 +23,7 @@ int test_connect_with_will_username_password(); int test_connect_disconnect_connect(); int test_connect_custom_keepalive(); -void callback(_UNUSED_ char* topic, _UNUSED_ uint8_t* payload, _UNUSED_ size_t length) { +void callback(_UNUSED_ char* topic, _UNUSED_ uint8_t* payload, _UNUSED_ size_t plength) { // handle message arrived } diff --git a/lib/pubsubclient3/tests/src/keepalive_spec.cpp b/lib/pubsubclient3/tests/src/keepalive_spec.cpp index 58e3f206c..37b99be72 100644 --- a/lib/pubsubclient3/tests/src/keepalive_spec.cpp +++ b/lib/pubsubclient3/tests/src/keepalive_spec.cpp @@ -9,14 +9,14 @@ byte server[] = {172, 16, 0, 2}; // function declarations -void callback(char* topic, uint8_t* payload, size_t length); +void callback(char* topic, uint8_t* payload, size_t plength); int test_keepalive_pings_idle(); int test_keepalive_pings_with_outbound_qos0(); int test_keepalive_pings_with_inbound_qos0(); int test_keepalive_no_pings_inbound_qos1(); int test_keepalive_disconnects_hung(); -void callback(_UNUSED_ char* topic, _UNUSED_ uint8_t* payload, _UNUSED_ size_t length) { +void callback(_UNUSED_ char* topic, _UNUSED_ uint8_t* payload, _UNUSED_ size_t plength) { // handle message arrived } @@ -54,7 +54,7 @@ int test_keepalive_pings_idle() { } int test_keepalive_pings_with_outbound_qos0() { - IT("keeps a connection alive that only sends qos0 (takes 1 minute)"); + IT("keeps a connection alive that only sends QoS 0 (takes 1 minute)"); ShimClient shimClient; shimClient.setAllowConnect(true); @@ -90,7 +90,7 @@ int test_keepalive_pings_with_outbound_qos0() { } int test_keepalive_pings_with_inbound_qos0() { - IT("keeps a connection alive that only receives qos0 (takes 1 minute)"); + IT("keeps a connection alive that only receives QoS 0 (takes 1 minute)"); ShimClient shimClient; shimClient.setAllowConnect(true); @@ -123,7 +123,7 @@ int test_keepalive_pings_with_inbound_qos0() { } int test_keepalive_no_pings_inbound_qos1() { - IT("does not send pings for connections with inbound qos1 (takes 1 minute)"); + IT("does not send pings for connections with inbound QoS 1 (takes 1 minute)"); ShimClient shimClient; shimClient.setAllowConnect(true); diff --git a/lib/pubsubclient3/tests/src/publish_spec.cpp b/lib/pubsubclient3/tests/src/publish_spec.cpp index 4346a721a..241ee57e2 100644 --- a/lib/pubsubclient3/tests/src/publish_spec.cpp +++ b/lib/pubsubclient3/tests/src/publish_spec.cpp @@ -7,7 +7,7 @@ byte server[] = {172, 16, 0, 2}; // function declarations -void callback(char* topic, uint8_t* payload, size_t length); +void callback(char* topic, uint8_t* payload, size_t plength); int test_publish(); int test_publish_bytes(); int test_publish_retained(); @@ -16,8 +16,14 @@ int test_publish_not_connected(); int test_publish_too_long(); int test_publish_P(); int test_publish_P_too_long(); +int test_publish_empty_topic(); +int test_publish_null_payload(); +int test_publish_qos1(); +int test_publish_qos2(); +int test_publish_P_qos1(); +int test_publish_P_qos2(); -void callback(_UNUSED_ char* topic, _UNUSED_ uint8_t* payload, _UNUSED_ size_t length) { +void callback(_UNUSED_ char* topic, _UNUSED_ uint8_t* payload, _UNUSED_ size_t plength) { // handle message arrived } @@ -27,14 +33,14 @@ int test_publish() { shimClient.setAllowConnect(true); byte connack[] = {0x20, 0x02, 0x00, 0x00}; - shimClient.respond(connack, 4); + shimClient.respond(connack, sizeof(connack)); PubSubClient client(server, 1883, callback, shimClient); bool rc = client.connect("client_test1"); IS_TRUE(rc); - byte publish[] = {0x30, 0xe, 0x0, 0x5, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64}; - shimClient.expect(publish, 16); + byte publish[] = {0x30, 0x0e, 0x00, 0x05, 't', 'o', 'p', 'i', 'c', 'p', 'a', 'y', 'l', 'o', 'a', 'd'}; + shimClient.expect(publish, sizeof(publish)); rc = client.publish("topic", "payload"); IS_TRUE(rc); @@ -49,18 +55,18 @@ int test_publish_bytes() { ShimClient shimClient; shimClient.setAllowConnect(true); - byte payload[] = {0x01, 0x02, 0x03, 0x0, 0x05}; - size_t length = 5; + byte payload[] = {0x01, 0x02, 0x03, 0x00, 0x05}; + size_t length = sizeof(payload); byte connack[] = {0x20, 0x02, 0x00, 0x00}; - shimClient.respond(connack, 4); + shimClient.respond(connack, sizeof(connack)); PubSubClient client(server, 1883, callback, shimClient); bool rc = client.connect("client_test1"); IS_TRUE(rc); - byte publish[] = {0x30, 0xc, 0x0, 0x5, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x1, 0x2, 0x3, 0x0, 0x5}; - shimClient.expect(publish, 14); + byte publish[] = {0x30, 0x0c, 0x00, 0x05, 't', 'o', 'p', 'i', 'c', 0x01, 0x02, 0x03, 0x00, 0x05}; + shimClient.expect(publish, sizeof(publish)); rc = client.publish("topic", payload, length); IS_TRUE(rc); @@ -75,18 +81,18 @@ int test_publish_retained() { ShimClient shimClient; shimClient.setAllowConnect(true); - byte payload[] = {0x01, 0x02, 0x03, 0x0, 0x05}; - size_t length = 5; + byte payload[] = {0x01, 0x02, 0x03, 0x00, 0x05}; + size_t length = sizeof(payload); byte connack[] = {0x20, 0x02, 0x00, 0x00}; - shimClient.respond(connack, 4); + shimClient.respond(connack, sizeof(connack)); PubSubClient client(server, 1883, callback, shimClient); bool rc = client.connect("client_test1"); IS_TRUE(rc); - byte publish[] = {0x31, 0xc, 0x0, 0x5, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x1, 0x2, 0x3, 0x0, 0x5}; - shimClient.expect(publish, 14); + byte publish[] = {0x31, 0x0c, 0x00, 0x05, 't', 'o', 'p', 'i', 'c', 0x1, 0x2, 0x3, 0x0, 0x5}; + shimClient.expect(publish, sizeof(publish)); rc = client.publish("topic", payload, length, true); IS_TRUE(rc); @@ -102,14 +108,14 @@ int test_publish_retained_2() { shimClient.setAllowConnect(true); byte connack[] = {0x20, 0x02, 0x00, 0x00}; - shimClient.respond(connack, 4); + shimClient.respond(connack, sizeof(connack)); PubSubClient client(server, 1883, callback, shimClient); bool rc = client.connect("client_test1"); IS_TRUE(rc); - byte publish[] = {0x31, 0xc, 0x0, 0x5, 0x74, 0x6f, 0x70, 0x69, 0x63, 'A', 'B', 'C', 'D', 'E'}; - shimClient.expect(publish, 14); + byte publish[] = {0x31, 0x0c, 0x00, 0x05, 't', 'o', 'p', 'i', 'c', 'A', 'B', 'C', 'D', 'E'}; + shimClient.expect(publish, sizeof(publish)); rc = client.publish("topic", "ABCDE", true); IS_TRUE(rc); @@ -165,18 +171,18 @@ int test_publish_P() { ShimClient shimClient; shimClient.setAllowConnect(true); - byte payload[] = {0x01, 0x02, 0x03, 0x0, 0x05}; - size_t length = 5; + byte payload[] = {0x01, 0x02, 0x03, 0x00, 0x05}; + size_t length = sizeof(payload); byte connack[] = {0x20, 0x02, 0x00, 0x00}; - shimClient.respond(connack, 4); + shimClient.respond(connack, sizeof(connack)); PubSubClient client(server, 1883, callback, shimClient); bool rc = client.connect("client_test1"); IS_TRUE(rc); - byte publish[] = {0x31, 0xc, 0x0, 0x5, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x1, 0x2, 0x3, 0x0, 0x5}; - shimClient.expect(publish, 14); + byte publish[] = {0x31, 0x0c, 0x00, 0x05, 't', 'o', 'p', 'i', 'c', 0x01, 0x02, 0x03, 0x00, 0x05}; + shimClient.expect(publish, sizeof(publish)); rc = client.publish_P("topic", payload, length, true); IS_TRUE(rc); @@ -187,7 +193,7 @@ int test_publish_P() { } int test_publish_P_too_long() { - IT("publish using PROGMEM fails when topic are too long"); + IT("publish using PROGMEM fails when topic is too long"); ShimClient shimClient; shimClient.setAllowConnect(true); @@ -198,7 +204,7 @@ int test_publish_P_too_long() { char payload[] = "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"; byte connack[] = {0x20, 0x02, 0x00, 0x00}; - shimClient.respond(connack, 4); + shimClient.respond(connack, sizeof(connack)); PubSubClient client(server, 1883, callback, shimClient); client.setBufferSize(64); @@ -213,15 +219,164 @@ int test_publish_P_too_long() { END_IT } +int test_publish_empty_topic() { + IT("publish fails when topic is empty"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = {0x20, 0x02, 0x00, 0x00}; + shimClient.respond(connack, 4); + + PubSubClient client(server, 1883, callback, shimClient); + bool rc = client.connect("client_test1"); + IS_TRUE(rc); + + rc = client.publish("", "payload"); + IS_FALSE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_publish_null_payload() { + IT("publishes with null payload"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = {0x20, 0x02, 0x00, 0x00}; + shimClient.respond(connack, sizeof(connack)); + + PubSubClient client(server, 1883, callback, shimClient); + bool rc = client.connect("client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x30, 0x07, 0x00, 0x05, 't', 'o', 'p', 'i', 'c'}; + shimClient.expect(publish, sizeof(publish)); + + rc = client.publish("topic", nullptr); + IS_TRUE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_publish_qos1() { + IT("publishes with QoS 1"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = {0x20, 0x02, 0x00, 0x00}; + shimClient.respond(connack, 4); + + PubSubClient client(server, 1883, callback, shimClient); + bool rc = client.connect("client_test1"); + IS_TRUE(rc); + + // Example publish packet for QoS 1 (0x32) + byte publish[] = {0x32, 0x10, 0x00, 0x05, 't', 'o', 'p', 'i', 'c', 'p', 'a', 'y', 'l', 'o', 'a', 'd', 0x00, 0x02}; + shimClient.expect(publish, sizeof(publish)); + + rc = client.publish("topic", "payload", MQTT_QOS1, false); + IS_TRUE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_publish_qos2() { + IT("publishes with QoS 2"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = {0x20, 0x02, 0x00, 0x00}; + shimClient.respond(connack, 4); + + PubSubClient client(server, 1883, callback, shimClient); + bool rc = client.connect("client_test1"); + IS_TRUE(rc); + + // Example publish packet for QoS 2 (0x34) + byte publish[] = {0x34, 0x10, 0x00, 0x05, 't', 'o', 'p', 'i', 'c', 'p', 'a', 'y', 'l', 'o', 'a', 'd', 0x00, 0x02}; + shimClient.expect(publish, sizeof(publish)); + + rc = client.publish("topic", "payload", MQTT_QOS2, false); + IS_TRUE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_publish_P_qos1() { + IT("publishes using PROGMEM with QoS 1 retained"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte payload[] = {0x01, 0x02, 0x03, 0x04, 0x05}; + size_t length = sizeof(payload); + + byte connack[] = {0x20, 0x02, 0x00, 0x00}; + shimClient.respond(connack, sizeof(connack)); + + PubSubClient client(server, 1883, callback, shimClient); + bool rc = client.connect("client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x33, 0x0e, 0x00, 0x05, 't', 'o', 'p', 'i', 'c', 0x01, 0x02, 0x03, 0x04, 0x05, 0x00, 0x02}; + shimClient.expect(publish, sizeof(publish)); + + rc = client.publish_P("topic", payload, length, MQTT_QOS1, true); + IS_TRUE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_publish_P_qos2() { + IT("publishes using PROGMEM with QoS 2 retained"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte payload[] = {0x01, 0x02, 0x03, 0x04, 0x05}; + size_t length = sizeof(payload); + + byte connack[] = {0x20, 0x02, 0x00, 0x00}; + shimClient.respond(connack, sizeof(connack)); + + PubSubClient client(server, 1883, callback, shimClient); + bool rc = client.connect("client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x35, 0x0e, 0x00, 0x05, 't', 'o', 'p', 'i', 'c', 0x01, 0x02, 0x03, 0x04, 0x05, 0x00, 0x02}; + shimClient.expect(publish, sizeof(publish)); + + rc = client.publish_P("topic", payload, length, MQTT_QOS2, true); + IS_TRUE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + int main() { SUITE("Publish"); test_publish(); test_publish_bytes(); test_publish_retained(); test_publish_retained_2(); + test_publish_qos1(); + test_publish_qos2(); + test_publish_null_payload(); test_publish_not_connected(); + test_publish_empty_topic(); test_publish_too_long(); test_publish_P(); + test_publish_P_qos1(); + test_publish_P_qos2(); test_publish_P_too_long(); FINISH diff --git a/lib/pubsubclient3/tests/src/receive_spec.cpp b/lib/pubsubclient3/tests/src/receive_spec.cpp index 3635e9778..8cbe0efae 100644 --- a/lib/pubsubclient3/tests/src/receive_spec.cpp +++ b/lib/pubsubclient3/tests/src/receive_spec.cpp @@ -18,7 +18,7 @@ char lastPayload[1024]; size_t lastLength; // function declarations -void callback(char* topic, uint8_t* payload, size_t length); +void callback(char* topic, uint8_t* payload, size_t plength); void reset_callback(); int test_receive_callback(); int test_receive_stream(); @@ -36,12 +36,12 @@ void reset_callback() { lastLength = 0; } -void callback(char* topic, uint8_t* payload, size_t length) { - TRACE("Callback received topic=[" << topic << "] length=" << length << "\n") +void callback(char* topic, uint8_t* payload, size_t plength) { + TRACE("Callback received topic=[" << topic << "] plength=" << plength << "\n") callback_called = true; strcpy(lastTopic, topic); - memcpy(lastPayload, payload, length); - lastLength = length; + memcpy(lastPayload, payload, plength); + lastLength = plength; } int test_receive_callback() { diff --git a/lib/pubsubclient3/tests/src/subscribe_spec.cpp b/lib/pubsubclient3/tests/src/subscribe_spec.cpp index 8ae83e7ad..c00eddf75 100644 --- a/lib/pubsubclient3/tests/src/subscribe_spec.cpp +++ b/lib/pubsubclient3/tests/src/subscribe_spec.cpp @@ -7,7 +7,7 @@ byte server[] = {172, 16, 0, 2}; // function declarations -void callback(char* topic, uint8_t* payload, size_t length); +void callback(char* topic, uint8_t* payload, size_t plength); int test_subscribe_no_qos(); int test_subscribe_qos_1(); int test_subscribe_not_connected(); @@ -16,12 +16,12 @@ int test_subscribe_too_long(); int test_unsubscribe(); int test_unsubscribe_not_connected(); -void callback(_UNUSED_ char* topic, _UNUSED_ uint8_t* payload, _UNUSED_ size_t length) { +void callback(_UNUSED_ char* topic, _UNUSED_ uint8_t* payload, _UNUSED_ size_t plength) { // handle message arrived } int test_subscribe_no_qos() { - IT("subscribe without qos defaults to 0"); + IT("subscribe without QoS (defaults to 0)"); ShimClient shimClient; shimClient.setAllowConnect(true); @@ -46,7 +46,7 @@ int test_subscribe_no_qos() { } int test_subscribe_qos_1() { - IT("subscribes qos 1"); + IT("subscribe with QoS 1"); ShimClient shimClient; shimClient.setAllowConnect(true); @@ -62,7 +62,7 @@ int test_subscribe_qos_1() { byte suback[] = {0x90, 0x3, 0x0, 0x2, 0x1}; shimClient.respond(suback, 5); - rc = client.subscribe("topic", 1); + rc = client.subscribe("topic", MQTT_QOS1); IS_TRUE(rc); IS_FALSE(shimClient.error()); @@ -85,7 +85,7 @@ int test_subscribe_not_connected() { } int test_subscribe_invalid_qos() { - IT("subscribe fails with invalid qos values"); + IT("subscribe fails with invalid QoS values"); ShimClient shimClient; shimClient.setAllowConnect(true); @@ -96,7 +96,7 @@ int test_subscribe_invalid_qos() { bool rc = client.connect("client_test1"); IS_TRUE(rc); - rc = client.subscribe("topic", 2); + rc = client.subscribe("topic", MQTT_QOS2); IS_FALSE(rc); rc = client.subscribe("topic", 254); IS_FALSE(rc); @@ -119,13 +119,13 @@ int test_subscribe_too_long() { bool rc = client.connect("client_test1"); IS_TRUE(rc); - // max length should be allowed - // 0 1 2 3 4 5 6 7 8 9 0 1 2 - rc = client.subscribe("12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"); + // max length should be allowed (buffer size - MQTT_MAX_HEADER_SIZE - msgId (2) - topic length bytes (2) - QoS (1) = 118) + // 0 1 2 3 4 5 6 7 8 9 0 1 2 + rc = client.subscribe("1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678"); IS_TRUE(rc); - // 0 1 2 3 4 5 6 7 8 9 0 1 2 - rc = client.subscribe("123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"); + // 0 1 2 3 4 5 6 7 8 9 0 1 2 + rc = client.subscribe("12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"); IS_FALSE(rc); IS_FALSE(shimClient.error()); diff --git a/misc/ESP32P4/OTA_esp-hosted-mcu/Readme.md b/misc/ESP32P4/OTA_esp-hosted-mcu/Readme.md new file mode 100644 index 000000000..5fbaf51d8 --- /dev/null +++ b/misc/ESP32P4/OTA_esp-hosted-mcu/Readme.md @@ -0,0 +1,43 @@ +# ESP-Hosted SDIO WiFi + +The ESP32-p4 does not have a WiFi radio built in. +Therefore each ESP32-p4 board is equiped with a separate ESP32-C6 module to add WiFi-support. + +Both ESP32-p4 and ESP32-c6 are connected via a fast SDIO bus. + +The ESP32-c6 does run a firmware on its own. +This is the esp-hosted-mcu firmware. +For more information see [esp-hosted-mcu GitHub docs](https://github.com/espressif/esp-hosted-mcu/blob/main/docs/sdio.md) + + +The factory flashed firmware of this ESP32-c6 is quite old and typically is running version 0.0.6 + +N.B. Whenever the ESP32-C6 module is not yet flashed, or an update failed, the C6 can be flashed again with build 0.0.6 using [this site](https://espressif.github.io/arduino-esp32/c6-hosted/) +For this, a separate USB to serial adapter must be wired to the RX/TX pins of the C6, which are accessible via some pin header. (at least on all P4 boards currently available) + +ESP-IDF 5.5 is compiled to link against firmware 2.0.12 + +As stated in the [esp-hosted-mcu troubleshooting docs](https://github.com/espressif/esp-hosted-mcu/blob/main/docs/troubleshooting.md#3-make-sure-hosted-code-is-in-sync-for-master-and-slave), both the 'hosted code' (running on the ESP32-p4) and the 'slave code' (running on the ESP32-c6) should be kept in sync. + + + +# OTA update ESP-Hosted-MCU firmware + +In this folder, there is a freshly build firmware for the ESP32-C6, based on version 2.0.13 + +This file must be made available via some HTTP (not HTTPS) web server, which can be accessed by the ESP32-P4 via WiFi. + +For example upload the file to another ESPEasy node with at least 1.2 MByte free space on its file system. (e.g. some ESP32-S3 with 16M flash) + +Then this command must be given (adapt the URL for your own situation) + +``` +wifiotahostedmcu,"http://192.168.11.1/network_adapter.bin" +``` + +Make sure the complete URL is given, including `http://` and ending with `network_adapter.bin` + + +Keep an eye on the logs via the serial port of the P4 to see its progres. + +After about 30 - 60 seconds (depending also on the connection speed of the ESP board serving the file) the ESP32-P4 will reboot and start using the newly flashed WiFi firmware. \ No newline at end of file diff --git a/misc/ESP32P4/OTA_esp-hosted-mcu/network_adapter.bin b/misc/ESP32P4/OTA_esp-hosted-mcu/network_adapter.bin new file mode 100644 index 000000000..0a1b4b6fd Binary files /dev/null and b/misc/ESP32P4/OTA_esp-hosted-mcu/network_adapter.bin differ diff --git a/platformio.ini b/platformio.ini index 6430530f1..e040e8205 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,17 +22,20 @@ extra_configs = platformio_esp82xx_envs.ini platformio_esp32_envs.ini platformio_esp32_solo1.ini - platformio_esp32c3_envs.ini platformio_esp32s2_envs.ini platformio_esp32s3_envs.ini platformio_special_envs.ini platformio_esp32c2_envs.ini + platformio_esp32c3_envs.ini + platformio_esp32c5_envs.ini platformio_esp32c6_envs.ini + platformio_esp32c61_envs.ini + platformio_esp32p4_envs.ini ;default_envs = normal_ESP32_4M -default_envs = max_ESP32_16M8M_LittleFS_ETH -;default_envs = max_ESP32c5_8M1M_LittleFS_ETH +default_envs = max_ESP32_16M8M +;default_envs = max_ESP32c5_8M1M ;default_envs = normal_ESP32c6_4M316k_LittleFS_CDC ; default_envs = custom_ESP8266_4M1M diff --git a/platformio_core_defs.ini b/platformio_core_defs.ini index 81fb7e44b..d93cab796 100644 --- a/platformio_core_defs.ini +++ b/platformio_core_defs.ini @@ -162,18 +162,39 @@ extra_scripts = ${esp82xx_common.extra_scripts} +; ESP_IDF 5.5.0 +[core_esp32_IDF5_5_1__3_3_3_LittleFS] +;platform = https://github.com/Jason2866/platform-espressif32.git#Arduino/IDF55_gcc15 +;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/0410-1232-5.5/framework-arduinoespressif32-release_v5.5-2f303469.tar.xz + +;platform = https://github.com/Jason2866/platform-espressif32.git#23aed73e4e570d3b6608c15677fea01d6a722d94 +;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/1311-2008-5.5_orig/framework-arduinoespressif32-release_v5.5_orig-276436da.tar.xz + +platform = https://github.com/Jason2866/platform-espressif32.git#Arduino/IDF55_gcc15 +platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/0401-1225-5.5/framework-arduinoespressif32-release_v5.5-ad03770f.tar.xz + +custom_remove_include = true + +custom_component_remove = + espressif/esp_wifi_remote + espressif/esp-dsp + espressif/esp32-camera + espressif/libsodium + espressif/esp-modbus + espressif/qrcode + espressif/esp_insights + espressif/esp_diag_data_store + espressif/esp_diagnostics + espressif/esp_rainmaker + espressif/rmaker_common -; ESP_IDF 5.4.1 -[core_esp32_IDF5_4_1__3_2_0_LittleFS] -platform = https://github.com/Jason2866/platform-espressif32.git#Arduino/IDF54 -platform_packages = -;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/3743/framework-arduinoespressif32-all-release_v5.4-a4437683.zip build_flags = -DESP32_STAGE -DESP_IDF_VERSION_MAJOR=5 -DLIBRARIES_NO_LOG=1 -DDISABLE_SC16IS752_SPI ; -DCONFIG_PM_ENABLE=0 - -DETH_TYPE_JL1101_SUPPORTED + -DETH_TYPE_JL1101_SUPPORTED=1 + -DETH_PHY_LAN867X_SUPPORTED=1 ; PR_9453_FLUSH_TO_CLEAR_REVERTED -DPR_9453_FLUSH_TO_CLEAR=clear ; -DCONFIG_LWIP_L2_TO_L3_COPY @@ -186,11 +207,22 @@ build_flags = -DESP32_STAGE -include "sdkconfig.h" -include "ESPEasy_config.h" -include "esp32x_fixes.h" + -DUPDATE_NOCRYPT + -O3 ; -Wnull-dereference -lib_ignore = +lib_ignore = BLE + Zigbee + ESP RainMaker + ESP Insights + Matter + WiFiProv + TFLite Micro + ESP_SR + NetBIOS lib_extra_dirs = lib/lib_ssl + ; Regarding the define PR_9453_FLUSH_TO_CLEAR ; See: ; https://github.com/espressif/arduino-esp32/pull/8871/files diff --git a/platformio_esp32_envs.ini b/platformio_esp32_envs.ini index 9e261988d..14d72e373 100644 --- a/platformio_esp32_envs.ini +++ b/platformio_esp32_envs.ini @@ -1,5 +1,7 @@ -[esp32_base_idf5] -extends = common, core_esp32_IDF5_4_1__3_2_0_LittleFS + + +[esp32_base_idf5_5] +extends = common, core_esp32_IDF5_5_1__3_3_3_LittleFS upload_speed = 460800 upload_before_reset = default_reset upload_after_reset = hard_reset @@ -17,22 +19,23 @@ extra_scripts = pre:tools/pio/pre_esp32.py ; -mtext-section-literals may be required when building large builds ; However LTO cannot optimize builds with text section literals and thus will result in quite a lot larger builds (80k - 140k larger) build_unflags = -Wall - -fno-lto -build_flags = ${core_esp32_IDF5_4_1__3_2_0_LittleFS.build_flags} +; -fno-lto +build_flags = ${core_esp32_IDF5_5_1__3_3_3_LittleFS.build_flags} ; ${mqtt_flags.build_flags} -DMQTT_MAX_PACKET_SIZE=2048 -DCONFIG_FREERTOS_ASSERT_DISABLE -DCONFIG_LWIP_ESP_GRATUITOUS_ARP -fno-strict-aliasing - -flto=auto +; -flto=auto -Wswitch -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_NONE ; -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_INFO ; -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE -DLWIP_IPV6=1 + -DCONFIG_BT_NIMBLE_NVS_PERSIST=y monitor_filters = esp32_exception_decoder lib_ignore = - ${core_esp32_IDF5_4_1__3_2_0_LittleFS.lib_ignore} + ${core_esp32_IDF5_5_1__3_3_3_LittleFS.lib_ignore} [esp32_always] @@ -46,27 +49,31 @@ lib_ignore = ESP8266Ping TinyWireM LittleFS_esp32 Adafruit NeoPixel - ${esp32_base_idf5.lib_ignore} + ${esp32_base_idf5_5.lib_ignore} - -[esp32_common_LittleFS_ETH] -extends = esp32_base_idf5 -build_flags = ${esp32_base_idf5.build_flags} +[esp32_common_LittleFS] +extends = esp32_base_idf5_5 +build_flags = ${esp32_base_idf5_5.build_flags} ; -mtext-section-literals - -DFEATURE_ETHERNET=1 -DESP32_CLASSIC -DUSE_LITTLEFS -build_unflags = ${esp32_base_idf5.build_unflags} -extra_scripts = ${esp32_base_idf5.extra_scripts} +build_unflags = ${esp32_base_idf5_5.build_unflags} +extra_scripts = ${esp32_base_idf5_5.extra_scripts} board_build.filesystem = littlefs lib_ignore = ${esp32_always.lib_ignore} ESP32_ping ESP32 BLE Arduino - ${esp32_base_idf5.lib_ignore} + ${esp32_base_idf5_5.lib_ignore} -[esp32_IRExt_LittleFS_ETH] +[esp32_common_LittleFS_ETH] +extends = esp32_common_LittleFS +build_flags = ${esp32_common_LittleFS.build_flags} + -DFEATURE_ETHERNET=1 + + +[esp32_IRExt] extends = esp32_common_LittleFS_ETH lib_ignore = ${esp32_always.lib_ignore} ESP32_ping @@ -88,21 +95,21 @@ extra_scripts = ${esp32_common_LittleFS_ETH.extra_scripts} pre:tools/pio/pre_custom_esp32.py -[env:custom_ESP32_4M316k_LittleFS_ETH] +[env:custom_ESP32_4M316k] extends = esp32_custom_base_LittleFS board = esp32_4M build_flags = ${esp32_custom_base_LittleFS.build_flags} -DFEATURE_ETHERNET=1 -[env:custom_ESP32_16M8M_LittleFS_ETH] -extends = env:custom_ESP32_4M316k_LittleFS_ETH +[env:custom_ESP32_16M8M] +extends = env:custom_ESP32_4M316k board = esp32_16M8M board_upload.flash_size = 16MB -build_flags = ${env:custom_ESP32_4M316k_LittleFS_ETH.build_flags} +build_flags = ${env:custom_ESP32_4M316k.build_flags} -[env:custom_IR_ESP32_4M316k_LittleFS_ETH] +[env:custom_IR_ESP32_4M316k] extends = esp32_common_LittleFS_ETH board = esp32_4M build_flags = ${esp32_common_LittleFS_ETH.build_flags} @@ -115,24 +122,25 @@ extra_scripts = ${esp32_common_LittleFS_ETH.extra_scripts} pre:tools/pio/pre_custom_esp32_IR.py pre:tools/pio/ir_build_check.py -[env:custom_ESP32_4M2M_NO_OTA_LittleFS_ETH] -extends = env:custom_ESP32_4M316k_LittleFS_ETH +[env:custom_ESP32_4M2M_NO_OTA] +extends = env:custom_ESP32_4M316k board = esp32_4M2M -build_flags = ${env:custom_ESP32_4M316k_LittleFS_ETH.build_flags} +build_flags = ${env:custom_ESP32_4M316k.build_flags} -DNO_HTTP_UPDATER -[env:collection_A_ESP32_4M316k_LittleFS_ETH] +[env:collection_A_ESP32_4M316k] extends = esp32_common_LittleFS_ETH board = esp32_4M build_flags = ${esp32_common_LittleFS_ETH.build_flags} -DFEATURE_ARDUINO_OTA=1 -DPLUGIN_SET_COLLECTION_ESP32 + -DPLUGIN_SET_COLLECTION_A_ESP32 -DCOLLECTION_FEATURE_RTTTL=1 -[env:collection_B_ESP32_4M316k_LittleFS_ETH] +[env:collection_B_ESP32_4M316k] extends = esp32_common_LittleFS_ETH board = esp32_4M build_flags = ${esp32_common_LittleFS_ETH.build_flags} @@ -141,7 +149,7 @@ build_flags = ${esp32_common_LittleFS_ETH.build_flags} -DCOLLECTION_FEATURE_RTTTL=1 -[env:collection_C_ESP32_4M316k_LittleFS_ETH] +[env:collection_C_ESP32_4M316k] extends = esp32_common_LittleFS_ETH board = esp32_4M build_flags = ${esp32_common_LittleFS_ETH.build_flags} @@ -150,7 +158,7 @@ build_flags = ${esp32_common_LittleFS_ETH.build_flags} -DCOLLECTION_FEATURE_RTTTL=1 -[env:collection_D_ESP32_4M316k_LittleFS_ETH] +[env:collection_D_ESP32_4M316k] extends = esp32_common_LittleFS_ETH board = esp32_4M build_flags = ${esp32_common_LittleFS_ETH.build_flags} @@ -159,7 +167,7 @@ build_flags = ${esp32_common_LittleFS_ETH.build_flags} -DCOLLECTION_FEATURE_RTTTL=1 -[env:collection_E_ESP32_4M316k_LittleFS_ETH] +[env:collection_E_ESP32_4M316k] extends = esp32_common_LittleFS_ETH board = esp32_4M build_flags = ${esp32_common_LittleFS_ETH.build_flags} @@ -168,7 +176,7 @@ build_flags = ${esp32_common_LittleFS_ETH.build_flags} -DCOLLECTION_FEATURE_RTTTL=1 -[env:collection_F_ESP32_4M316k_LittleFS_ETH] +[env:collection_F_ESP32_4M316k] extends = esp32_common_LittleFS_ETH board = esp32_4M build_flags = ${esp32_common_LittleFS_ETH.build_flags} @@ -177,7 +185,7 @@ build_flags = ${esp32_common_LittleFS_ETH.build_flags} -DCOLLECTION_FEATURE_RTTTL=1 -[env:collection_G_ESP32_4M316k_LittleFS_ETH] +[env:collection_G_ESP32_4M316k] extends = esp32_common_LittleFS_ETH board = esp32_4M build_flags = ${esp32_common_LittleFS_ETH.build_flags} @@ -186,50 +194,66 @@ build_flags = ${esp32_common_LittleFS_ETH.build_flags} -DCOLLECTION_FEATURE_RTTTL=1 - -[env:collection_A_ESP32_IRExt_4M316k_LittleFS_ETH] -extends = esp32_IRExt_LittleFS_ETH +[env:collection_H_ESP32_4M316k] +extends = esp32_common_LittleFS_ETH board = esp32_4M -build_flags = ${esp32_IRExt_LittleFS_ETH.build_flags} +build_flags = ${esp32_common_LittleFS_ETH.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_SET_COLLECTION_H_ESP32 + -DCOLLECTION_FEATURE_RTTTL=1 + + + +[env:collection_A_ESP32_IRExt_4M316k] +extends = esp32_IRExt +board = esp32_4M +build_flags = ${esp32_IRExt.build_flags} -DPLUGIN_SET_COLLECTION_ESP32 + -DPLUGIN_SET_COLLECTION_A_ESP32 -[env:collection_B_ESP32_IRExt_4M316k_LittleFS_ETH] -extends = esp32_IRExt_LittleFS_ETH +[env:collection_B_ESP32_IRExt_4M316k] +extends = esp32_IRExt board = esp32_4M -build_flags = ${esp32_IRExt_LittleFS_ETH.build_flags} +build_flags = ${esp32_IRExt.build_flags} -DPLUGIN_SET_COLLECTION_B_ESP32 -[env:collection_C_ESP32_IRExt_4M316k_LittleFS_ETH] -extends = esp32_IRExt_LittleFS_ETH +[env:collection_C_ESP32_IRExt_4M316k] +extends = esp32_IRExt board = esp32_4M -build_flags = ${esp32_IRExt_LittleFS_ETH.build_flags} +build_flags = ${esp32_IRExt.build_flags} -DPLUGIN_SET_COLLECTION_C_ESP32 -[env:collection_D_ESP32_IRExt_4M316k_LittleFS_ETH] -extends = esp32_IRExt_LittleFS_ETH +[env:collection_D_ESP32_IRExt_4M316k] +extends = esp32_IRExt board = esp32_4M -build_flags = ${esp32_IRExt_LittleFS_ETH.build_flags} +build_flags = ${esp32_IRExt.build_flags} -DPLUGIN_SET_COLLECTION_D_ESP32 -[env:collection_E_ESP32_IRExt_4M316k_LittleFS_ETH] -extends = esp32_IRExt_LittleFS_ETH +[env:collection_E_ESP32_IRExt_4M316k] +extends = esp32_IRExt board = esp32_4M -build_flags = ${esp32_IRExt_LittleFS_ETH.build_flags} +build_flags = ${esp32_IRExt.build_flags} -DPLUGIN_SET_COLLECTION_E_ESP32 -[env:collection_F_ESP32_IRExt_4M316k_LittleFS_ETH] -extends = esp32_IRExt_LittleFS_ETH +[env:collection_F_ESP32_IRExt_4M316k] +extends = esp32_IRExt board = esp32_4M -build_flags = ${esp32_IRExt_LittleFS_ETH.build_flags} +build_flags = ${esp32_IRExt.build_flags} -DPLUGIN_SET_COLLECTION_F_ESP32 -[env:collection_G_ESP32_IRExt_4M316k_LittleFS_ETH] -extends = esp32_IRExt_LittleFS_ETH +[env:collection_G_ESP32_IRExt_4M316k] +extends = esp32_IRExt board = esp32_4M -build_flags = ${esp32_IRExt_LittleFS_ETH.build_flags} +build_flags = ${esp32_IRExt.build_flags} -DPLUGIN_SET_COLLECTION_G_ESP32 -[env:energy_ESP32_4M316k_LittleFS_ETH] +[env:collection_H_ESP32_IRExt_4M316k] +extends = esp32_IRExt +board = esp32_4M +build_flags = ${esp32_IRExt.build_flags} + -DPLUGIN_SET_COLLECTION_H_ESP32 + +[env:energy_ESP32_4M316k] extends = esp32_common_LittleFS_ETH board = esp32_4M build_flags = ${esp32_common_LittleFS_ETH.build_flags} @@ -237,18 +261,27 @@ build_flags = ${esp32_common_LittleFS_ETH.build_flags} -DPLUGIN_ENERGY_COLLECTION -[env:display_ESP32_4M316k_LittleFS_ETH] +[env:display_A_ESP32_4M316k] +extends = esp32_common_LittleFS_ETH +board = esp32_4M +build_flags = ${esp32_common_LittleFS.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_DISPLAY_A_COLLECTION + -D ST7789_EXTRA_INIT=1 + -D P116_EXTRA_ST7789=1 + +[env:display_B_ESP32_4M316k] extends = esp32_common_LittleFS_ETH board = esp32_4M build_flags = ${esp32_common_LittleFS_ETH.build_flags} -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_DISPLAY_COLLECTION + -DPLUGIN_DISPLAY_B_COLLECTION -D ST7789_EXTRA_INIT=1 -D P116_EXTRA_ST7789=1 - -[env:climate_ESP32_4M316k_LittleFS_ETH] + +[env:climate_A_ESP32_4M316k] extends = esp32_common_LittleFS_ETH board = esp32_4M lib_ignore = ${esp32_always.lib_ignore} @@ -256,10 +289,20 @@ lib_ignore = ${esp32_always.lib_ignore} ${esp32_common_LittleFS_ETH.lib_ignore} ${no_ir.lib_ignore} build_flags = ${esp32_common_LittleFS_ETH.build_flags} - -DPLUGIN_CLIMATE_COLLECTION + -DPLUGIN_CLIMATE_A_COLLECTION + +[env:climate_B_ESP32_4M316k] +extends = esp32_common_LittleFS_ETH +board = esp32_4M +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping + ${esp32_common_LittleFS_ETH.lib_ignore} + ${no_ir.lib_ignore} +build_flags = ${esp32_common_LittleFS_ETH.build_flags} + -DPLUGIN_CLIMATE_B_COLLECTION -[env:neopixel_ESP32_4M316k_LittleFS_ETH] +[env:neopixel_ESP32_4M316k] extends = esp32_common_LittleFS_ETH board = esp32_4M build_flags = ${esp32_common_LittleFS_ETH.build_flags} @@ -270,7 +313,7 @@ build_flags = ${esp32_common_LittleFS_ETH.build_flags} -[env:custom_IR_ESP32_16M8M_LittleFS_ETH] +[env:custom_IR_ESP32_16M8M] extends = esp32_common_LittleFS_ETH board = esp32_16M8M board_upload.flash_size = 16MB @@ -286,7 +329,7 @@ extra_scripts = ${esp32_common_LittleFS_ETH.extra_scripts} -[env:normal_ESP32_4M316k_LittleFS_ETH] +[env:normal_ESP32_4M316k] extends = esp32_common_LittleFS_ETH board = esp32_4M lib_ignore = ${esp32_always.lib_ignore} @@ -297,10 +340,10 @@ build_flags = ${esp32_common_LittleFS_ETH.build_flags} -[env:normal_ESP32_IRExt_4M316k_LittleFS_ETH] -extends = esp32_IRExt_LittleFS_ETH +[env:normal_ESP32_IRExt_4M316k] +extends = esp32_IRExt board = esp32_4M -build_flags = ${esp32_IRExt_LittleFS_ETH.build_flags} +build_flags = ${esp32_IRExt.build_flags} @@ -308,7 +351,7 @@ build_flags = ${esp32_IRExt_LittleFS_ETH.build_flags} ; ESP32 MAX builds 16M flash ------------------------------ ; A Lolin D32 PRO with 16MB Flash, allowing 4MB sketch size, and file storage using LittleFS filesystem -[env:max_ESP32_16M8M_LittleFS_ETH] +[env:max_ESP32_16M8M] extends = esp32_common_LittleFS_ETH board = esp32_16M8M board_upload.flash_size = 16MB @@ -325,7 +368,7 @@ extra_scripts = ${esp32_common_LittleFS_ETH.extra_scripts} board_build.filesystem = littlefs ; If you have a board with Ethernet integrated and 16MB Flash, then this configuration could be enabled, it's based on the max_ESP32_16M8M_LittleFS definition -; [env:max_ESP32_16M8M_LittleFS_ETH] +; [env:max_ESP32_16M8M] ; extends = env:max_ESP32_16M8M_LittleFS ; board = ${env:max_ESP32_16M8M_LittleFS.board} ; build_flags = ${env:max_ESP32_16M8M_LittleFS.build_flags} diff --git a/platformio_esp32_solo1.ini b/platformio_esp32_solo1.ini index 0c755e934..2307febd7 100644 --- a/platformio_esp32_solo1.ini +++ b/platformio_esp32_solo1.ini @@ -1,22 +1,22 @@ ; IDF 5.3.1 [esp32_solo1_common_LittleFS] -extends = esp32_base_idf5 +extends = esp32_base_idf5_5 board = esp32_solo1_4M ;platform = https://github.com/Jason2866/platform-espressif32.git#Arduino/IDF53 ;platform_packages = framework-arduino-solo1 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/3507/framework-arduinoespressif32-solo1-release_v5.3-2952ac93.zip -build_flags = ${esp32_base_idf5.build_flags} +build_flags = ${esp32_base_idf5_5.build_flags} -DFEATURE_ARDUINO_OTA=1 -DUSE_LITTLEFS -DCORE32SOLO1 -lib_ignore = ${esp32_base_idf5.lib_ignore} -extra_scripts = ${esp32_base_idf5.extra_scripts} -build_unflags = ${esp32_base_idf5.build_unflags} +lib_ignore = ${esp32_base_idf5_5.lib_ignore} +extra_scripts = ${esp32_base_idf5_5.extra_scripts} +build_unflags = ${esp32_base_idf5_5.build_unflags} -fexceptions board_build.filesystem = littlefs -[env:custom_ESP32solo1_4M316k_LittleFS_ETH] +[env:custom_ESP32solo1_4M316k] extends = esp32_solo1_common_LittleFS build_flags = ${esp32_solo1_common_LittleFS.build_flags} -DPLUGIN_BUILD_CUSTOM @@ -25,7 +25,7 @@ extra_scripts = ${esp32_solo1_common_LittleFS.extra_scripts} pre:tools/pio/pre_custom_esp32.py -[env:normal_ESP32solo1_4M316k_LittleFS_ETH] +[env:normal_ESP32solo1_4M316k] extends = esp32_solo1_common_LittleFS build_flags = ${esp32_solo1_common_LittleFS.build_flags} -DFEATURE_ETHERNET=1 @@ -34,16 +34,23 @@ lib_ignore = ${esp32_solo1_common_LittleFS.lib_ignore} extra_scripts = ${esp32_solo1_common_LittleFS.extra_scripts} -[env:energy_ESP32solo1_4M316k_LittleFS_ETH] +[env:energy_ESP32solo1_4M316k] extends = esp32_solo1_common_LittleFS build_flags = ${esp32_solo1_common_LittleFS.build_flags} -D PLUGIN_ENERGY_COLLECTION -DFEATURE_ETHERNET=1 extra_scripts = ${esp32_solo1_common_LittleFS.extra_scripts} -[env:climate_ESP32solo1_4M316k_LittleFS_ETH] +[env:climate_A_ESP32solo1_4M316k] extends = esp32_solo1_common_LittleFS build_flags = ${esp32_solo1_common_LittleFS.build_flags} - -D PLUGIN_CLIMATE_COLLECTION + -D PLUGIN_CLIMATE_A_COLLECTION + -DFEATURE_ETHERNET=1 +extra_scripts = ${esp32_solo1_common_LittleFS.extra_scripts} + +[env:climate_B_ESP32solo1_4M316k] +extends = esp32_solo1_common_LittleFS +build_flags = ${esp32_solo1_common_LittleFS.build_flags} + -D PLUGIN_CLIMATE_B_COLLECTION -DFEATURE_ETHERNET=1 extra_scripts = ${esp32_solo1_common_LittleFS.extra_scripts} diff --git a/platformio_esp32c2_envs.ini b/platformio_esp32c2_envs.ini index de68b96fc..fdebe7b23 100644 --- a/platformio_esp32c2_envs.ini +++ b/platformio_esp32c2_envs.ini @@ -1,16 +1,15 @@ ; No Ethernet for ESP32-C2 as this one already hasn't much RAM. ; Thus Jason removed Ethernet support for ESP32-C2 from the PIO platform_packages [esp32c2_common_LittleFS] -extends = esp32_base_idf5 -build_flags = ${esp32_base_idf5.build_flags} +extends = esp32_base_idf5_5 +build_flags = ${esp32_base_idf5_5.build_flags} -DFEATURE_ARDUINO_OTA=1 - -DUSE_LITTLEFS -DESP32C2 -extra_scripts = ${esp32_base_idf5.extra_scripts} -build_unflags = ${esp32_base_idf5.build_unflags} +extra_scripts = ${esp32_base_idf5_5.extra_scripts} +build_unflags = ${esp32_base_idf5_5.build_unflags} -fexceptions board_build.filesystem = littlefs -lib_ignore = ${esp32_base_idf5.lib_ignore} +lib_ignore = ${esp32_base_idf5_5.lib_ignore} NeoPixelBus NeoPixelBus_wrapper Adafruit NeoMatrix via NeoPixelBus diff --git a/platformio_esp32c3_envs.ini b/platformio_esp32c3_envs.ini index 874ecd23d..e9108a454 100644 --- a/platformio_esp32c3_envs.ini +++ b/platformio_esp32c3_envs.ini @@ -1,19 +1,19 @@ [esp32c3_common_LittleFS] -extends = esp32_base_idf5 -build_flags = ${esp32_base_idf5.build_flags} +extends = esp32_base_idf5_5 +build_flags = ${esp32_base_idf5_5.build_flags} -DFEATURE_ARDUINO_OTA=1 -DFEATURE_ETHERNET=1 -DUSE_LITTLEFS -DESP32C3 -extra_scripts = ${esp32_base_idf5.extra_scripts} -build_unflags = ${esp32_base_idf5.build_unflags} +extra_scripts = ${esp32_base_idf5_5.extra_scripts} +build_unflags = ${esp32_base_idf5_5.build_unflags} -fexceptions board_build.filesystem = littlefs -lib_ignore = ${esp32_base_idf5.lib_ignore} +lib_ignore = ${esp32_base_idf5_5.lib_ignore} board = esp32c3cdc -[env:custom_ESP32c3_4M316k_LittleFS_ETH] +[env:custom_ESP32c3_4M316k] extends = esp32c3_common_LittleFS build_flags = ${esp32c3_common_LittleFS.build_flags} -DPLUGIN_BUILD_CUSTOM @@ -22,81 +22,100 @@ extra_scripts = ${esp32c3_common_LittleFS.extra_scripts} -[env:normal_ESP32c3_4M316k_LittleFS_ETH] +[env:normal_ESP32c3_4M316k] extends = esp32c3_common_LittleFS build_flags = ${esp32c3_common_LittleFS.build_flags} lib_ignore = ${esp32c3_common_LittleFS.lib_ignore} ${no_ir.lib_ignore} -[env:collection_A_ESP32c3_4M316k_LittleFS_ETH] +[env:collection_A_ESP32c3_4M316k] extends = esp32c3_common_LittleFS build_flags = ${esp32c3_common_LittleFS.build_flags} -DPLUGIN_SET_COLLECTION_ESP32 + -DPLUGIN_SET_COLLECTION_A_ESP32 -DCOLLECTION_FEATURE_RTTTL=1 -[env:collection_B_ESP32c3_4M316k_LittleFS_ETH] +[env:collection_B_ESP32c3_4M316k] extends = esp32c3_common_LittleFS build_flags = ${esp32c3_common_LittleFS.build_flags} -DPLUGIN_SET_COLLECTION_B_ESP32 -DCOLLECTION_FEATURE_RTTTL=1 -[env:collection_C_ESP32c3_4M316k_LittleFS_ETH] +[env:collection_C_ESP32c3_4M316k] extends = esp32c3_common_LittleFS build_flags = ${esp32c3_common_LittleFS.build_flags} -DPLUGIN_SET_COLLECTION_C_ESP32 -DCOLLECTION_FEATURE_RTTTL=1 -[env:collection_D_ESP32c3_4M316k_LittleFS_ETH] +[env:collection_D_ESP32c3_4M316k] extends = esp32c3_common_LittleFS build_flags = ${esp32c3_common_LittleFS.build_flags} -DPLUGIN_SET_COLLECTION_D_ESP32 -DCOLLECTION_FEATURE_RTTTL=1 -[env:collection_E_ESP32c3_4M316k_LittleFS_ETH] +[env:collection_E_ESP32c3_4M316k] extends = esp32c3_common_LittleFS build_flags = ${esp32c3_common_LittleFS.build_flags} -DPLUGIN_SET_COLLECTION_E_ESP32 -DCOLLECTION_FEATURE_RTTTL=1 -[env:collection_F_ESP32c3_4M316k_LittleFS_ETH] +[env:collection_F_ESP32c3_4M316k] extends = esp32c3_common_LittleFS build_flags = ${esp32c3_common_LittleFS.build_flags} -DPLUGIN_SET_COLLECTION_F_ESP32 -DCOLLECTION_FEATURE_RTTTL=1 -[env:collection_G_ESP32c3_4M316k_LittleFS_ETH] +[env:collection_G_ESP32c3_4M316k] extends = esp32c3_common_LittleFS build_flags = ${esp32c3_common_LittleFS.build_flags} -DPLUGIN_SET_COLLECTION_G_ESP32 -DCOLLECTION_FEATURE_RTTTL=1 +[env:collection_H_ESP32c3_4M316k] +extends = esp32c3_common_LittleFS +build_flags = ${esp32c3_common_LittleFS.build_flags} + -DPLUGIN_SET_COLLECTION_H_ESP32 + -DCOLLECTION_FEATURE_RTTTL=1 -[env:energy_ESP32c3_4M316k_LittleFS_ETH] + +[env:energy_ESP32c3_4M316k] extends = esp32c3_common_LittleFS build_flags = ${esp32c3_common_LittleFS.build_flags} -D PLUGIN_ENERGY_COLLECTION -[env:display_ESP32c3_4M316k_LittleFS_ETH] +[env:display_A_ESP32c3_4M316k] extends = esp32c3_common_LittleFS build_flags = ${esp32c3_common_LittleFS.build_flags} - -D PLUGIN_DISPLAY_COLLECTION + -D PLUGIN_DISPLAY_A_COLLECTION -D ST7789_EXTRA_INIT=1 -D P116_EXTRA_ST7789=1 -[env:climate_ESP32c3_4M316k_LittleFS_ETH] +[env:display_B_ESP32c3_4M316k] extends = esp32c3_common_LittleFS build_flags = ${esp32c3_common_LittleFS.build_flags} - -D PLUGIN_CLIMATE_COLLECTION + -D PLUGIN_DISPLAY_B_COLLECTION + -D ST7789_EXTRA_INIT=1 + -D P116_EXTRA_ST7789=1 -[env:neopixel_ESP32c3_4M316k_LittleFS_ETH] +[env:climate_A_ESP32c3_4M316k] +extends = esp32c3_common_LittleFS +build_flags = ${esp32c3_common_LittleFS.build_flags} + -D PLUGIN_CLIMATE_A_COLLECTION + +[env:climate_B_ESP32c3_4M316k] +extends = esp32c3_common_LittleFS +build_flags = ${esp32c3_common_LittleFS.build_flags} + -D PLUGIN_CLIMATE_B_COLLECTION + +[env:neopixel_ESP32c3_4M316k] extends = esp32c3_common_LittleFS build_flags = ${esp32c3_common_LittleFS.build_flags} -DFEATURE_ARDUINO_OTA=1 -DFEATURE_SD=1 -DPLUGIN_NEOPIXEL_COLLECTION -[env:max_ESP32c3_16M8M_LittleFS_ETH] +[env:max_ESP32c3_16M8M] extends = esp32c3_common_LittleFS board = esp32c3cdc-16M build_flags = ${esp32c3_common_LittleFS.build_flags} diff --git a/platformio_esp32c5_envs.ini b/platformio_esp32c5_envs.ini new file mode 100644 index 000000000..d84a15971 --- /dev/null +++ b/platformio_esp32c5_envs.ini @@ -0,0 +1,29 @@ + + +[esp32c5_common_LittleFS] +extends = esp32_base_idf5_5 +build_flags = ${esp32_base_idf5_5.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DUSE_LITTLEFS + -DESP32C5 +extra_scripts = ${esp32_base_idf5_5.extra_scripts} +build_unflags = ${esp32_base_idf5_5.build_unflags} + -fexceptions +board_build.filesystem = littlefs +lib_ignore = ${esp32_base_idf5_5.lib_ignore} +board = esp32c5cdc + + +[env:max_ESP32c5_8M1M] +extends = esp32c5_common_LittleFS +board = esp32c5cdc-8M +build_flags = ${esp32c5_common_LittleFS.build_flags} + -DFEATURE_ETHERNET=1 + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_BUILD_MAX_ESP32 + -DPLUGIN_BUILD_IR_EXTENDED + -D ST7789_EXTRA_INIT=1 + -D P116_EXTRA_ST7789=1 +extra_scripts = ${esp32c5_common_LittleFS.extra_scripts} + + diff --git a/platformio_esp32c61_envs.ini b/platformio_esp32c61_envs.ini new file mode 100644 index 000000000..fbcee4542 --- /dev/null +++ b/platformio_esp32c61_envs.ini @@ -0,0 +1,32 @@ +[esp32c61_common_LittleFS] +extends = esp32_base_idf5_5 +build_flags = ${esp32_base_idf5_5.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DUSE_LITTLEFS + -DESP32C61 + -DDISABLE_NEOPIXEL_PLUGINS=1 +extra_scripts = ${esp32_base_idf5_5.extra_scripts} +build_unflags = ${esp32_base_idf5_5.build_unflags} + -fexceptions +board_build.filesystem = littlefs +lib_ignore = ${esp32_base_idf5_5.lib_ignore} + NeoPixelBus + Adafruit NeoPixel + NeoPixelBus_wrapper + Adafruit NeoMatrix via NeoPixelBus +board = esp32c61cdc + + +[env:max_ESP32c61_8M1M] +extends = esp32c61_common_LittleFS +board = esp32c61cdc-8M +build_flags = ${esp32c61_common_LittleFS.build_flags} + -DFEATURE_ETHERNET=1 + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_BUILD_MAX_ESP32 + -DPLUGIN_BUILD_IR_EXTENDED + -D ST7789_EXTRA_INIT=1 + -D P116_EXTRA_ST7789=1 +extra_scripts = ${esp32c61_common_LittleFS.extra_scripts} + + diff --git a/platformio_esp32c6_envs.ini b/platformio_esp32c6_envs.ini index 084e231ee..37856d019 100644 --- a/platformio_esp32c6_envs.ini +++ b/platformio_esp32c6_envs.ini @@ -1,18 +1,19 @@ [esp32c6_common_LittleFS] -extends = esp32_base_idf5 -build_flags = ${esp32_base_idf5.build_flags} +extends = esp32_base_idf5_5 +build_flags = ${esp32_base_idf5_5.build_flags} -DFEATURE_ARDUINO_OTA=1 -DUSE_LITTLEFS -DESP32C6 -extra_scripts = ${esp32_base_idf5.extra_scripts} -build_unflags = ${esp32_base_idf5.build_unflags} + -DFEATURE_ETHERNET +extra_scripts = ${esp32_base_idf5_5.extra_scripts} +build_unflags = ${esp32_base_idf5_5.build_unflags} -fexceptions board_build.filesystem = littlefs -lib_ignore = ${esp32_base_idf5.lib_ignore} +lib_ignore = ${esp32_base_idf5_5.lib_ignore} board = esp32c6cdc -[env:custom_ESP32c6_4M316k_LittleFS_ETH] +[env:custom_ESP32c6_4M316k] extends = esp32c6_common_LittleFS build_flags = ${esp32c6_common_LittleFS.build_flags} -DPLUGIN_BUILD_CUSTOM @@ -21,32 +22,44 @@ extra_scripts = ${esp32c6_common_LittleFS.extra_scripts} pre:tools/pio/pre_custom_esp32c6.py -[env:normal_ESP32c6_4M316k_LittleFS_ETH] +[env:normal_ESP32c6_4M316k] extends = esp32c6_common_LittleFS build_flags = ${esp32c6_common_LittleFS.build_flags} -DFEATURE_ETHERNET=1 lib_ignore = ${esp32c6_common_LittleFS.lib_ignore} ${no_ir.lib_ignore} -[env:energy_ESP32c6_4M316k_LittleFS_ETH] +[env:energy_ESP32c6_4M316k] extends = esp32c6_common_LittleFS build_flags = ${esp32c6_common_LittleFS.build_flags} -D PLUGIN_ENERGY_COLLECTION -[env:display_ESP32c6_4M316k_LittleFS_ETH] +[env:display_A_ESP32c6_4M316k] extends = esp32c6_common_LittleFS build_flags = ${esp32c6_common_LittleFS.build_flags} - -D PLUGIN_DISPLAY_COLLECTION + -D PLUGIN_DISPLAY_A_COLLECTION -D ST7789_EXTRA_INIT=1 -D P116_EXTRA_ST7789=1 -[env:climate_ESP32c6_4M316k_LittleFS_ETH] +[env:display_B_ESP32c6_4M316k] extends = esp32c6_common_LittleFS build_flags = ${esp32c6_common_LittleFS.build_flags} - -D PLUGIN_CLIMATE_COLLECTION + -D PLUGIN_DISPLAY_B_COLLECTION + -D ST7789_EXTRA_INIT=1 + -D P116_EXTRA_ST7789=1 -[env:neopixel_ESP32c6_4M316k_LittleFS_ETH] +[env:climate_A_ESP32c6_4M316k] +extends = esp32c6_common_LittleFS +build_flags = ${esp32c6_common_LittleFS.build_flags} + -D PLUGIN_CLIMATE_A_COLLECTION + +[env:climate_B_ESP32c6_4M316k] +extends = esp32c6_common_LittleFS +build_flags = ${esp32c6_common_LittleFS.build_flags} + -D PLUGIN_CLIMATE_B_COLLECTION + +[env:neopixel_ESP32c6_4M316k] extends = esp32c6_common_LittleFS build_flags = ${esp32c6_common_LittleFS.build_flags} -DFEATURE_ARDUINO_OTA=1 @@ -54,7 +67,7 @@ build_flags = ${esp32c6_common_LittleFS.build_flags} -DPLUGIN_NEOPIXEL_COLLECTION -[env:max_ESP32c6_8M1M_LittleFS_ETH] +[env:max_ESP32c6_8M1M] extends = esp32c6_common_LittleFS board = esp32c6cdc-8M build_flags = ${esp32c6_common_LittleFS.build_flags} @@ -67,7 +80,7 @@ build_flags = ${esp32c6_common_LittleFS.build_flags} extra_scripts = ${esp32c6_common_LittleFS.extra_scripts} -[env:max_ESP32c6_16M8M_LittleFS_ETH] +[env:max_ESP32c6_16M8M] extends = esp32c6_common_LittleFS board = esp32c6cdc-16M build_flags = ${esp32c6_common_LittleFS.build_flags} @@ -80,3 +93,5 @@ build_flags = ${esp32c6_common_LittleFS.build_flags} extra_scripts = ${esp32c6_common_LittleFS.extra_scripts} + + diff --git a/platformio_esp32p4_envs.ini b/platformio_esp32p4_envs.ini new file mode 100644 index 000000000..fb4bd2dcd --- /dev/null +++ b/platformio_esp32p4_envs.ini @@ -0,0 +1,29 @@ + + +[esp32p4_common_LittleFS] +extends = esp32_base_idf5_5 +build_flags = ${esp32_base_idf5_5.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DUSE_LITTLEFS + -DESP32P4 +extra_scripts = ${esp32_base_idf5_5.extra_scripts} +build_unflags = ${esp32_base_idf5_5.build_unflags} + -fexceptions +board_build.filesystem = littlefs +lib_ignore = ${esp32_base_idf5_5.lib_ignore} +board = esp32p4_ev + + +[env:max_ESP32p4_16M8M] +extends = esp32p4_common_LittleFS +board = esp32p4_ev +build_flags = ${esp32p4_common_LittleFS.build_flags} + -DFEATURE_ETHERNET=1 + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_BUILD_MAX_ESP32 + -DPLUGIN_BUILD_IR_EXTENDED + -D ST7789_EXTRA_INIT=1 + -D P116_EXTRA_ST7789=1 +extra_scripts = ${esp32p4_common_LittleFS.extra_scripts} + + diff --git a/platformio_esp32s2_envs.ini b/platformio_esp32s2_envs.ini index 98c9a46a8..6b7e2b2c0 100644 --- a/platformio_esp32s2_envs.ini +++ b/platformio_esp32s2_envs.ini @@ -1,18 +1,18 @@ [esp32s2_common_LittleFS] -extends = esp32_base_idf5 -build_flags = ${esp32_base_idf5.build_flags} +extends = esp32_base_idf5_5 +build_flags = ${esp32_base_idf5_5.build_flags} -DFEATURE_ARDUINO_OTA=1 -DFEATURE_ETHERNET=1 -DUSE_LITTLEFS -DESP32S2 -extra_scripts = ${esp32_base_idf5.extra_scripts} -build_unflags = ${esp32_base_idf5.build_unflags} +extra_scripts = ${esp32_base_idf5_5.extra_scripts} +build_unflags = ${esp32_base_idf5_5.build_unflags} -fexceptions board_build.filesystem = littlefs -lib_ignore = ${esp32_base_idf5.lib_ignore} +lib_ignore = ${esp32_base_idf5_5.lib_ignore} -[env:neopixel_ESP32s2_4M316k_LittleFS_ETH] +[env:neopixel_ESP32s2_4M316k] extends = esp32s2_common_LittleFS board = esp32s2cdc build_flags = ${esp32s2_common_LittleFS.build_flags} @@ -22,7 +22,7 @@ build_flags = ${esp32s2_common_LittleFS.build_flags} -[env:custom_IR_ESP32s2_4M316k_LittleFS_ETH] +[env:custom_IR_ESP32s2_4M316k] extends = esp32s2_common_LittleFS board = esp32s2cdc build_flags = ${esp32s2_common_LittleFS.build_flags} @@ -35,7 +35,7 @@ extra_scripts = ${esp32s2_common_LittleFS.extra_scripts} pre:tools/pio/ir_build_check.py -[env:custom_ESP32s2_4M316k_LittleFS_ETH] +[env:custom_ESP32s2_4M316k] extends = esp32s2_common_LittleFS board = esp32s2cdc lib_ignore = ${esp32s2_common_LittleFS.lib_ignore} @@ -48,7 +48,7 @@ extra_scripts = ${esp32s2_common_LittleFS.extra_scripts} -[env:normal_ESP32s2_4M316k_LittleFS_ETH] +[env:normal_ESP32s2_4M316k] extends = esp32s2_common_LittleFS board = esp32s2cdc build_flags = ${esp32s2_common_LittleFS.build_flags} @@ -56,73 +56,95 @@ build_flags = ${esp32s2_common_LittleFS.build_flags} lib_ignore = ${esp32s2_common_LittleFS.lib_ignore} ${no_ir.lib_ignore} -[env:collection_A_ESP32s2_4M316k_LittleFS_ETH] +[env:collection_A_ESP32s2_4M316k] extends = esp32s2_common_LittleFS board = esp32s2cdc build_flags = ${esp32s2_common_LittleFS.build_flags} -DPLUGIN_SET_COLLECTION_ESP32 + -DPLUGIN_SET_COLLECTION_A_ESP32 -DCOLLECTION_FEATURE_RTTTL=1 -[env:collection_B_ESP32s2_4M316k_LittleFS_ETH] +[env:collection_B_ESP32s2_4M316k] extends = esp32s2_common_LittleFS board = esp32s2cdc build_flags = ${esp32s2_common_LittleFS.build_flags} -DPLUGIN_SET_COLLECTION_B_ESP32 -DCOLLECTION_FEATURE_RTTTL=1 -[env:collection_C_ESP32s2_4M316k_LittleFS_ETH] +[env:collection_C_ESP32s2_4M316k] extends = esp32s2_common_LittleFS board = esp32s2cdc build_flags = ${esp32s2_common_LittleFS.build_flags} -DPLUGIN_SET_COLLECTION_C_ESP32 -DCOLLECTION_FEATURE_RTTTL=1 -[env:collection_D_ESP32s2_4M316k_LittleFS_ETH] +[env:collection_D_ESP32s2_4M316k] extends = esp32s2_common_LittleFS board = esp32s2cdc build_flags = ${esp32s2_common_LittleFS.build_flags} -DPLUGIN_SET_COLLECTION_D_ESP32 -DCOLLECTION_FEATURE_RTTTL=1 -[env:collection_E_ESP32s2_4M316k_LittleFS_ETH] +[env:collection_E_ESP32s2_4M316k] extends = esp32s2_common_LittleFS board = esp32s2cdc build_flags = ${esp32s2_common_LittleFS.build_flags} -DPLUGIN_SET_COLLECTION_E_ESP32 -DCOLLECTION_FEATURE_RTTTL=1 -[env:collection_F_ESP32s2_4M316k_LittleFS_ETH] +[env:collection_F_ESP32s2_4M316k] extends = esp32s2_common_LittleFS board = esp32s2cdc build_flags = ${esp32s2_common_LittleFS.build_flags} -DPLUGIN_SET_COLLECTION_F_ESP32 -DCOLLECTION_FEATURE_RTTTL=1 -[env:collection_G_ESP32s2_4M316k_LittleFS_ETH] +[env:collection_G_ESP32s2_4M316k] extends = esp32s2_common_LittleFS board = esp32s2cdc build_flags = ${esp32s2_common_LittleFS.build_flags} -DPLUGIN_SET_COLLECTION_G_ESP32 -DCOLLECTION_FEATURE_RTTTL=1 +[env:collection_H_ESP32s2_4M316k] +extends = esp32s2_common_LittleFS +board = esp32s2cdc +build_flags = ${esp32s2_common_LittleFS.build_flags} + -DPLUGIN_SET_COLLECTION_H_ESP32 + -DCOLLECTION_FEATURE_RTTTL=1 -[env:energy_ESP32s2_4M316k_LittleFS_ETH] + +[env:energy_ESP32s2_4M316k] extends = esp32s2_common_LittleFS board = esp32s2cdc build_flags = ${esp32s2_common_LittleFS.build_flags} -D PLUGIN_ENERGY_COLLECTION -[env:display_ESP32s2_4M316k_LittleFS_ETH] +[env:display_A_ESP32s2_4M316k] extends = esp32s2_common_LittleFS board = esp32s2cdc build_flags = ${esp32s2_common_LittleFS.build_flags} - -D PLUGIN_DISPLAY_COLLECTION + -D PLUGIN_DISPLAY_A_COLLECTION -D ST7789_EXTRA_INIT=1 -D P116_EXTRA_ST7789=1 -[env:climate_ESP32s2_4M316k_LittleFS_ETH] +[env:display_B_ESP32s2_4M316k] extends = esp32s2_common_LittleFS board = esp32s2cdc build_flags = ${esp32s2_common_LittleFS.build_flags} - -D PLUGIN_CLIMATE_COLLECTION + -D PLUGIN_DISPLAY_B_COLLECTION + -D ST7789_EXTRA_INIT=1 + -D P116_EXTRA_ST7789=1 + +[env:climate_A_ESP32s2_4M316k] +extends = esp32s2_common_LittleFS +board = esp32s2cdc +build_flags = ${esp32s2_common_LittleFS.build_flags} + -D PLUGIN_CLIMATE_A_COLLECTION + +[env:climate_B_ESP32s2_4M316k] +extends = esp32s2_common_LittleFS +board = esp32s2cdc +build_flags = ${esp32s2_common_LittleFS.build_flags} + -D PLUGIN_CLIMATE_B_COLLECTION diff --git a/platformio_esp32s3_envs.ini b/platformio_esp32s3_envs.ini index ee5185ec9..c5e8d4e38 100644 --- a/platformio_esp32s3_envs.ini +++ b/platformio_esp32s3_envs.ini @@ -1,21 +1,21 @@ [esp32s3_common_LittleFS] -extends = esp32_base_idf5 +extends = esp32_base_idf5_5 lib_ignore = ${esp32_common_LittleFS_ETH.lib_ignore} ESP32_ping - ${esp32_base_idf5.lib_ignore} -build_flags = ${esp32_base_idf5.build_flags} + ${esp32_base_idf5_5.lib_ignore} +build_flags = ${esp32_base_idf5_5.build_flags} ; -mtext-section-literals -DFEATURE_ARDUINO_OTA=1 -DFEATURE_ETHERNET=1 -DUSE_LITTLEFS -DESP32S3 -extra_scripts = ${esp32_base_idf5.extra_scripts} -build_unflags = ${esp32_base_idf5.build_unflags} +extra_scripts = ${esp32_base_idf5_5.extra_scripts} +build_unflags = ${esp32_base_idf5_5.build_unflags} -fexceptions board_build.filesystem = littlefs -[env:custom_ESP32s3_4M316k_LittleFS_ETH] +[env:custom_ESP32s3_4M316k] extends = esp32s3_common_LittleFS board = esp32s3cdc-qio_qspi build_flags = ${esp32s3_common_LittleFS.build_flags} @@ -24,7 +24,7 @@ extra_scripts = ${esp32s3_common_LittleFS.extra_scripts} pre:tools/pio/pre_custom_esp32.py -[env:custom_IR_ESP32s3_4M316k_LittleFS_ETH] +[env:custom_IR_ESP32s3_4M316k] extends = esp32s3_common_LittleFS board = esp32s3cdc-qio_qspi build_flags = ${esp32s3_common_LittleFS.build_flags} @@ -37,7 +37,7 @@ extra_scripts = ${esp32s3_common_LittleFS.extra_scripts} pre:tools/pio/ir_build_check.py -[env:normal_ESP32s3_4M316k_LittleFS_ETH] +[env:normal_ESP32s3_4M316k] extends = esp32s3_common_LittleFS board = esp32s3cdc-qio_qspi lib_ignore = ${esp32s3_common_LittleFS.lib_ignore} @@ -47,78 +47,100 @@ build_flags = ${esp32s3_common_LittleFS.build_flags} -DFEATURE_SD=1 -[env:collection_A_ESP32s3_4M316k_LittleFS_ETH] +[env:collection_A_ESP32s3_4M316k] extends = esp32s3_common_LittleFS board = esp32s3cdc-qio_qspi build_flags = ${esp32s3_common_LittleFS.build_flags} -DPLUGIN_SET_COLLECTION_ESP32 + -DPLUGIN_SET_COLLECTION_A_ESP32 -DCOLLECTION_FEATURE_RTTTL=1 -[env:collection_B_ESP32s3_4M316k_LittleFS_ETH] +[env:collection_B_ESP32s3_4M316k] extends = esp32s3_common_LittleFS board = esp32s3cdc-qio_qspi build_flags = ${esp32s3_common_LittleFS.build_flags} -DPLUGIN_SET_COLLECTION_B_ESP32 -DCOLLECTION_FEATURE_RTTTL=1 -[env:collection_C_ESP32s3_4M316k_LittleFS_ETH] +[env:collection_C_ESP32s3_4M316k] extends = esp32s3_common_LittleFS board = esp32s3cdc-qio_qspi build_flags = ${esp32s3_common_LittleFS.build_flags} -DPLUGIN_SET_COLLECTION_C_ESP32 -DCOLLECTION_FEATURE_RTTTL=1 -[env:collection_D_ESP32s3_4M316k_LittleFS_ETH] +[env:collection_D_ESP32s3_4M316k] extends = esp32s3_common_LittleFS board = esp32s3cdc-qio_qspi build_flags = ${esp32s3_common_LittleFS.build_flags} -DPLUGIN_SET_COLLECTION_D_ESP32 -DCOLLECTION_FEATURE_RTTTL=1 -[env:collection_E_ESP32s3_4M316k_LittleFS_ETH] +[env:collection_E_ESP32s3_4M316k] extends = esp32s3_common_LittleFS board = esp32s3cdc-qio_qspi build_flags = ${esp32s3_common_LittleFS.build_flags} -DPLUGIN_SET_COLLECTION_E_ESP32 -DCOLLECTION_FEATURE_RTTTL=1 -[env:collection_F_ESP32s3_4M316k_LittleFS_ETH] +[env:collection_F_ESP32s3_4M316k] extends = esp32s3_common_LittleFS board = esp32s3cdc-qio_qspi build_flags = ${esp32s3_common_LittleFS.build_flags} -DPLUGIN_SET_COLLECTION_F_ESP32 -DCOLLECTION_FEATURE_RTTTL=1 -[env:collection_G_ESP32s3_4M316k_LittleFS_ETH] +[env:collection_G_ESP32s3_4M316k] extends = esp32s3_common_LittleFS board = esp32s3cdc-qio_qspi build_flags = ${esp32s3_common_LittleFS.build_flags} -DPLUGIN_SET_COLLECTION_G_ESP32 -DCOLLECTION_FEATURE_RTTTL=1 +[env:collection_H_ESP32s3_4M316k] +extends = esp32s3_common_LittleFS +board = esp32s3cdc-qio_qspi +build_flags = ${esp32s3_common_LittleFS.build_flags} + -DPLUGIN_SET_COLLECTION_H_ESP32 + -DCOLLECTION_FEATURE_RTTTL=1 -[env:energy_ESP32s3_4M316k_LittleFS_ETH] + +[env:energy_ESP32s3_4M316k] extends = esp32s3_common_LittleFS board = esp32s3cdc-qio_qspi build_flags = ${esp32s3_common_LittleFS.build_flags} -D PLUGIN_ENERGY_COLLECTION -[env:display_ESP32s3_4M316k_LittleFS_ETH] +[env:display_A_ESP32s3_4M316k] extends = esp32s3_common_LittleFS board = esp32s3cdc-qio_qspi build_flags = ${esp32s3_common_LittleFS.build_flags} - -D PLUGIN_DISPLAY_COLLECTION + -D PLUGIN_DISPLAY_A_COLLECTION -D ST7789_EXTRA_INIT=1 -D P116_EXTRA_ST7789=1 -[env:climate_ESP32s3_4M316k_LittleFS_ETH] +[env:display_B_ESP32s3_4M316k] extends = esp32s3_common_LittleFS board = esp32s3cdc-qio_qspi build_flags = ${esp32s3_common_LittleFS.build_flags} - -D PLUGIN_CLIMATE_COLLECTION + -D PLUGIN_DISPLAY_B_COLLECTION + -D ST7789_EXTRA_INIT=1 + -D P116_EXTRA_ST7789=1 + +[env:climate_A_ESP32s3_4M316k] +extends = esp32s3_common_LittleFS +board = esp32s3cdc-qio_qspi +build_flags = ${esp32s3_common_LittleFS.build_flags} + -D PLUGIN_CLIMATE_A_COLLECTION + +[env:climate_B_ESP32s3_4M316k] +extends = esp32s3_common_LittleFS +board = esp32s3cdc-qio_qspi +build_flags = ${esp32s3_common_LittleFS.build_flags} + -D PLUGIN_CLIMATE_B_COLLECTION -[env:neopixel_ESP32s3_4M316k_LittleFS_ETH] +[env:neopixel_ESP32s3_4M316k] extends = esp32s3_common_LittleFS board = esp32s3cdc-qio_qspi build_flags = ${esp32s3_common_LittleFS.build_flags} @@ -128,7 +150,7 @@ build_flags = ${esp32s3_common_LittleFS.build_flags} -[env:custom_ESP32s3_8M1M_LittleFS_ETH] +[env:custom_ESP32s3_8M1M] extends = esp32s3_common_LittleFS board = esp32s3cdc-qio_qspi-8M build_flags = ${esp32s3_common_LittleFS.build_flags} @@ -138,12 +160,12 @@ build_flags = ${esp32s3_common_LittleFS.build_flags} extra_scripts = ${esp32s3_common_LittleFS.extra_scripts} pre:tools/pio/pre_custom_esp32.py -[env:custom_ESP32s3_8M1M_LittleFS_OPI_PSRAM_ETH] -extends = env:custom_ESP32s3_8M1M_LittleFS_ETH +[env:custom_ESP32s3_8M1M_OPI_PSRAM] +extends = env:custom_ESP32s3_8M1M board = esp32s3cdc-qio_opi-8M -[env:max_ESP32s3_8M1M_LittleFS_ETH] +[env:max_ESP32s3_8M1M] extends = esp32s3_common_LittleFS board = esp32s3cdc-qio_qspi-8M build_flags = ${esp32s3_common_LittleFS.build_flags} @@ -155,12 +177,12 @@ build_flags = ${esp32s3_common_LittleFS.build_flags} extra_scripts = ${esp32s3_common_LittleFS.extra_scripts} -[env:max_ESP32s3_8M1M_LittleFS_OPI_PSRAM_ETH] -extends = env:max_ESP32s3_8M1M_LittleFS_ETH +[env:max_ESP32s3_8M1M_OPI_PSRAM] +extends = env:max_ESP32s3_8M1M board = esp32s3cdc-qio_opi-8M -[env:custom_ESP32s3_16M8M_LittleFS_ETH] +[env:custom_ESP32s3_16M8M] extends = esp32s3_common_LittleFS board = esp32s3cdc-qio_qspi-16M build_flags = ${esp32s3_common_LittleFS.build_flags} @@ -171,12 +193,12 @@ build_flags = ${esp32s3_common_LittleFS.build_flags} extra_scripts = ${esp32s3_common_LittleFS.extra_scripts} pre:tools/pio/pre_custom_esp32.py -[env:custom_ESP32s3_16M8M_LittleFS_OPI_PSRAM_ETH] -extends = env:custom_ESP32s3_16M8M_LittleFS_ETH +[env:custom_ESP32s3_16M8M_OPI_PSRAM] +extends = env:custom_ESP32s3_16M8M board = esp32s3cdc-qio_opi-16M -[env:max_ESP32s3_16M8M_LittleFS_ETH] +[env:max_ESP32s3_16M8M] extends = esp32s3_common_LittleFS board = esp32s3cdc-qio_qspi-16M build_flags = ${esp32s3_common_LittleFS.build_flags} @@ -186,9 +208,10 @@ build_flags = ${esp32s3_common_LittleFS.build_flags} -D ST7789_EXTRA_INIT=1 -D P116_EXTRA_ST7789=1 extra_scripts = ${esp32s3_common_LittleFS.extra_scripts} +;custom_sdkconfig = '# CONFIG_ESP_MODEM_CMUX_DEFRAGMENT_PAYLOAD is not set' -[env:max_ESP32s3_16M8M_LittleFS_OPI_PSRAM_ETH] +[env:max_ESP32s3_16M8M_OPI_PSRAM] extends = esp32s3_common_LittleFS board = esp32s3cdc-qio_opi-16M build_flags = ${esp32s3_common_LittleFS.build_flags} diff --git a/platformio_esp82xx_base.ini b/platformio_esp82xx_base.ini index bc45ac8b5..40c4fd013 100644 --- a/platformio_esp82xx_base.ini +++ b/platformio_esp82xx_base.ini @@ -89,12 +89,12 @@ src_filter = +<*> -<.git/> -<.svn/> - - -=1.10.1 cryptography==44.0.1 setuptools>=75.8 +uv \ No newline at end of file diff --git a/src/Custom-sample.h b/src/Custom-sample.h index 1bcc9a70f..7ef077162 100644 --- a/src/Custom-sample.h +++ b/src/Custom-sample.h @@ -77,7 +77,7 @@ // See: https://github.com/letscontrolit/ESPEasy/issues/2724 #define DEFAULT_SEND_TO_HTTP_ACK false // Wait for ack with SendToHttp command. -#define DEFAULT_AP_DONT_FORCE_SETUP false // Allow optional usage of Sensor without WIFI avaiable // When set you can use the Sensor in AP-Mode without beeing forced to /setup +#define DEFAULT_AP_FORCE_SETUP true // When set, start Captive Portal to redirect user to web interface when connecting to AP #define DEFAULT_DONT_ALLOW_START_AP false // Usually the AP will be started when no WiFi is defined, or the defined one cannot be found. This flag may prevent it. // --- Default Controller ------------------------------------------------------------------------------ @@ -252,6 +252,23 @@ // #define FEATURE_RTTTL_EVENTS 1 // Enable RTTTL events for Async use, for blocking it doesn't make sense // #define FEATURE_BUSCMD_STRING 1 // Enable support for String data-format in Helpers/BusCmd_Handler, default disabled for LIMIT_BUILD_SIZE only // #define FEATURE_STRING_VARIABLES 1 // Enable String variable support (enabled on ESP32, NOT supported on ESP8266 for memory restrictions!) +// #define FEATURE_COMMAND_OWSCAN 0 // Disable 1-wire scanner support, only feasible when 1-wire support is included in the build (P004, P080, P100), default disabled for MINIMAL_OTA builds +// #define FEATURE_MQTT_CONNECT_BACKGROUND 1 // Enable connecting to an MQTT broker in an ESP32 RTOS background thread (not possible on ESP8266) +// #define FEATURE_I2C_MULTIPLE 0 // Disable multiple I2C buses, only available for ESP32, default enabled on ESP32, can be disabled here +// #define FEATURE_PLUGIN_LIST 1 // Enable the Tools / Plugin list page (default enabled for ESP32) +// #define FEATURE_LAT_LONG_VAR_CMD 1 // Enable the %latitude% and %longitude% system variables, and Latitude and Longitude commands (default enabled for ESP32) + +// #define FEATURE_TASKVALUE_ATTRIBUTES 1 // Enable extra Task Value attributes (default enabled for ESP32) +// #define FEATURE_TASKVALUE_UNIT_OF_MEASURE 1 // Enable Unit of Measure per Task Value (default enabled for ESP32), also useful for MQTT Discovery +// #define FEATURE_CUSTOM_TASKVAR_VTYPE 1 // Enable Custom Value Type per Task Value (default enabled for ESP32), also useful for MQTT Discovery + +// #define FEATURE_MQTT_DISCOVER 1 // Enable MQTT Auto Discovery (currently only available for Home Assistant C005) +// #define FEATURE_MQTT_DEVICECLASS 1 // Enable selectable Device Class for Auto Discovery +// #define FEATURE_MQTT_STATE_CLASS 1 // Enable selectable State Class per Task Valie for Auto Discovery + +// #define FEATURE_MQTT_TLS 1 // Enable TLS support for MQTT Controller connections (only available on ESP32) +// #define FEATURE_EMAIL_TLS 1 // Enable TLS support for Email Notifications (only available on ESP32) +// #define FEATURE_HTTP_TLS 1 // Enable TLS support for HTTP connections (only available on ESP32) #if FEATURE_CUSTOM_PROVISIONING // For device models, see src/src/DataTypes/DeviceModel.h @@ -291,7 +308,9 @@ /* #define MENU_INDEX_CONFIG_VISIBLE false #define MENU_INDEX_CONTROLLERS_VISIBLE false +#define MENU_INDEX_NETWORK_VISIBLE false #define MENU_INDEX_HARDWARE_VISIBLE false +#define MENU_INDEX_BUSES_VISIBLE false #define MENU_INDEX_DEVICES_VISIBLE false #define MENU_INDEX_RULES_VISIBLE false #define MENU_INDEX_NOTIFICATIONS_VISIBLE false @@ -597,7 +616,7 @@ static const char DATA_ESPEASY_DEFAULT_MIN_CSS[] PROGMEM = { // #define USES_C016 // Cache controller // #define USES_C017 // Zabbix // #define USES_C018 // TTN/RN2483 - +// #define USES_C023 // AT-command LoRaWAN /* ####################################################################################################### diff --git a/src/src/DataStructs/MAC_address.cpp b/src/ESPEasy/net/DataStructs/MAC_address.cpp similarity index 97% rename from src/src/DataStructs/MAC_address.cpp rename to src/ESPEasy/net/DataStructs/MAC_address.cpp index b7ecfd8d2..bd75941c8 100644 --- a/src/src/DataStructs/MAC_address.cpp +++ b/src/ESPEasy/net/DataStructs/MAC_address.cpp @@ -1,6 +1,6 @@ #include "../DataStructs/MAC_address.h" -#include "../../ESPEasy_common.h" +#include "../../../ESPEasy_common.h" MAC_address::MAC_address(const uint8_t new_mac[6]) diff --git a/src/src/DataStructs/MAC_address.h b/src/ESPEasy/net/DataStructs/MAC_address.h similarity index 91% rename from src/src/DataStructs/MAC_address.h rename to src/ESPEasy/net/DataStructs/MAC_address.h index 1f5ab6b3f..5f0ad49a1 100644 --- a/src/src/DataStructs/MAC_address.h +++ b/src/ESPEasy/net/DataStructs/MAC_address.h @@ -1,5 +1,4 @@ -#ifndef DATASTRUCTS_MAC_ADDRESS_H -#define DATASTRUCTS_MAC_ADDRESS_H +#pragma once #include #include @@ -75,5 +74,3 @@ private: bool mac_addr_cmp(const uint8_t other[6]) const; }; - -#endif // DATASTRUCTS_MAC_ADDRESS_H \ No newline at end of file diff --git a/src/ESPEasy/net/DataStructs/NWPluginData_base.cpp b/src/ESPEasy/net/DataStructs/NWPluginData_base.cpp new file mode 100644 index 000000000..14074c7d2 --- /dev/null +++ b/src/ESPEasy/net/DataStructs/NWPluginData_base.cpp @@ -0,0 +1,440 @@ +#include "../DataStructs/NWPluginData_base.h" + +#include "../../../src/DataStructs/ESPEasy_EventStruct.h" +#include "../../../src/Globals/RuntimeData.h" +#include "../../../src/Globals/Settings.h" +#include "../../../src/Helpers/StringConverter.h" +#if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS +# include "../../../src/Helpers/_ESPEasy_key_value_store.h" +#include "../_NWPlugin_Helper.h" +#endif +#ifdef ESP32 +# include "../Globals/NetworkState.h" + +# include +# include +#endif // ifdef ESP32 + +namespace ESPEasy { +namespace net { + +NWPluginData_base::NWPluginData_base( + nwpluginID_t nwpluginID, networkIndex_t networkIndex +#ifdef ESP32 + , NetworkInterface *netif +#endif + ) : +#if FEATURE_NETWORK_STATS + _plugin_stats_array(nullptr), +#endif // if FEATURE_NETWORK_STATS + +#if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + _kvs(nullptr), +#endif + _nw_data_pluginID(nwpluginID), + _networkIndex(networkIndex) +#ifdef ESP32 + , _netif(netif) +#endif +{ +#if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + + if (_kvs == nullptr) { + _kvs = new (std::nothrow) ESPEasy_key_value_store; + } +#endif // if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS +} + +NWPluginData_base::~NWPluginData_base() +{ +#if FEATURE_NETWORK_STATS + delete _plugin_stats_array; + _plugin_stats_array = nullptr; +#endif // if FEATURE_NETWORK_STATS + +#if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + + if (_kvs) { delete _kvs; } + _kvs = nullptr; +#endif // if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS +} + +bool NWPluginData_base::hasPluginStats() const { +#if FEATURE_NETWORK_STATS + + if (_plugin_stats_array != nullptr) { + return _plugin_stats_array->hasStats(); + } +#endif // if FEATURE_NETWORK_STATS + return false; +} + +bool NWPluginData_base::hasPeaks() const { +#if FEATURE_NETWORK_STATS + + if (_plugin_stats_array != nullptr) { + return _plugin_stats_array->hasPeaks(); + } +#endif // if FEATURE_NETWORK_STATS + return false; +} + +size_t NWPluginData_base::nrSamplesPresent() const { +#if FEATURE_NETWORK_STATS + + if (_plugin_stats_array != nullptr) { + return _plugin_stats_array->nrSamplesPresent(); + } +#endif // if FEATURE_NETWORK_STATS + return 0; +} + +#if FEATURE_NETWORK_STATS + +void NWPluginData_base::initPluginStats( + networkStatsVarIndex_t networkStatsVarIndex, + const String & label, + uint8_t nrDecimals, + float errorValue, + const PluginStats_Config_t& displayConfig) +{ + if (networkStatsVarIndex < INVALID_NETWORK_STATS_VAR_INDEX) { + if (_plugin_stats_array == nullptr) { + constexpr unsigned size = sizeof(PluginStats_array); + void *ptr = special_calloc(1, size); + + if (ptr != nullptr) { + _plugin_stats_array = new (ptr) PluginStats_array(); + } + } + + if (_plugin_stats_array != nullptr) { + _plugin_stats_array->initPluginStats( + networkStatsVarIndex, + label, + nrDecimals, + errorValue, + displayConfig); + } + } +} + +# if FEATURE_NETWORK_TRAFFIC_COUNT + +void NWPluginData_base::initPluginStats_trafficCount(networkStatsVarIndex_t networkStatsVarIndex, bool isTX) +{ + PluginStats_Config_t displayConfig; + + displayConfig.setAxisPosition(PluginStats_Config_t::AxisPosition::Right); + displayConfig.setEnabled(true); + displayConfig.setAxisIndex(3); // Set to a fixed index so RX/TX are on the same axis index + initPluginStats( + networkStatsVarIndex, + concat(isTX ? F("TX") : F("RX"), F(" Bytes")), + 0, + NAN, + displayConfig); +} + +# endif // if FEATURE_NETWORK_TRAFFIC_COUNT + +bool NWPluginData_base::initPluginStats() +{ +# if FEATURE_NETWORK_TRAFFIC_COUNT + + // Virtual function has no override in derived class, so only init traffic count + initPluginStats_trafficCount(0, true); // TX + initPluginStats_trafficCount(1, false); // RX + return true; +# else // if FEATURE_NETWORK_TRAFFIC_COUNT + return false; +# endif // if FEATURE_NETWORK_TRAFFIC_COUNT +} + +void NWPluginData_base::clearPluginStats(networkStatsVarIndex_t networkStatsVarIndex) +{ + if ((networkStatsVarIndex < INVALID_NETWORK_STATS_VAR_INDEX) && _plugin_stats_array) { + _plugin_stats_array->clearPluginStats(networkStatsVarIndex); + + if (!_plugin_stats_array->hasStats()) { + delete _plugin_stats_array; + _plugin_stats_array = nullptr; + } + } +} + +void NWPluginData_base::processTimeSet(const double& time_offset) +{ + if (_plugin_stats_array != nullptr) { + _plugin_stats_array->processTimeSet(time_offset); + } +} + +#endif // if FEATURE_NETWORK_STATS + +bool NWPluginData_base::pushStatsValues(EventStruct *event, + size_t valueCount, + bool trackPeaks, + bool onlyUpdateTimestampWhenSame) +{ +#if FEATURE_NETWORK_STATS + + if (_plugin_stats_array != nullptr) { +# if FEATURE_NETWORK_TRAFFIC_COUNT + + // Include traffic + TX_RX_traffic_count traffic{}; + + if (getTrafficCount(traffic)) { + // Only set value when _prevRX/TX was set to make sure there isn't an enormous spike + event->ParfN[valueCount++] = _prevTX == 0 || (traffic._tx_count < _prevTX) ? 0 : traffic._tx_count - _prevTX; + event->ParfN[valueCount++] = _prevRX == 0 || (traffic._rx_count < _prevRX) ? 0 : traffic._rx_count - _prevRX; + _prevRX = traffic._rx_count; + _prevTX = traffic._tx_count; + } else { + event->ParfN[valueCount++] = NAN; + event->ParfN[valueCount++] = NAN; + _prevTX = 0; + _prevRX = 0; + } +# endif // if FEATURE_NETWORK_TRAFFIC_COUNT + + if (valueCount) { + return _plugin_stats_array->pushStatsValues(event, valueCount, trackPeaks, onlyUpdateTimestampWhenSame); + } + } +#endif // if FEATURE_NETWORK_STATS + return false; +} + +bool NWPluginData_base::plugin_write_base(EventStruct *event, + const String& string) +{ +#if FEATURE_NETWORK_STATS + + if (_plugin_stats_array != nullptr) { + return _plugin_stats_array->plugin_write_base(event, string); + } +#endif // if FEATURE_NETWORK_STATS + + return false; +} + +#if FEATURE_NETWORK_STATS + +bool NWPluginData_base::record_stats() +{ + if (_plugin_stats_array != nullptr) { + # ifdef ESP32 + + EventStruct tmpEvent; + size_t valueCount{}; + bool trackPeaks = true; + bool onlyUpdateTimestampWhenSame = true; + return pushStatsValues(&tmpEvent, valueCount, trackPeaks, onlyUpdateTimestampWhenSame); +# endif // ifdef ESP32 + } + return false; +} + +bool NWPluginData_base::webformLoad_show_stats(EventStruct *event) const +{ + if (_plugin_stats_array != nullptr) { + return _plugin_stats_array->webformLoad_show_stats(event); + } + return false; +} + +# if FEATURE_CHART_JS + +void NWPluginData_base::plot_ChartJS(bool onlyJSON) const +{ + if (_plugin_stats_array != nullptr) { + _plugin_stats_array->plot_ChartJS(onlyJSON); + } +} + +void NWPluginData_base::plot_ChartJS_scatter( + networkStatsVarIndex_t values_X_axis_index, + networkStatsVarIndex_t values_Y_axis_index, + const __FlashStringHelper *id, + const ChartJS_title & chartTitle, + const ChartJS_dataset_config& datasetConfig, + bool showAverage, + const String & options, + bool onlyJSON) const +{ + if (_plugin_stats_array != nullptr) { + // TODO TD-er: Show TX-power vs. RSSI as scatter plot + _plugin_stats_array->plot_ChartJS_scatter( + values_X_axis_index, + values_Y_axis_index, + id, + chartTitle, + datasetConfig, + showAverage, + options, + onlyJSON); + } +} + +# endif // if FEATURE_CHART_JS +#endif // if FEATURE_NETWORK_STATS + + +#if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + +bool NWPluginData_base::init_KVS() +{ + if (!_KVS_initialized()) { return false; } + + // _load(); + + // TODO TD-er: load() can also return false when some other data used to be present. Have to think about how to handle this. + return true; +} + +#endif // if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + +LongTermTimer::Duration NWPluginData_base::getConnectedDuration_ms() { + auto data = getNWPluginData_static_runtime(); + + if (data) { + return data->_connectedStats.getLastOnDuration_ms(); + } + return 0; +} + +bool NWPluginData_base::handle_nwplugin_write(EventStruct *event, String& str) { return false; } + +#ifdef ESP32 + +bool NWPluginData_base::handle_priority_route_changed() +{ + bool res{}; + + if ((_netif != nullptr) && _netif->isDefault()) { + auto cache = getNWPluginData_static_runtime(); + + if (!cache) { + if (NWPlugin::forceDHCP_request(_netif)) { + return true; + } + } + + // Check to see if we may need to restore any cached DNS server + for (size_t i = 0; i < NR_ELEMENTS(cache->_dns_cache); ++i) { + auto tmp = _netif->dnsIP(i); + + if ((cache->_dns_cache[i] != INADDR_NONE) && (cache->_dns_cache[i] != tmp)) { + addLog(LOG_LEVEL_INFO, strformat( + F("%s: Restore cached DNS server %d from %s to %s"), + _netif->desc(), + i, + tmp.toString().c_str(), + cache->_dns_cache[i].toString().c_str() + )); + + _netif->dnsIP(i, cache->_dns_cache[i]); + res = true; + } + } + } + return res; +} + +#endif // ifdef ESP32 + +#if FEATURE_NETWORK_TRAFFIC_COUNT + +void NWPluginData_base::enable_txrx_events() +{ + auto cache = getNWPluginData_static_runtime(); + + if (cache) { + cache->enable_txrx_events(); + } +} + +bool NWPluginData_base::getTrafficCount(TX_RX_traffic_count& traffic) +{ + if (!_netif) { return false; } + auto cache = getNWPluginData_static_runtime(); + return cache && cache->getTrafficCount(traffic); +} + +#endif // if FEATURE_NETWORK_TRAFFIC_COUNT + +#if FEATURE_NETWORK_STATS + +PluginStats * NWPluginData_base::getPluginStats(networkStatsVarIndex_t networkStatsVarIndex) const +{ + if (_plugin_stats_array != nullptr) { + return _plugin_stats_array->getPluginStats(networkStatsVarIndex); + } + return nullptr; +} + +PluginStats * NWPluginData_base::getPluginStats(networkStatsVarIndex_t networkStatsVarIndex) +{ + if (_plugin_stats_array != nullptr) { + return _plugin_stats_array->getPluginStats(networkStatsVarIndex); + } + return nullptr; +} + +#endif // if FEATURE_NETWORK_STATS + +#ifdef ESP32 +/* +bool NWPluginData_base::_restore_DNS_cache() +{ + bool res{}; + + if ((_netif != nullptr) && _netif->isDefault()) { + if (NWPlugin::forceDHCP_request(_netif)) { return true; } + + auto cache = getNWPluginData_static_runtime(); + + if (!cache) { return res; } + + // Check to see if we may need to restore any cached DNS server + for (size_t i = 0; i < NR_ELEMENTS(cache->_dns_cache); ++i) { + auto tmp = _netif->dnsIP(i); + + if ((cache->_dns_cache[i] != INADDR_NONE) && (cache->_dns_cache[i] != tmp)) { + addLog(LOG_LEVEL_INFO, strformat( + F("NW%03%d: Restore cached DNS server %d from %s to %s"), + _nw_data_pluginID, + i, + tmp.toString().c_str(), + cache->_dns_cache[i].toString().c_str() + )); + + _netif->dnsIP(i, cache->_dns_cache[i]); + res = true; + } + } + } + return res; +} + */ + +#endif // ifdef ESP32 + +#if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + +bool NWPluginData_base::_load() +{ + return load_nwpluginTaskData_KVS(_kvs, _networkIndex, _nw_data_pluginID); +} + +bool NWPluginData_base::_store() +{ + return store_nwpluginTaskData_KVS(_kvs, _networkIndex, _nw_data_pluginID); +} + +#endif // if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + +} // namespace net +} // namespace ESPEasy diff --git a/src/ESPEasy/net/DataStructs/NWPluginData_base.h b/src/ESPEasy/net/DataStructs/NWPluginData_base.h new file mode 100644 index 000000000..8ea8a01db --- /dev/null +++ b/src/ESPEasy/net/DataStructs/NWPluginData_base.h @@ -0,0 +1,172 @@ +#pragma once + +#include "../../../ESPEasy_common.h" + +#include "../DataTypes/NWPluginID.h" +#include "../DataTypes/NetworkIndex.h" +#include "../DataStructs/NWPluginData_static_runtime.h" +#include "../../../src/DataStructs/ESPEasy_EventStruct.h" +#include "../../../src/DataStructs/PluginStats_array.h" + +#if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS +# include "../../../src/Helpers/_ESPEasy_key_value_store.h" +# include "../../../src/Helpers/ESPEasy_key_value_store_import_export.h" +#endif + +namespace ESPEasy { +namespace net { + + +// ============================================= +// Data used by instances of NW-plugins. +// ============================================= + +// base class to be able to delete a data object from the array. +// N.B. in order to use this, a data object must inherit from this base class. +// This is a compile time check. +struct NWPluginData_base { + NWPluginData_base(nwpluginID_t nwpluginID, + networkIndex_t networkIndex +#ifdef ESP32 + , + NetworkInterface *netif +#endif // ifdef ESP32 + ); + + virtual ~NWPluginData_base(); + + virtual bool init(EventStruct *event) = 0; + + virtual bool exit(EventStruct *event) = 0; + + bool hasPluginStats() const; + + bool hasPeaks() const; + + size_t nrSamplesPresent() const; + + #if FEATURE_NETWORK_STATS + virtual bool initPluginStats(); + + void clearPluginStats(networkStatsVarIndex_t networkStatsVarIndex); + + // Update any logged timestamp with this newly set system time. + void processTimeSet(const double& time_offset); + #endif // if FEATURE_NETWORK_STATS + + + bool pushStatsValues(EventStruct *event, + size_t valueCount, + bool trackPeaks, + bool onlyUpdateTimestampWhenSame); + + + bool plugin_write_base(EventStruct *event, + const String& string); + +#if FEATURE_NETWORK_STATS + virtual bool record_stats(); + virtual bool webformLoad_show_stats(EventStruct *event) const; + +# if FEATURE_CHART_JS + void plot_ChartJS(bool onlyJSON = false) const; + + void plot_ChartJS_scatter( + networkStatsVarIndex_t values_X_axis_index, + networkStatsVarIndex_t values_Y_axis_index, + const __FlashStringHelper *id, + const ChartJS_title & chartTitle, + const ChartJS_dataset_config& datasetConfig, + bool showAverage = true, + const String & options = EMPTY_STRING, + bool onlyJSON = false) const; + +# endif // if FEATURE_CHART_JS +#endif // if FEATURE_NETWORK_STATS + + + // Should only be called from initNWPluginData +#if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + bool init_KVS(); +#endif + + nwpluginID_t getNWPluginID() const { return _nw_data_pluginID; } + + virtual LongTermTimer::Duration getConnectedDuration_ms(); + virtual bool handle_nwplugin_write(EventStruct *event, + String & str); + +#ifdef ESP32 + virtual bool handle_priority_route_changed(); +#endif +#if FEATURE_NETWORK_TRAFFIC_COUNT + void enable_txrx_events(); + bool getTrafficCount(TX_RX_traffic_count& traffic); +#endif // if FEATURE_NETWORK_TRAFFIC_COUNT + + virtual NWPluginData_static_runtime* getNWPluginData_static_runtime() = 0; + + +#if FEATURE_NETWORK_STATS + + PluginStats* getPluginStats(networkStatsVarIndex_t networkStatsVarIndex) const; + + PluginStats* getPluginStats(networkStatsVarIndex_t networkStatsVarIndex); + +protected: + + void initPluginStats( + networkStatsVarIndex_t networkStatsVarIndex, + const String & label, + uint8_t nrDecimals, + float errorValue, + const PluginStats_Config_t& displayConfig); + +# if FEATURE_NETWORK_TRAFFIC_COUNT + void initPluginStats_trafficCount(networkStatsVarIndex_t networkStatsVarIndex, + bool isTX); +# endif // if FEATURE_NETWORK_TRAFFIC_COUNT + + + // Array of pointers to PluginStats. One per task value. + PluginStats_array *_plugin_stats_array = nullptr; +#endif // if FEATURE_NETWORK_STATS + +protected: + +#ifdef ESP32 +// bool _restore_DNS_cache(); +#endif + +#if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + + bool _KVS_initialized() const { return _kvs != nullptr; } + + // Load settings in the _kvs + bool _load(); + + // Save settings from the _kvs to the settings + bool _store(); + + ESPEasy_key_value_store *_kvs = nullptr; +#endif // if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + + // We cannot use dynamic_cast, so we must keep track of the plugin ID to + // perform checks on the casting. + // This is also a check to only use these functions and not to insert pointers + // at random in the Plugin_task_data array. + nwpluginID_t _nw_data_pluginID = INVALID_NW_PLUGIN_ID; + networkIndex_t _networkIndex = INVALID_NETWORK_INDEX; + +#ifdef ESP32 + NetworkInterface *_netif{}; +# if FEATURE_NETWORK_STATS && FEATURE_NETWORK_TRAFFIC_COUNT + uint64_t _prevTX{}; + uint64_t _prevRX{}; +# endif // if FEATURE_NETWORK_STATS && FEATURE_NETWORK_TRAFFIC_COUNT +#endif // ifdef ESP32 + +}; + +} // namespace net +} // namespace ESPEasy diff --git a/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime.cpp b/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime.cpp new file mode 100644 index 000000000..efcc77aa8 --- /dev/null +++ b/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime.cpp @@ -0,0 +1,361 @@ +#include "../DataStructs/NWPluginData_static_runtime.h" + +#include "../../../src/Globals/EventQueue.h" +#include "../../../src/Globals/Settings.h" + +#include "../../../src/Helpers/NetworkStatusLED.h" +#include "../../../src/Helpers/StringConverter.h" + +#include "../wifi/ESPEasyWifi.h" + +#ifdef ESP32 +# include +#endif + + +namespace ESPEasy { +namespace net { + +#define CONNECTION_CONSIDERED_STABLE_MSEC 60000 +#define CONNECT_TIMEOUT_MAX 10000 // in milliSeconds + +#if FEATURE_NETWORK_TRAFFIC_COUNT + +typedef std::map InterfaceTrafficCount_t; + +static InterfaceTrafficCount_t interfaceTrafficCount; + +static void tx_rx_event_handler(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + if (event_data == nullptr) { return; } + ip_event_tx_rx_t *event = (ip_event_tx_rx_t *)event_data; + + if (event->len > 0) { + const int key = esp_netif_get_netif_impl_index(event->esp_netif); + + + if (event->dir == ESP_NETIF_TX) { + interfaceTrafficCount[key]._tx_count += event->len; + interfaceTrafficCount[key]._tx_packets++; + } else if (event->dir == ESP_NETIF_RX) { + interfaceTrafficCount[key]._rx_count += event->len; + interfaceTrafficCount[key]._rx_packets++; + + /* + addLog(LOG_LEVEL_INFO, strformat( + F("RX: %s key: %d len: %d total: %d"), + esp_netif_get_desc(event->esp_netif), + key, + event->len, + interfaceTrafficCount[key]._rx_count + )); + */ + } + } +} + +void NWPluginData_static_runtime::enable_txrx_events() +{ + if (_netif) { + const int key = _netif->impl_index(); + interfaceTrafficCount[key].clear(); + + if (_netif->netif()) { + static bool registered_IP_EVENT_TX_RX = false; + const int key = esp_netif_get_netif_impl_index(_netif->netif()); + interfaceTrafficCount[key].clear(); + esp_netif_tx_rx_event_enable(_netif->netif()); + + if (!registered_IP_EVENT_TX_RX) { + esp_event_handler_instance_register(IP_EVENT, IP_EVENT_TX_RX, &tx_rx_event_handler, NULL, &_handler_inst); + registered_IP_EVENT_TX_RX = true; + } + esp_netif_tx_rx_event_enable(_netif->netif()); + } + + // _netif->netif()->tx_rx_events_enabled = true; + } +} + +bool NWPluginData_static_runtime::getTrafficCount(TX_RX_traffic_count& traffic) const +{ + if (_netif == nullptr) { return false; } + const int key = _netif->impl_index(); + auto it = interfaceTrafficCount.find(key); + + if (it == interfaceTrafficCount.end()) { return false; } + traffic = it->second; + return true; +} + +#endif // if FEATURE_NETWORK_TRAFFIC_COUNT + +void NWPluginData_static_runtime::clear(networkIndex_t networkIndex) +{ + _connectedStats.clear(); + _gotIPStats.clear(); +#if FEATURE_USE_IPV6 + _gotIP6Stats.clear(); +#endif + _operationalStats.clear(); +#if FEATURE_NETWORK_TRAFFIC_COUNT + + if (_netif) { + const int key = _netif->impl_index(); + interfaceTrafficCount[key].clear(); + } +#endif // if FEATURE_NETWORK_TRAFFIC_COUNT + + _networkIndex = networkIndex; +#ifdef ESP32 + + if (_netif) { + if ((_eventInterfaceName.length() == 0) && _netif) { + _eventInterfaceName = _netif->desc(); + _eventInterfaceName.toUpperCase(); + } + } +#endif // ifdef ESP32 + + _connectionFailures = 0; + + // FIXME TD-er: Should also clear dns cache? +} + +void NWPluginData_static_runtime::processEvent_and_clear() +{ + processEvents(); + clear(); +} + +bool NWPluginData_static_runtime::operational() const +{ + if (!Settings.getNetworkEnabled(_networkIndex)) { return false; } + + if (_isAP) { + return ESPEasy::net::wifi::wifiAPmodeActivelyUsed(); + } + return connected() && hasIP(); + + // FIXME TD-er: WiFi STA keeps reporting it is + // connected and has IP even after call to networkdisable,1 +} + +void NWPluginData_static_runtime::processEvents() +{ +#ifdef ESP32 + + if (_netif == nullptr) { return; } +#endif // ifdef ESP32 + + // TD-er: Just set these just to be sure we didn't miss any events. + _connectedStats.set(connected()); + + // _gotIPStats.set(hasIP()); +#if FEATURE_USE_IPV6 + + // _gotIP6Stats.set(hasIPv6()); +#endif // if FEATURE_USE_IPV6 + const bool connected_changed = _connectedStats.changedSinceLastCheck_and_clear(); + const bool establishConnect_changed = _establishConnectStats.changedSinceLastCheck_and_clear(); + + if (_gotIPStats.changedSinceLastCheck_and_clear()) { +#ifdef ESP32 + + if (loglevelActiveFor(LOG_LEVEL_INFO) && _netif) { + auto ip = _netif->localIP(); + + if (ip != INADDR_NONE) { + addLog(LOG_LEVEL_INFO, strformat( + F("%s: Got IP: %s/%d GW: %s"), + _eventInterfaceName.c_str(), + ip.toString().c_str(), + _netif->subnetCIDR(), + _netif->gatewayIP().toString().c_str() + )); + } + } +#endif // ifdef ESP32 +#ifdef ESP8266 + # ifndef BUILD_NO_DEBUG + + if (_isAP) { + addLog(LOG_LEVEL_INFO, F("AP: Got IP")); + } + else { + addLog(LOG_LEVEL_INFO, concat( + F("STA: Got IP "), + WiFi.localIP().toString())); + } + # endif // ifndef BUILD_NO_DEBUG +#endif // ifdef ESP8266 + } + +#if FEATURE_USE_IPV6 + + if (_gotIP6Stats.changedSinceLastCheck_and_clear()) { + for (uint8_t i = 0; i < NR_ELEMENTS(_gotIP6Events); ++i) { + ip_event_got_ip6_t ip6Event; + memcpy(&ip6Event, &_gotIP6Events[i], sizeof(ip_event_got_ip6_t)); + memset(&_gotIP6Events[i], 0, sizeof(ip_event_got_ip6_t)); + + if (loglevelActiveFor(LOG_LEVEL_INFO) && (ip6Event.esp_netif != nullptr)) { + esp_ip6_addr_type_t addr_type = esp_netif_ip6_get_addr_type(&ip6Event.ip6_info.ip); + static const char *addr_types[] = { "UNKNOWN", "GLOBAL", "LINK_LOCAL", "SITE_LOCAL", "UNIQUE_LOCAL", "IPV4_MAPPED_IPV6" }; + + if (addr_type < NR_ELEMENTS(addr_types)) { + addLog(LOG_LEVEL_INFO, strformat( + F("%s: Got IPv6: IP Index: %d, Type: %s, Zone: %d, Address: " IPV6STR), + _eventInterfaceName.c_str(), + ip6Event.ip_index, + addr_types[addr_type], + ip6Event.ip6_info.ip.zone, + IPV62STR(ip6Event.ip6_info.ip) + )); + } + } + } + } +#endif // if FEATURE_USE_IPV6 + + if (connected_changed || establishConnect_changed) + { + if (_connectedStats.isOn()) { + log_connected(); + + // _establishConnectStats.resetCount(); + } else if (_connectedStats.isOff() && !_establishConnectStats.isOn()) { + log_disconnected(); + } + } + + _operationalStats.set(operational()); + + if (_operationalStats.changedSinceLastCheck_and_clear()) { +#if FEATURE_NETWORK_TRAFFIC_COUNT + + if (_operationalStats.isOn()) { + enable_txrx_events(); + } +#endif // if FEATURE_NETWORK_TRAFFIC_COUNT + + // Send out event + if (Settings.UseRules && _eventInterfaceName.length()) + { + if (_operationalStats.isOn()) { + if (_isAP) { + eventQueue.add(F("WiFi#APmodeConnected")); + } + else { + eventQueue.add(concat(_eventInterfaceName, F("#Connected"))); + } + } else if (_operationalStats.isOff()) { + if (_isAP) { + eventQueue.add(F("WiFi#APmodeDisconnected")); + } + else { + eventQueue.add(concat(_eventInterfaceName, F("#Disconnected"))); + } + } + } + statusLED(true); + } + + if (_startStopStats.changedSinceLastCheck_and_clear() && Settings.UseRules) { + if (_startStopStats.isOn()) { + if (_isAP) { + eventQueue.add(F("WiFi#APmodeEnabled")); + } + else { + eventQueue.add(concat(_eventInterfaceName, F("#Enabled"))); + } + } else if (_startStopStats.isOff()) { + if (_isAP) { + eventQueue.add(F("WiFi#APmodeDisabled")); + } + else { + eventQueue.add(concat(_eventInterfaceName, F("#Disabled"))); + } + } + } +} + +String NWPluginData_static_runtime::statusToString() const +{ + String log; + + if (connected()) { + log += F("Conn. "); + } + + if (hasIP()) { + log += F("IP "); + } + + if (operational()) { + log += F("Init"); + } + + if (log.isEmpty()) { log = F("DISCONNECTED"); } + return log; +} + +bool NWPluginData_static_runtime::stableConnection() const +{ + return _connectedStats.isOn() && _connectedStats.getLastOnDuration_ms() > CONNECTION_CONSIDERED_STABLE_MSEC; +} + +uint32_t NWPluginData_static_runtime::getSuggestedTimeout( + int index, + uint32_t minimum_timeout) const +{ + auto it = _connectDurations.find(index); + + if (it == _connectDurations.end()) { + // Store the suggested timeout as negative value, + // to make clear it was last suggested, but not yet confirmed. + _connectDurations[index] = -3 * minimum_timeout; + return 3 * minimum_timeout; + } + const uint32_t returnvalue = constrain(std::abs(3 * it->second), minimum_timeout, CONNECT_TIMEOUT_MAX); + + if (it->second < 0) { + _connectDurations[index] = -1 * returnvalue; + } else { + _connectDurations[index] = returnvalue; + } + return returnvalue; +} + +void NWPluginData_static_runtime::markConnectionSuccess( + int index, + uint32_t duration_ms) const +{ + auto it = _connectDurations.find(index); + + if ((it == _connectDurations.end()) || (it->second < 0)) { _connectDurations[index] = duration_ms; } + + // Apply some slight filtering so we won't get into trouble when a connection suddenly was faster. + _connectDurations[index] += 2 * duration_ms; + _connectDurations[index] /= 3; + + if (_connectionFailures > 0) { + --_connectionFailures; + } +} + +void NWPluginData_static_runtime::markPublishSuccess() const +{ + if (_connectionFailures > 0) { + --_connectionFailures; + } +} + +void NWPluginData_static_runtime::markConnectionFailed(int index) const +{ + ++_connectionFailures; +} + +} // namespace net +} // namespace ESPEasy diff --git a/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime.h b/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime.h new file mode 100644 index 000000000..4641e8995 --- /dev/null +++ b/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime.h @@ -0,0 +1,187 @@ +#pragma once + +#include "../DataTypes/NetworkIndex.h" + +#include "../../../ESPEasy_common.h" + +#include "../../../src/Helpers/LongTermOnOffTimer.h" + +#include +#ifdef ESP32 +# include +#endif + +#include +#include + +namespace ESPEasy { +namespace net { + +#if FEATURE_NETWORK_TRAFFIC_COUNT +struct TX_RX_traffic_count { + + void clear() { _tx_count = 0; _rx_count = 0; _tx_packets = 0; _rx_packets = 0; } + + uint64_t _tx_count{}; + uint64_t _rx_count{}; + uint32_t _tx_packets{}; + uint32_t _rx_packets{}; + +}; + +#endif // if FEATURE_NETWORK_TRAFFIC_COUNT + +struct NWPluginData_static_runtime { +#ifdef ESP32 + NWPluginData_static_runtime( + NetworkInterface *netif, + const String & eventInterfaceName = EMPTY_STRING); + + NWPluginData_static_runtime( + bool isAP, + NetworkInterface *netif, + const String & eventInterfaceName = EMPTY_STRING); +#else // ifdef ESP32 + NWPluginData_static_runtime(bool isAP, + const char *eventInterfaceName); + +#endif // ifdef ESP32 + +#if FEATURE_NETWORK_TRAFFIC_COUNT + void enable_txrx_events(); + + bool getTrafficCount(TX_RX_traffic_count& traffic) const; +#endif // if FEATURE_NETWORK_TRAFFIC_COUNT + + void clear(networkIndex_t networkIndex = INVALID_NETWORK_INDEX); + + void processEvent_and_clear(); + + bool started() const; + + bool connected() const; + +#if FEATURE_ETHERNET + bool linkUp() const; +#endif + + bool isDefaultRoute() const; + + bool hasIP() const; + +#if FEATURE_USE_IPV6 + bool hasIPv6() const; +#endif + + bool operational() const; + + String statusToString() const; + + // Return true when connected over 1 minute. + bool stableConnection() const; + + + // ============================================= + // mark_xxx() to act on typical events + // ============================================= + void mark_start(); + + void mark_stop(); + + void mark_got_IP(); +#if FEATURE_USE_IPV6 + void mark_got_IPv6(ip_event_got_ip6_t *event); +#endif + + void mark_lost_IP(); + + void mark_begin_establish_connection(); + + void mark_connected(); + void log_connected(); + + void mark_disconnected(); + void log_disconnected(); + + // ============================================= + // Keep track of connection durations + // per host/interface + // + // When a suggested timeout is not 'acknowledged' + // a next suggested timeout will be 3x longer. + // ============================================= + + // Return a suggested timeout + uint32_t getSuggestedTimeout(int index, + uint32_t minimum_timeout) const; + + void markConnectionSuccess(int index, + uint32_t duration_ms) const; + + void markPublishSuccess() const; + + void markConnectionFailed(int index) const; + + uint32_t getConnectionFailures() const { return _connectionFailures; } + + void processEvents(); + + // ============================================= + // OnOffTimers for keeping track of: + // - start/stop of interface + // - Connected/Disconnected state + // - GotIP/LostIP + // - GotIPv6/LostIP + // ============================================= + + LongTermOnOffTimer _startStopStats{}; + LongTermOnOffTimer _establishConnectStats{}; + LongTermOnOffTimer _connectedStats{}; + LongTermOnOffTimer _gotIPStats{}; +#if FEATURE_USE_IPV6 + LongTermOnOffTimer _gotIP6Stats{}; + ip_event_got_ip6_t _gotIP6Events[6]{}; +#endif // if FEATURE_USE_IPV6 + LongTermOnOffTimer _operationalStats{}; // is started, connected and had IP + + // ============================================= + // Cached DNS servers & Route Prio + // ============================================= +#ifdef ESP32 + IPAddress _dns_cache[2]{}; + int _routePrio = -1; // Cached route prio as it is being used from callbacks +#endif // ifdef ESP32 + +#if FEATURE_USE_IPV6 + bool _enableIPv6{}; // Cached enableIPv6 flag as it is being used from callbacks +#endif + +private: + +#ifdef ESP32 + NetworkInterface *_netif{}; +#endif // ifdef ESP32 + + networkIndex_t _networkIndex = INVALID_NETWORK_INDEX; + const bool _isAP; + + String _eventInterfaceName; + + // Store durations it took to connect to some host. + // This depends on host and interface. + // Duration is negative when it was suggested but not actually set. + // Duration is positive when actually being set + mutable std::map_connectDurations; + + mutable uint32_t _connectionFailures{}; + +#if FEATURE_NETWORK_TRAFFIC_COUNT + esp_event_handler_instance_t _handler_inst{}; +#endif + +}; + +DEF_UP(NWPluginData_static_runtime); + +} // namespace net +} // namespace ESPEasy diff --git a/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime_ESP32.cpp b/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime_ESP32.cpp new file mode 100644 index 000000000..93fd941ab --- /dev/null +++ b/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime_ESP32.cpp @@ -0,0 +1,284 @@ +#include "../DataStructs/NWPluginData_static_runtime.h" + +#ifdef ESP32 + +# include "../ESPEasyNetwork.h" + +# include "../../../src/Helpers/StringConverter.h" +# include "../../../src/Globals/Settings.h" + +# include "../Globals/NetworkState.h" + +# include +# include + +# define NW_PLUGIN_LOG_EVENTS false + +namespace ESPEasy { +namespace net { + +NWPluginData_static_runtime::NWPluginData_static_runtime( + NetworkInterface *netif, + const String & eventInterfaceName) + : _netif(netif), + _isAP(false), + _eventInterfaceName(eventInterfaceName) +{ + if (eventInterfaceName.isEmpty() && (_netif != nullptr)) { + _eventInterfaceName = _netif->desc(); + _eventInterfaceName.toUpperCase(); + } +} + +NWPluginData_static_runtime::NWPluginData_static_runtime( + bool isAP, + NetworkInterface *netif, + const String & eventInterfaceName) + : _netif(netif), + _isAP(isAP), + _eventInterfaceName(eventInterfaceName) +{ + if (eventInterfaceName.isEmpty() && (_netif != nullptr)) { + _eventInterfaceName = _netif->desc(); + _eventInterfaceName.toUpperCase(); + } +} + +bool NWPluginData_static_runtime::started() const +{ + if (!_netif) { return false; } + return _netif->started(); +} + +bool NWPluginData_static_runtime::connected() const +{ + if (!_netif) { return false; } + return _netif->connected(); +} + +# if FEATURE_ETHERNET + +bool NWPluginData_static_runtime::linkUp() const +{ + if (!_netif) { return false; } + return _netif->linkUp(); +} + +# endif // if FEATURE_ETHERNET + +bool NWPluginData_static_runtime::isDefaultRoute() const +{ + if (!_netif) { return false; } + return _netif->isDefault(); +} + +bool NWPluginData_static_runtime::hasIP() const +{ + if (!_netif) { return false; } + return _netif->hasIP(); +} + +# if FEATURE_USE_IPV6 + +bool NWPluginData_static_runtime::hasIPv6() const +{ + if (!_netif) { return false; } + return _netif->hasGlobalIPv6() || _netif->hasLinkLocalIPv6(); +} + +# endif // if FEATURE_USE_IPV6 + +void NWPluginData_static_runtime::mark_start() +{ + // if (!_startStopStats.setOn()) { return; } + _startStopStats.setOn(); + + if (!_netif) { return; } + + _netif->setHostname(NetworkCreateRFCCompliantHostname().c_str()); +# if FEATURE_USE_IPV6 + _netif->enableIPv6(_enableIPv6); +# endif + +# if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) + + if (_routePrio > 0) { + _netif->setRoutePrio(_routePrio); + } +# endif // if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) +# if NW_PLUGIN_LOG_EVENTS + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat( + F("%s: Started"), + _eventInterfaceName.c_str())); + } +# endif // if NW_PLUGIN_LOG_EVENTS +} + +void NWPluginData_static_runtime::mark_stop() +{ + _startStopStats.setOff(); +# if NW_PLUGIN_LOG_EVENTS + + if (_netif && loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat( + F("%s: Stopped"), + _eventInterfaceName.c_str())); + } +# endif // if NW_PLUGIN_LOG_EVENTS +} + +void NWPluginData_static_runtime::mark_got_IP() +{ + // Set OnOffTimer to off so we can also count how often we get new IP + _gotIPStats.forceSet(true); + + if (!_netif) { return; } + + if (!_netif->isDefault()) { + nonDefaultNetworkInterface_gotIP = true; + } + + for (size_t i = 0; i < NR_ELEMENTS(_dns_cache); ++i) { + auto tmp = _netif->dnsIP(i); + + _dns_cache[i] = tmp; // Also set the 'empty' ones so we won't set left-over DNS server from when another interface was active. +# if NW_PLUGIN_LOG_EVENTS + + if ((tmp != INADDR_NONE) && loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat( + F("%s: DNS Cache %d set to %s"), + _eventInterfaceName.c_str(), + i, + tmp.toString(true).c_str())); + } +# endif // if NW_PLUGIN_LOG_EVENTS + } +# if NW_PLUGIN_LOG_EVENTS + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat( + F("%s: Got IP: %s"), + _eventInterfaceName.c_str(), + _netif->localIP().toString().c_str() + )); + } +# endif // if NW_PLUGIN_LOG_EVENTS + +} + +# if FEATURE_USE_IPV6 + +void NWPluginData_static_runtime::mark_got_IPv6(ip_event_got_ip6_t *event) +{ + if (!_netif || !event) { return; } + + if (_netif->netif() != event->esp_netif) { return; } + _gotIP6Stats.setOn(); + + + if (!_netif->isDefault()) { + nonDefaultNetworkInterface_gotIP = true; + } + + if (event) { + if (event->ip_index < NR_ELEMENTS(_gotIP6Events)) { + memcpy(&_gotIP6Events[event->ip_index], event, sizeof(ip_event_got_ip6_t)); + _gotIP6Stats.forceSet(true); + } + } +} + +# endif // if FEATURE_USE_IPV6 + +void NWPluginData_static_runtime::mark_lost_IP() +{ + _gotIPStats.setOff(); +# if FEATURE_USE_IPV6 + _gotIP6Stats.setOff(); +# endif +# if NW_PLUGIN_LOG_EVENTS + + if (_netif && loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat( + F("%s: Lost IP"), + _eventInterfaceName.c_str())); + } +# endif // if NW_PLUGIN_LOG_EVENTS +} + +void NWPluginData_static_runtime::mark_begin_establish_connection() +{ + _establishConnectStats.forceSet(true); + _connectedStats.setOff(); + _operationalStats.setOff(); +# if FEATURE_USE_IPV6 + + if (_netif) { + _enableIPv6 = Settings.EnableIPv6() && Settings.getNetworkEnabled_IPv6(_networkIndex); + _netif->enableIPv6(_enableIPv6); + } else { + _enableIPv6 = false; + } +# endif // if FEATURE_USE_IPV6 +# ifdef ESP32 + _routePrio = Settings.getRoutePrio_for_network(_networkIndex); +# endif +} + +void NWPluginData_static_runtime::mark_connected() +{ + _establishConnectStats.setOff(); + _connectedStats.forceSet(true); +} + +void NWPluginData_static_runtime::log_connected() +{ + if (_netif && loglevelActiveFor(LOG_LEVEL_INFO)) { + if (_establishConnectStats.getCycleCount()) { + // Log duration + addLog(LOG_LEVEL_INFO, strformat( + F("%s: Connected, took: %s in %d attempts"), + _eventInterfaceName.c_str(), + format_msec_duration_HMS( + _establishConnectStats.getLastOnDuration_ms()).c_str(), + _establishConnectStats.getCycleCount())); + } else if (_establishConnectStats.isOff()) { + addLog(LOG_LEVEL_INFO, strformat( + F("%s: Connected, took: %s"), + _eventInterfaceName.c_str(), + format_msec_duration_HMS( + _establishConnectStats.getLastOnDuration_ms()).c_str())); + } else { + addLog(LOG_LEVEL_INFO, strformat( + F("%s: Connected"), + _eventInterfaceName.c_str())); + } + } +} + +void NWPluginData_static_runtime::mark_disconnected() +{ + _establishConnectStats.setOff(); + _connectedStats.setOff(); + _operationalStats.setOff(); + + // TODO TD-er: Also clear _gotIPStats and _gotIP6Stats ? +} + +void NWPluginData_static_runtime::log_disconnected() +{ + if (_netif && loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat( + F("%s: Disconnected. Connected for: %s"), + _eventInterfaceName.c_str(), + format_msec_duration_HMS( + _connectedStats.getLastOnDuration_ms()).c_str())); + } +} + +} // namespace net +} // namespace ESPEasy + +#endif // ifdef ESP32 diff --git a/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime_ESP8266.cpp b/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime_ESP8266.cpp new file mode 100644 index 000000000..cbc21b166 --- /dev/null +++ b/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime_ESP8266.cpp @@ -0,0 +1,121 @@ +#include "../DataStructs/NWPluginData_static_runtime.h" + +#ifdef ESP8266 + +# include "../../../src/Helpers/StringConverter.h" + +namespace ESPEasy { +namespace net { + +NWPluginData_static_runtime::NWPluginData_static_runtime(bool isAP, const char *eventInterfaceName) + : _isAP(isAP), _eventInterfaceName(eventInterfaceName) {} + +bool NWPluginData_static_runtime::started() const +{ + // FIXME TD-er: Does this work reliable on ESP8266? + return _startStopStats.isOn(); +} + +bool NWPluginData_static_runtime::connected() const +{ + if (!_isAP) { return WiFi.status() == WL_CONNECTED; } + return false; +} + +bool NWPluginData_static_runtime::isDefaultRoute() const +{ + // FIXME TD-er: Should I just return connected() here? + return !_isAP; +} + +bool NWPluginData_static_runtime::hasIP() const +{ + return _gotIPStats.isOn(); +} + +void NWPluginData_static_runtime::mark_start() +{ + _startStopStats.setOn(); + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, _isAP ? F("AP: Started") : F("STA: Started")); + # endif +} + +void NWPluginData_static_runtime::mark_stop() +{ + _startStopStats.setOff(); + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, _isAP ? F("AP: Stopped") : F("STA: Stopped")); + # endif +} + +void NWPluginData_static_runtime::mark_got_IP() +{ + // Set OnOffTimer to off so we can also count how often we het new IP + _gotIPStats.forceSet(true); + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, _isAP ? F("AP: Got IP") : F("STA: Got IP")); + # endif +} + +void NWPluginData_static_runtime::mark_lost_IP() +{ + _gotIPStats.setOff(); + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, _isAP ? F("AP: Lost IP") : F("STA: Lost IP")); + # endif +} + +void NWPluginData_static_runtime::mark_begin_establish_connection() +{ + _establishConnectStats.forceSet(true); + _connectedStats.setOff(); + _operationalStats.setOff(); +} + +void NWPluginData_static_runtime::mark_connected() +{ + _establishConnectStats.setOff(); + _connectedStats.setOn(); +} + +void NWPluginData_static_runtime::log_connected() +{ +# ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + if (_establishConnectStats.getCycleCount()) { + addLog(LOG_LEVEL_INFO, concat( + F("STA: Connected, took: "), + format_msec_duration_HMS( + _establishConnectStats.getLastOnDuration_ms()))); + } else { + addLog(LOG_LEVEL_INFO, F("STA: Connected")); + } + } +# endif // ifndef BUILD_NO_DEBUG +} + +void NWPluginData_static_runtime::mark_disconnected() +{ + _establishConnectStats.setOff(); + _connectedStats.setOff(); + _operationalStats.setOff(); +} + +void NWPluginData_static_runtime::log_disconnected() +{ +# ifndef BUILD_NO_DEBUG + + if (!_isAP && loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, concat( + F("STA: Disconnected. Connected for: "), + format_msec_duration_HMS(_connectedStats.getLastOnDuration_ms()))); + } +# endif // ifndef BUILD_NO_DEBUG +} + +} // namespace net +} // namespace ESPEasy + +#endif // ifdef ESP8266 diff --git a/src/ESPEasy/net/DataStructs/NetworkDriverStruct.cpp b/src/ESPEasy/net/DataStructs/NetworkDriverStruct.cpp new file mode 100644 index 000000000..8f813525c --- /dev/null +++ b/src/ESPEasy/net/DataStructs/NetworkDriverStruct.cpp @@ -0,0 +1,7 @@ +#include "../DataStructs/NetworkDriverStruct.h" + +namespace ESPEasy { +namespace net { + +} +} diff --git a/src/ESPEasy/net/DataStructs/NetworkDriverStruct.h b/src/ESPEasy/net/DataStructs/NetworkDriverStruct.h new file mode 100644 index 000000000..22464c06a --- /dev/null +++ b/src/ESPEasy/net/DataStructs/NetworkDriverStruct.h @@ -0,0 +1,29 @@ +#pragma once + +#include "../../../ESPEasy_common.h" + +#include "../DataTypes/NetworkIndex.h" + +#include + +namespace ESPEasy { +namespace net { + + +/*********************************************************************************************\ +* NetworkDriverStruct +\*********************************************************************************************/ +struct NetworkDriverStruct +{ + NetworkDriverStruct() = default; + + bool onlySingleInstance = true; + bool alwaysPresent = false; + bool enabledOnFactoryReset = false; + networkIndex_t fixedNetworkIndex = INVALID_NETWORK_INDEX; + + +}; + +} // namespace net +} // namespace ESPEasy diff --git a/src/ESPEasy/net/DataStructs/NetworkSettingsStruct.cpp b/src/ESPEasy/net/DataStructs/NetworkSettingsStruct.cpp new file mode 100644 index 000000000..0b8d41a7a --- /dev/null +++ b/src/ESPEasy/net/DataStructs/NetworkSettingsStruct.cpp @@ -0,0 +1,47 @@ +#include "../DataStructs/NetworkSettingsStruct.h" + +#include "../../../ESPEasy_common.h" + +#include "../../../src/CustomBuild/ESPEasyLimits.h" +#include "../../../src/ESPEasyCore/ESPEasy_Log.h" +#include "../ESPEasyNetwork.h" +#include "../../../src/Helpers/Misc.h" +#include "../../../src/Helpers/Networking.h" +#include "../../../src/Helpers/StringConverter.h" + + +#include +#include +#include +#include + +namespace ESPEasy { +namespace net { + + +NetworkSettingsStruct::NetworkSettingsStruct() +{ +} + +void NetworkSettingsStruct::reset() { + // Need to make sure every byte between the members is also zero + // Otherwise the checksum will fail and settings will be saved too often. + memset(this, 0, sizeof(NetworkSettingsStruct)); + + +} + +void NetworkSettingsStruct::validate() { + + +} + +UP_NetworkSettingsStruct MakeNetworkSettings() +{ + void *calloc_ptr = special_calloc(1, sizeof(NetworkSettingsStruct)); + UP_NetworkSettingsStruct T(new (calloc_ptr) NetworkSettingsStruct()); + return T; +} + +} // namespace net +} // namespace ESPEasy diff --git a/src/ESPEasy/net/DataStructs/NetworkSettingsStruct.h b/src/ESPEasy/net/DataStructs/NetworkSettingsStruct.h new file mode 100644 index 000000000..5b1a479c6 --- /dev/null +++ b/src/ESPEasy/net/DataStructs/NetworkSettingsStruct.h @@ -0,0 +1,58 @@ +#pragma once + +/*********************************************************************************************\ +* NetworkSettingsStruct definition +\*********************************************************************************************/ +#include "../../../ESPEasy_common.h" + +#include // For std::unique_ptr +#include // for std::nothrow + +#include "../../../src/Globals/Plugins.h" +#include "../../../src/Helpers/Memory.h" + +namespace ESPEasy { +namespace net { + + +struct NetworkSettingsStruct +{ + // ******************************************************************************** + // IDs of network settings, used to generate web forms + // ******************************************************************************** + enum VarType { + NETWORK_IP, + NETWORK_PORT, + NETWORK_USER, + NETWORK_PASS, + NETWORK_TIMEOUT, + + + // Keep this as last, is used to loop over all parameters + NETWORK_ENABLED + + }; + + + NetworkSettingsStruct(); + + void reset(); + + void validate(); + + + uint8_t IP[4]; + unsigned int Port; + unsigned int ClientTimeout; + +private: + +}; + +DEF_UP(NetworkSettingsStruct); + +UP_NetworkSettingsStruct MakeNetworkSettings(); + + +} // namespace net +} // namespace ESPEasy diff --git a/src/src/DataStructs/WiFi_AP_Candidate.cpp b/src/ESPEasy/net/DataStructs/WiFi_AP_Candidate.cpp similarity index 54% rename from src/src/DataStructs/WiFi_AP_Candidate.cpp rename to src/ESPEasy/net/DataStructs/WiFi_AP_Candidate.cpp index a286b8cea..01b32ed27 100644 --- a/src/src/DataStructs/WiFi_AP_Candidate.cpp +++ b/src/ESPEasy/net/DataStructs/WiFi_AP_Candidate.cpp @@ -1,13 +1,14 @@ #include "../DataStructs/WiFi_AP_Candidate.h" +#include "../../../ESPEasy_common.h" + #include "../Globals/ESPEasyWiFiEvent.h" -#include "../Globals/SecuritySettings.h" -#include "../Globals/Statistics.h" -#include "../Helpers/ESPEasy_time_calc.h" -#include "../Helpers/Misc.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/StringGenerator_WiFi.h" -#include "../../ESPEasy_common.h" +#include "../../../src/Globals/SecuritySettings.h" +#include "../../../src/Globals/Statistics.h" +#include "../../../src/Helpers/ESPEasy_time_calc.h" +#include "../../../src/Helpers/Misc.h" +#include "../../../src/Helpers/StringConverter.h" +#include "../../../src/Helpers/StringGenerator_WiFi.h" #if defined(ESP8266) # include @@ -16,53 +17,61 @@ # include #endif // if defined(ESP32) -#define WIFI_AP_CANDIDATE_MAX_AGE 300000 // 5 minutes in msec + +#if FEATURE_WIFI + +namespace ESPEasy { +namespace net { +namespace wifi { + + + WiFi_AP_Candidate::WiFi_AP_Candidate() : -#ifdef ESP32 -# if ESP_IDF_VERSION_MAJOR >= 5 -country({ - .cc = "01", - .schan = 1, - .nchan = 14, - .policy = WIFI_COUNTRY_POLICY_AUTO, -}), -#endif -#endif +# ifdef ESP32 +# if ESP_IDF_VERSION_MAJOR >= 5 + country({ + .cc = "01", + .schan = 1, + .nchan = 14, + .policy = WIFI_COUNTRY_POLICY_AUTO, + }), +# endif // if ESP_IDF_VERSION_MAJOR >= 5 +# endif // ifdef ESP32 last_seen(0), rssi(0), channel(0), index(0), enc_type(0) { _allBits = 0u; } WiFi_AP_Candidate::WiFi_AP_Candidate(const WiFi_AP_Candidate& other) -: ssid(other.ssid), - last_seen(other.last_seen), + : ssid(other.ssid), + last_seen(other.last_seen), bssid(other.bssid), - rssi(other.rssi), - channel(other.channel), - index(other.index), + rssi(other.rssi), + channel(other.channel), + index(other.index), enc_type(other.enc_type) { _allBits = other._allBits; - #ifdef ESP32 - # if ESP_IDF_VERSION_MAJOR >= 5 + # ifdef ESP32 + # if ESP_IDF_VERSION_MAJOR >= 5 memcpy(&this->country, &other.country, sizeof(wifi_country_t)); - #endif - #endif + # endif + # endif // ifdef ESP32 } WiFi_AP_Candidate::WiFi_AP_Candidate(uint8_t index_c, const String& ssid_c) : -#ifdef ESP32 -# if ESP_IDF_VERSION_MAJOR >= 5 -country({ - .cc = "01", - .schan = 1, - .nchan = 14, - .policy = WIFI_COUNTRY_POLICY_AUTO, -}), -#endif -#endif +# ifdef ESP32 +# if ESP_IDF_VERSION_MAJOR >= 5 + country({ + .cc = "01", + .schan = 1, + .nchan = 14, + .policy = WIFI_COUNTRY_POLICY_AUTO, + }), +# endif // if ESP_IDF_VERSION_MAJOR >= 5 +# endif // ifdef ESP32 last_seen(0), rssi(0), channel(0), index(index_c), enc_type(0) { _allBits = 0u; @@ -89,9 +98,9 @@ WiFi_AP_Candidate::WiFi_AP_Candidate(uint8_t networkItem) : index(0) { channel = WiFi.channel(networkItem); bssid = WiFi.BSSID(networkItem); enc_type = WiFi.encryptionType(networkItem); - #ifdef ESP8266 + # ifdef ESP8266 bits.isHidden = WiFi.isHidden(networkItem); - # ifdef CORE_POST_3_0_0 + # ifdef CORE_POST_3_0_0 const bss_info *it = reinterpret_cast(WiFi.getScanInfoByIndex(networkItem)); if (it) { @@ -100,9 +109,9 @@ WiFi_AP_Candidate::WiFi_AP_Candidate(uint8_t networkItem) : index(0) { bits.phy_11n = it->phy_11n; bits.wps = it->wps; } - # endif // ifdef CORE_POST_3_0_0 - #endif // ifdef ESP8266 - #ifdef ESP32 + # endif // ifdef CORE_POST_3_0_0 + # endif // ifdef ESP8266 + # ifdef ESP32 bits.isHidden = ssid.isEmpty(); wifi_ap_record_t *it = reinterpret_cast(WiFi.getScanInfoByIndex(networkItem)); @@ -111,54 +120,27 @@ WiFi_AP_Candidate::WiFi_AP_Candidate(uint8_t networkItem) : index(0) { bits.phy_11g = it->phy_11g; bits.phy_11n = it->phy_11n; bits.phy_lr = it->phy_lr; -# if ESP_IDF_VERSION_MAJOR >= 5 +# if ESP_IDF_VERSION_MAJOR >= 5 bits.phy_11ax = it->phy_11ax; bits.ftm_initiator = it->ftm_initiator; bits.ftm_responder = it->ftm_responder; -# endif // if ESP_IDF_VERSION_MAJOR >= 5 +# if CONFIG_SOC_WIFI_SUPPORT_5G + bits.phy_11a = it->phy_11a; + bits.phy_11ac = it->phy_11ac; +# endif // if CONFIG_SOC_WIFI_SUPPORT_5G +# endif // if ESP_IDF_VERSION_MAJOR >= 5 bits.wps = it->wps; // FIXME TD-er: Maybe also add other info like 2nd channel, ftm and phy_lr support? -# if ESP_IDF_VERSION_MAJOR >= 5 +# if ESP_IDF_VERSION_MAJOR >= 5 memcpy(&country, &(it->country), sizeof(wifi_country_t)); -#endif + bandwidth = it->bandwidth; +# endif // if ESP_IDF_VERSION_MAJOR >= 5 } - #endif // ifdef ESP32 + # endif // ifdef ESP32 last_seen = millis(); } -#ifdef ESP8266 -# if FEATURE_ESP8266_DIRECT_WIFI_SCAN -WiFi_AP_Candidate::WiFi_AP_Candidate(const bss_info& ap) : - rssi(ap.rssi), channel(ap.channel), bssid(ap.bssid), - index(0), enc_type(0), isHidden(ap.is_hidden), - phy_11b(ap.phy_11b), phy_11g(ap.phy_11g), phy_11n(ap.phy_11n), - wps(ap.wps) -{ - _allBits = 0u; - last_seen = millis(); - - switch (ap.authmode) { - case AUTH_OPEN: enc_type = ENC_TYPE_NONE; break; - case AUTH_WEP: enc_type = ENC_TYPE_WEP; break; - case AUTH_WPA_PSK: enc_type = ENC_TYPE_TKIP; break; - case AUTH_WPA2_PSK: enc_type = ENC_TYPE_CCMP; break; - case AUTH_WPA_WPA2_PSK: enc_type = ENC_TYPE_AUTO; break; - case AUTH_MAX: break; - } - - char tmp[33]; // ssid can be up to 32chars, => plus null term - const size_t ssid_len = std::min(static_cast(ap.ssid_len), sizeof(ap.ssid)); - - memcpy(tmp, ap.ssid, ssid_len); - tmp[ssid_len] = 0; // nullterm marking end of string - - ssid = String(reinterpret_cast(tmp)); -} - -# endif // if FEATURE_ESP8266_DIRECT_WIFI_SCAN -#endif // ifdef ESP8266 - bool WiFi_AP_Candidate::operator<(const WiFi_AP_Candidate& other) const { if (bits.isEmergencyFallback != other.bits.isEmergencyFallback) { @@ -185,19 +167,20 @@ bool WiFi_AP_Candidate::operator<(const WiFi_AP_Candidate& other) const { WiFi_AP_Candidate& WiFi_AP_Candidate::operator=(const WiFi_AP_Candidate& other) { - ssid = other.ssid; + ssid = other.ssid; last_seen = other.last_seen; - bssid = other.bssid; - rssi = other.rssi; - channel = other.channel; - index = other.index; - enc_type = other.enc_type; - _allBits = other._allBits; - #ifdef ESP32 - # if ESP_IDF_VERSION_MAJOR >= 5 + bssid = other.bssid; + rssi = other.rssi; + channel = other.channel; + index = other.index; + enc_type = other.enc_type; + _allBits = other._allBits; + # ifdef ESP32 + # if ESP_IDF_VERSION_MAJOR >= 5 memcpy(&this->country, &other.country, sizeof(wifi_country_t)); - #endif - #endif + bandwidth = other.bandwidth; + # endif // if ESP_IDF_VERSION_MAJOR >= 5 + # endif // ifdef ESP32 return *this; } @@ -207,20 +190,30 @@ bool WiFi_AP_Candidate::usable() const { // if (key.isEmpty()) return false; if (bits.isEmergencyFallback) { int allowedUptimeMinutes = 10; - #ifdef CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME + # ifdef CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME allowedUptimeMinutes = CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME; - #endif // ifdef CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME + # endif // ifdef CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME if ((getUptimeMinutes() > allowedUptimeMinutes) || !SecuritySettings.hasWiFiCredentials() || - WiFiEventData.performedClearWiFiCredentials || +// WiFiEventData.performedClearWiFiCredentials || (lastBootCause != BOOT_CAUSE_COLD_BOOT)) { return false; } } if (!bits.isHidden && (ssid.isEmpty())) { return false; } - return !expired(); + if (!fromScan()) { + // Not from a scan, thus usable + return true; + } + + return channel != 0 && !expired(); +} + +bool WiFi_AP_Candidate::fromScan() const +{ + return last_seen != 0; } bool WiFi_AP_Candidate::expired() const { @@ -246,6 +239,15 @@ String WiFi_AP_Candidate::toString(const String& separator) const { separator.c_str(), channel); +# if CONFIG_SOC_WIFI_SUPPORT_5G + + if (channel < 36) { + result += F(" 2.4 GHz"); + } else { + result += F(" 5 GHz"); + } +# endif // if CONFIG_SOC_WIFI_SUPPORT_5G + if (rssi == -1) { result += F(" (RTC) "); } else { @@ -254,12 +256,15 @@ String WiFi_AP_Candidate::toString(const String& separator) const { result += encryption_type(); -#ifdef ESP32 -# if ESP_IDF_VERSION_MAJOR >= 5 +# ifdef ESP32 +# if ESP_IDF_VERSION_MAJOR >= 5 + // Country code string - if (country.cc[0] != '\0' && country.cc[1] != '\0') { + if ((country.cc[0] != '\0') && (country.cc[1] != '\0')) { result += strformat(F(" '%c%c'"), country.cc[0], country.cc[1]); - switch (country.cc[2]) { + + switch (country.cc[2]) + { case 'O': // Outdoor case 'I': // Indoor case 'X': // "non-country" @@ -267,22 +272,43 @@ String WiFi_AP_Candidate::toString(const String& separator) const { break; } } + if (country.nchan > 0) { result += strformat(F(" ch: %d..%d"), country.schan, country.schan + country.nchan - 1); } -#endif -#endif +# endif // if ESP_IDF_VERSION_MAJOR >= 5 +# endif // ifdef ESP32 if (phy_known()) { String phy_str; + // Order of items is kinda in order of age, oldest first. + + // first 2.4 GHz version + // Later called "Wi-Fi 1" if (bits.phy_11b) { phy_str += 'b'; } + // 2nd 2.4 GHz version to adopt the same 54 Mbps bandwidth as used in 802.11a + // Later called "Wi-Fi 3" if (bits.phy_11g) { phy_str += 'g'; } + // 3rd iteration for 2.4 GHz + // Also used for 5 GHz to make it even more confusing + // To top the naming confusion even worse, this is later called "Wi-Fi 4" if (bits.phy_11n) { phy_str += 'n'; } -#ifdef ESP32 +# ifdef ESP32 + // Add 5 GHz WiFi types, oldest first. + + // 802.11a was one of the first WiFi standards, using 5 GHz + // Later called "Wi-Fi 2" + if (bits.phy_11a) { phy_str += F("/a"); } + + // 802.11ac was the significantly faster version for 5 GHz WiFi. + // Later called "Wi-Fi 5" + if (bits.phy_11ac) { phy_str += F("/ac"); } + + // AX is WiFi-6, on both frequencies if (bits.phy_11ax) { phy_str += F("/ax"); } if (bits.phy_lr) { phy_str += F("/lr"); } @@ -290,9 +316,28 @@ String WiFi_AP_Candidate::toString(const String& separator) const { if (bits.ftm_initiator) { phy_str += F("/FTM_i"); } if (bits.ftm_responder) { phy_str += F("/FTM_r"); } -#endif // ifdef ESP32 + +# endif // ifdef ESP32 if (phy_str.length()) { +# ifdef ESP32 + + switch (bandwidth) + { + case WIFI_BW_HT20: break; + case WIFI_BW_HT40: phy_str += F(" 40 MHz"); + break; + case WIFI_BW80: phy_str += F(" 80 MHz"); + break; + case WIFI_BW160: phy_str += F(" 160 MHz"); + break; + case WIFI_BW80_BW80: phy_str += F(" 80+80 MHz"); + break; + + default: + break; + } +# endif // ifdef ESP32 result += strformat(F(" (%s)"), phy_str.c_str()); } } @@ -302,3 +347,9 @@ String WiFi_AP_Candidate::toString(const String& separator) const { String WiFi_AP_Candidate::encryption_type() const { return WiFi_encryptionType(enc_type); } + +} // namespace wifi +} // namespace net +} // namespace ESPEasy + +#endif // if FEATURE_WIFI diff --git a/src/src/DataStructs/WiFi_AP_Candidate.h b/src/ESPEasy/net/DataStructs/WiFi_AP_Candidate.h similarity index 71% rename from src/src/DataStructs/WiFi_AP_Candidate.h rename to src/ESPEasy/net/DataStructs/WiFi_AP_Candidate.h index 970afe8f6..156176405 100644 --- a/src/src/DataStructs/WiFi_AP_Candidate.h +++ b/src/ESPEasy/net/DataStructs/WiFi_AP_Candidate.h @@ -1,9 +1,18 @@ -#ifndef DATASTRUCTS_WIFI_AP_CANDIDATES_H -#define DATASTRUCTS_WIFI_AP_CANDIDATES_H +#pragma once -#include "../../ESPEasy_common.h" +#include "../../../ESPEasy_common.h" #include "../DataStructs/MAC_address.h" + +#if FEATURE_WIFI + +namespace ESPEasy { +namespace net { +namespace wifi { + + +# define WIFI_AP_CANDIDATE_MAX_AGE 300000 // 5 minutes in msec + struct WiFi_AP_Candidate { WiFi_AP_Candidate(); WiFi_AP_Candidate(const WiFi_AP_Candidate& other); @@ -19,12 +28,6 @@ struct WiFi_AP_Candidate { // Construct using index from WiFi scan result WiFi_AP_Candidate(uint8_t networkItem); - #ifdef ESP8266 - # if FEATURE_ESP8266_DIRECT_WIFI_SCAN - WiFi_AP_Candidate(const bss_info& ap); - # endif // if FEATURE_ESP8266_DIRECT_WIFI_SCAN - #endif // ifdef ESP8266 - // Return true when this one is preferred over 'other'. bool operator<(const WiFi_AP_Candidate& other) const; @@ -38,6 +41,8 @@ struct WiFi_AP_Candidate { // Check if the candidate data can be used to actually connect to an AP. bool usable() const; + bool fromScan() const; + // Check if the candidate was recently seen bool expired() const; @@ -68,22 +73,23 @@ struct WiFi_AP_Candidate { return bits.phy_11b || bits.phy_11g || bits.phy_11n; } - String ssid; + String ssid; // String key; - #ifdef ESP32 - # if ESP_IDF_VERSION_MAJOR >= 5 - wifi_country_t country; - #endif - #endif + # ifdef ESP32 + # if ESP_IDF_VERSION_MAJOR >= 5 + wifi_country_t country; + wifi_bandwidth_t bandwidth{ WIFI_BW_HT20 }; + # endif // if ESP_IDF_VERSION_MAJOR >= 5 + # endif // ifdef ESP32 unsigned long last_seen = 0u; MAC_address bssid; int8_t rssi{}; uint8_t channel{}; - uint8_t index{}; // Index of the matching credentials - uint8_t enc_type{}; // Encryption used (e.g. WPA2) + uint8_t index{}; // Index of the matching credentials + uint8_t enc_type{}; // Encryption used (e.g. WPA2) union { struct { uint16_t isHidden : 1; // Hidden SSID @@ -93,15 +99,24 @@ struct WiFi_AP_Candidate { uint16_t phy_11g : 1; uint16_t phy_11n : 1; uint16_t phy_lr : 1; - uint16_t phy_11ax : 1; + uint16_t phy_11a : 1; // traditional (old) 5 GHz + uint16_t phy_11ac : 1; // 5 GHz WiFi 5, 802.11ac + uint16_t phy_11ax : 1; // WiFi 6, both bands possible uint16_t wps : 1; uint16_t ftm_responder : 1; uint16_t ftm_initiator : 1; - uint16_t unused : 5; - } bits; + uint16_t unused : 3; + + } bits; uint16_t _allBits; + }; + }; -#endif // ifndef DATASTRUCTS_WIFI_AP_CANDIDATES_H +} // namespace wifi +} // namespace net +} // namespace ESPEasy + +#endif // if FEATURE_WIFI diff --git a/src/src/DataStructs/WiFi_AP_Candidates_NVS.cpp b/src/ESPEasy/net/DataStructs/WiFi_AP_Candidates_NVS.cpp similarity index 84% rename from src/src/DataStructs/WiFi_AP_Candidates_NVS.cpp rename to src/ESPEasy/net/DataStructs/WiFi_AP_Candidates_NVS.cpp index 9810c11a1..24596b049 100644 --- a/src/src/DataStructs/WiFi_AP_Candidates_NVS.cpp +++ b/src/ESPEasy/net/DataStructs/WiFi_AP_Candidates_NVS.cpp @@ -1,9 +1,17 @@ #include "../DataStructs/WiFi_AP_Candidates_NVS.h" #ifdef ESP32 -# include "../Helpers/ESPEasy_NVS_Helper.h" +# include "../../../src/Helpers/ESPEasy_NVS_Helper.h" -# define WIFI_AP_CANDIDATE_NVS_KEY "WIFICANDIDATE" + +# if FEATURE_WIFI + +namespace ESPEasy { +namespace net { +namespace wifi { + + +# define WIFI_AP_CANDIDATE_NVS_KEY "WIFICANDIDATE" struct WiFi_AP_Candidates_NVS_data_t { union { @@ -11,10 +19,13 @@ struct WiFi_AP_Candidates_NVS_data_t { uint8_t BSSID[6]; uint8_t lastWiFiChannel; uint8_t lastWiFiSettingsIndex; + } APdata; uint64_t rawdata{}; + }; + }; bool WiFi_AP_Candidates_NVS::loadCandidate_from_NVS(WiFi_AP_Candidate& candidate) @@ -32,6 +43,7 @@ bool WiFi_AP_Candidates_NVS::loadCandidate_from_NVS(WiFi_AP_Candidate& candidate return false; } } + candidate.bssid.set(fromNVS.APdata.BSSID); candidate.channel = fromNVS.APdata.lastWiFiChannel; candidate.index = fromNVS.APdata.lastWiFiSettingsIndex; @@ -61,4 +73,10 @@ void WiFi_AP_Candidates_NVS::clear_from_NVS() } } +} // namespace wifi +} // namespace net +} // namespace ESPEasy + +# endif // if FEATURE_WIFI + #endif // ifdef ESP32 diff --git a/src/ESPEasy/net/DataStructs/WiFi_AP_Candidates_NVS.h b/src/ESPEasy/net/DataStructs/WiFi_AP_Candidates_NVS.h new file mode 100644 index 000000000..2aebc2f24 --- /dev/null +++ b/src/ESPEasy/net/DataStructs/WiFi_AP_Candidates_NVS.h @@ -0,0 +1,32 @@ +#pragma once + +#include "../../../ESPEasy_common.h" + +#ifdef ESP32 +# include "../DataStructs/WiFi_AP_Candidate.h" + +# if FEATURE_WIFI + +namespace ESPEasy { +namespace net { +namespace wifi { + + +class WiFi_AP_Candidates_NVS +{ +public: + + static bool loadCandidate_from_NVS(WiFi_AP_Candidate& candidate); + + static void currentConnection_to_NVS(const WiFi_AP_Candidate& candidate); + + static void clear_from_NVS(); +}; + +} // namespace wifi +} // namespace net +} // namespace ESPEasy + +# endif // if FEATURE_WIFI + +#endif // ifdef ESP32 diff --git a/src/src/DataTypes/EthernetParameters.cpp b/src/ESPEasy/net/DataTypes/EthernetParameters.cpp similarity index 51% rename from src/src/DataTypes/EthernetParameters.cpp rename to src/ESPEasy/net/DataTypes/EthernetParameters.cpp index ceab27833..8d8b20639 100644 --- a/src/src/DataTypes/EthernetParameters.cpp +++ b/src/ESPEasy/net/DataTypes/EthernetParameters.cpp @@ -1,75 +1,46 @@ #include "../DataTypes/EthernetParameters.h" -bool isValid(EthClockMode_t clockMode) { - switch (clockMode) { - case EthClockMode_t::Ext_crystal_osc: - case EthClockMode_t::Int_50MHz_GPIO_0: - case EthClockMode_t::Int_50MHz_GPIO_16: - case EthClockMode_t::Int_50MHz_GPIO_17_inv: - return true; +#if FEATURE_ETHERNET - // Do not use default: as this allows the compiler to detect any missing cases. - } - return false; -} - -bool isGpioUsedInETHClockMode(EthClockMode_t clockMode, - int8_t gpio) { - if (((clockMode == EthClockMode_t::Int_50MHz_GPIO_0) && (gpio == 0)) || - ((clockMode == EthClockMode_t::Int_50MHz_GPIO_16) && (gpio == 16)) || - ((clockMode == EthClockMode_t::Int_50MHz_GPIO_17_inv) && (gpio == 17))) { - return true; - } - return false; -} - -const __FlashStringHelper* toString(EthClockMode_t clockMode) { - switch (clockMode) { - case EthClockMode_t::Ext_crystal_osc: return F("External crystal oscillator"); - case EthClockMode_t::Int_50MHz_GPIO_0: return F("50MHz APLL Output on GPIO0"); - case EthClockMode_t::Int_50MHz_GPIO_16: return F("50MHz APLL Output on GPIO16"); - case EthClockMode_t::Int_50MHz_GPIO_17_inv: return F("50MHz APLL Inverted Output on GPIO17"); - - // Do not use default: as this allows the compiler to detect any missing cases. - } - return F("Unknown"); -} +namespace ESPEasy { +namespace net { bool isValid(EthPhyType_t phyType) { - switch (phyType) { -#if CONFIG_ETH_USE_ESP32_EMAC + switch (phyType) + { +# if CONFIG_ETH_USE_ESP32_EMAC && FEATURE_ETHERNET case EthPhyType_t::LAN8720: +# if ETH_PHY_LAN867X_SUPPORTED + case EthPhyType_t::LAN867X: +# endif case EthPhyType_t::TLK110: -# if ESP_IDF_VERSION_MAJOR > 3 case EthPhyType_t::RTL8201: -#if ETH_TYPE_JL1101_SUPPORTED +# if ETH_TYPE_JL1101_SUPPORTED case EthPhyType_t::JL1101: -#endif +# endif case EthPhyType_t::DP83848: case EthPhyType_t::KSZ8041: case EthPhyType_t::KSZ8081: -# endif // if ESP_IDF_VERSION_MAJOR > 3 return true; -#endif // if CONFIG_ETH_USE_ESP32_EMAC +# endif // if CONFIG_ETH_USE_ESP32_EMAC && FEATURE_ETHERNET -#if ESP_IDF_VERSION_MAJOR >= 5 -# if CONFIG_ETH_SPI_ETHERNET_DM9051 +# if ESP_IDF_VERSION_MAJOR >= 5 +# if CONFIG_ETH_SPI_ETHERNET_DM9051 case EthPhyType_t::DM9051: return true; -# endif // if CONFIG_ETH_SPI_ETHERNET_DM9051 -# if CONFIG_ETH_SPI_ETHERNET_W5500 +# endif // if CONFIG_ETH_SPI_ETHERNET_DM9051 +# if CONFIG_ETH_SPI_ETHERNET_W5500 case EthPhyType_t::W5500: return true; -# endif // if CONFIG_ETH_SPI_ETHERNET_W5500 -# if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL +# endif // if CONFIG_ETH_SPI_ETHERNET_W5500 +# if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL case EthPhyType_t::KSZ8851: return true; -# endif // if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL -#endif // if ESP_IDF_VERSION_MAJOR >= 5 +# endif // if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL +# endif // if ESP_IDF_VERSION_MAJOR >= 5 case EthPhyType_t::notSet: break; } return false; } -#if FEATURE_ETHERNET bool isSPI_EthernetType(EthPhyType_t phyType) { # if ESP_IDF_VERSION_MAJOR >= 5 return @@ -88,22 +59,43 @@ bool isSPI_EthernetType(EthPhyType_t phyType) { # endif // if ESP_IDF_VERSION_MAJOR >= 5 } +bool isRMII_EthernetType(EthPhyType_t phyType) { + return +# if CONFIG_ETH_USE_ESP32_EMAC && FEATURE_ETHERNET + phyType == EthPhyType_t::LAN8720 || +# if ETH_PHY_LAN867X_SUPPORTED + phyType == EthPhyType_t::LAN867X || +# endif + phyType == EthPhyType_t::TLK110 || + phyType == EthPhyType_t::RTL8201 || +# if ETH_TYPE_JL1101_SUPPORTED + phyType == EthPhyType_t::JL1101 || +# endif + phyType == EthPhyType_t::DP83848 || + phyType == EthPhyType_t::KSZ8041 || + phyType == EthPhyType_t::KSZ8081 || +# endif // if CONFIG_ETH_USE_ESP32_EMAC && FEATURE_ETHERNET + false; +} + eth_phy_type_t to_ESP_phy_type(EthPhyType_t phyType) { - switch (phyType) { -# if CONFIG_ETH_USE_ESP32_EMAC + switch (phyType) + { +# if CONFIG_ETH_USE_ESP32_EMAC && FEATURE_ETHERNET case EthPhyType_t::LAN8720: return ETH_PHY_LAN8720; +# if ETH_PHY_LAN867X_SUPPORTED + case EthPhyType_t::LAN867X: return ETH_PHY_LAN867X; +# endif case EthPhyType_t::TLK110: return ETH_PHY_TLK110; -# if ESP_IDF_VERSION_MAJOR > 3 case EthPhyType_t::RTL8201: return ETH_PHY_RTL8201; -# if ETH_TYPE_JL1101_SUPPORTED +# if ETH_TYPE_JL1101_SUPPORTED case EthPhyType_t::JL1101: return ETH_PHY_JL1101; -# endif +# endif case EthPhyType_t::DP83848: return ETH_PHY_DP83848; case EthPhyType_t::KSZ8041: return ETH_PHY_KSZ8041; case EthPhyType_t::KSZ8081: return ETH_PHY_KSZ8081; -# endif // if ESP_IDF_VERSION_MAJOR > 3 -# endif // if CONFIG_ETH_USE_ESP32_EMAC +# endif // if CONFIG_ETH_USE_ESP32_EMAC && FEATURE_ETHERNET # if ESP_IDF_VERSION_MAJOR >= 5 # if CONFIG_ETH_SPI_ETHERNET_DM9051 @@ -122,36 +114,35 @@ eth_phy_type_t to_ESP_phy_type(EthPhyType_t phyType) return ETH_PHY_MAX; } -#endif // if FEATURE_ETHERNET - - const __FlashStringHelper* toString(EthPhyType_t phyType) { - switch (phyType) { -#if CONFIG_ETH_USE_ESP32_EMAC - case EthPhyType_t::LAN8720: return F("LAN8710/LAN8720"); - case EthPhyType_t::TLK110: return F("TLK110"); -# if ESP_IDF_VERSION_MAJOR > 3 + switch (phyType) + { +# if CONFIG_ETH_USE_ESP32_EMAC && FEATURE_ETHERNET + case EthPhyType_t::LAN8720: return F("LAN8710 / LAN8720"); +# if ETH_PHY_LAN867X_SUPPORTED + case EthPhyType_t::LAN867X: return F("LAN867X"); +# endif + case EthPhyType_t::TLK110: return F("TLK110 / IP101"); case EthPhyType_t::RTL8201: return F("RTL8201"); -#if ETH_TYPE_JL1101_SUPPORTED +# if ETH_TYPE_JL1101_SUPPORTED case EthPhyType_t::JL1101: return F("JL1101"); -#endif +# endif case EthPhyType_t::DP83848: return F("DP83848"); case EthPhyType_t::KSZ8041: return F("KSZ8041"); case EthPhyType_t::KSZ8081: return F("KSZ8081"); -# endif // if ESP_IDF_VERSION_MAJOR > 3 -#endif // if CONFIG_ETH_USE_ESP32_EMAC +# endif // if CONFIG_ETH_USE_ESP32_EMAC && FEATURE_ETHERNET -#if ESP_IDF_VERSION_MAJOR >= 5 -# if CONFIG_ETH_SPI_ETHERNET_DM9051 +# if ESP_IDF_VERSION_MAJOR >= 5 +# if CONFIG_ETH_SPI_ETHERNET_DM9051 case EthPhyType_t::DM9051: return F("DM9051(SPI)"); -# endif // if CONFIG_ETH_SPI_ETHERNET_DM9051 -# if CONFIG_ETH_SPI_ETHERNET_W5500 +# endif // if CONFIG_ETH_SPI_ETHERNET_DM9051 +# if CONFIG_ETH_SPI_ETHERNET_W5500 case EthPhyType_t::W5500: return F("W5500(SPI)"); -# endif // if CONFIG_ETH_SPI_ETHERNET_W5500 -# if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL +# endif // if CONFIG_ETH_SPI_ETHERNET_W5500 +# if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL case EthPhyType_t::KSZ8851: return F("KSZ8851(SPI)"); -# endif // if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL -#endif // if ESP_IDF_VERSION_MAJOR >= 5 +# endif // if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL +# endif // if ESP_IDF_VERSION_MAJOR >= 5 case EthPhyType_t::notSet: break; @@ -159,3 +150,8 @@ const __FlashStringHelper* toString(EthPhyType_t phyType) { } return F("- None -"); } + +} // namespace net +} // namespace ESPEasy + +#endif // if FEATURE_ETHERNET diff --git a/src/ESPEasy/net/DataTypes/EthernetParameters.h b/src/ESPEasy/net/DataTypes/EthernetParameters.h new file mode 100644 index 000000000..11b43836d --- /dev/null +++ b/src/ESPEasy/net/DataTypes/EthernetParameters.h @@ -0,0 +1,99 @@ +#pragma once + +#include "../../../ESPEasy_common.h" + +#if FEATURE_ETHERNET + +# include + +namespace ESPEasy { +namespace net { + + +// Is stored in settings +# ifdef ESP32P4 + +// Clock pin can only be GPIO 32 / 44 / 50 (default) +enum class EthClockMode_t : uint8_t { + Default = 0, + Ext_crystal = 1, + Int_50MHz = 2, + + /* + Ext_crystal_GPIO_32 = (1 | (32 << 2)), + Ext_crystal_GPIO_44 = (1 | (44 << 2)), + Ext_crystal_GPIO_50 = (1 | (50 << 2)), + Int_50MHz_GPIO_32 = (2 | (32 << 2)), + Int_50MHz_GPIO_44 = (2 | (44 << 2)), + Int_50MHz_GPIO_50 = (2 | (50 << 2)), + */ + +}; + +# endif // ifdef ESP32P4 +# if CONFIG_IDF_TARGET_ESP32 +enum class EthClockMode_t : uint8_t { + Ext_crystal_osc = 0, + Int_50MHz_GPIO_0 = 1, + Int_50MHz_GPIO_16 = 2, + Int_50MHz_GPIO_17_inv = 3 + +}; + +# endif // if CONFIG_IDF_TARGET_ESP32 + +# if CONFIG_ETH_USE_ESP32_EMAC && FEATURE_ETHERNET + +bool isValid(EthClockMode_t clockMode); + +const __FlashStringHelper* toString(EthClockMode_t clockMode); + +bool isGpioUsedInETHClockMode(EthClockMode_t clockMode, + int8_t gpio); +# endif // if CONFIG_ETH_USE_ESP32_EMAC && FEATURE_ETHERNET + +// Is stored in settings +enum class EthPhyType_t : uint8_t { +# if CONFIG_ETH_USE_ESP32_EMAC && FEATURE_ETHERNET + LAN8720 = 0, + TLK110 = 1, + RTL8201 = 2, +# if ETH_TYPE_JL1101_SUPPORTED + JL1101 = 3, +# endif + DP83848 = 4, + KSZ8041 = 5, + KSZ8081 = 6, +# if ETH_PHY_LAN867X_SUPPORTED + LAN867X = 7, +# endif +# endif // if CONFIG_ETH_USE_ESP32_EMAC && FEATURE_ETHERNET +# if ESP_IDF_VERSION_MAJOR >= 5 +# if CONFIG_ETH_SPI_ETHERNET_DM9051 + DM9051 = 10, +# endif +# if CONFIG_ETH_SPI_ETHERNET_W5500 + W5500 = 11, +# endif +# if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL + KSZ8851 = 12, +# endif +# endif // if ESP_IDF_VERSION_MAJOR >= 5 + notSet = 127 // Might be processed in some code as int, uint8_t and int8_t + +}; + +bool isValid(EthPhyType_t phyType); + +bool isSPI_EthernetType(EthPhyType_t phyType); +bool isRMII_EthernetType(EthPhyType_t phyType); + +// Convert to internal enum type as those enum values may not always be the same int value +eth_phy_type_t to_ESP_phy_type(EthPhyType_t phyType); + +const __FlashStringHelper* toString(EthPhyType_t phyType); + +} // namespace net +} // namespace ESPEasy + +#endif // if FEATURE_ETHERNET diff --git a/src/ESPEasy/net/DataTypes/EthernetParameters_ESP32.cpp b/src/ESPEasy/net/DataTypes/EthernetParameters_ESP32.cpp new file mode 100644 index 000000000..5fd4e17e8 --- /dev/null +++ b/src/ESPEasy/net/DataTypes/EthernetParameters_ESP32.cpp @@ -0,0 +1,49 @@ +#include "../DataTypes/EthernetParameters.h" + + +#if CONFIG_IDF_TARGET_ESP32 && FEATURE_ETHERNET + +namespace ESPEasy { +namespace net { + +bool isValid(EthClockMode_t clockMode) { + switch (clockMode) + { + case EthClockMode_t::Ext_crystal_osc: + case EthClockMode_t::Int_50MHz_GPIO_0: + case EthClockMode_t::Int_50MHz_GPIO_16: + case EthClockMode_t::Int_50MHz_GPIO_17_inv: + return true; + + // Do not use default: as this allows the compiler to detect any missing cases. + } + return false; +} + +bool isGpioUsedInETHClockMode(EthClockMode_t clockMode, + int8_t gpio) { + if (((clockMode == EthClockMode_t::Int_50MHz_GPIO_0) && (gpio == 0)) || + ((clockMode == EthClockMode_t::Int_50MHz_GPIO_16) && (gpio == 16)) || + ((clockMode == EthClockMode_t::Int_50MHz_GPIO_17_inv) && (gpio == 17))) { + return true; + } + return false; +} + +const __FlashStringHelper* toString(EthClockMode_t clockMode) { + switch (clockMode) + { + case EthClockMode_t::Ext_crystal_osc: return F("External crystal oscillator"); + case EthClockMode_t::Int_50MHz_GPIO_0: return F("50MHz APLL Output on GPIO0"); + case EthClockMode_t::Int_50MHz_GPIO_16: return F("50MHz APLL Output on GPIO16"); + case EthClockMode_t::Int_50MHz_GPIO_17_inv: return F("50MHz APLL Inverted Output on GPIO17"); + + // Do not use default: as this allows the compiler to detect any missing cases. + } + return F("Unknown"); +} + +} // namespace net +} // namespace ESPEasy + +#endif // if CONFIG_IDF_TARGET_ESP32 diff --git a/src/ESPEasy/net/DataTypes/EthernetParameters_ESP32P4.cpp b/src/ESPEasy/net/DataTypes/EthernetParameters_ESP32P4.cpp new file mode 100644 index 000000000..b8800c8e5 --- /dev/null +++ b/src/ESPEasy/net/DataTypes/EthernetParameters_ESP32P4.cpp @@ -0,0 +1,76 @@ +#include "../DataTypes/EthernetParameters.h" + + +#if CONFIG_IDF_TARGET_ESP32P4 && FEATURE_ETHERNET + +# include + +namespace ESPEasy { +namespace net { + +bool isValid(EthClockMode_t clockMode) { + switch (clockMode) + { + case EthClockMode_t::Default: + case EthClockMode_t::Ext_crystal: + case EthClockMode_t::Int_50MHz: + /* + case EthClockMode_t::Ext_crystal_GPIO_32: + case EthClockMode_t::Ext_crystal_GPIO_44: + case EthClockMode_t::Ext_crystal_GPIO_50: + case EthClockMode_t::Int_50MHz_GPIO_32: + case EthClockMode_t::Int_50MHz_GPIO_44: + case EthClockMode_t::Int_50MHz_GPIO_50: + */ + return true; + + // Do not use default: as this allows the compiler to detect any missing cases. + } + return false; +} + +bool isGpioUsedInETHClockMode(EthClockMode_t clockMode, + int8_t gpio) { + switch (clockMode) + { + case EthClockMode_t::Default: + case EthClockMode_t::Ext_crystal: + case EthClockMode_t::Int_50MHz: return gpio == ETH_RMII_CLK; + + /* + case EthClockMode_t::Int_50MHz_GPIO_32: + case EthClockMode_t::Ext_crystal_GPIO_32: return (gpio == 32); + case EthClockMode_t::Int_50MHz_GPIO_44: + case EthClockMode_t::Ext_crystal_GPIO_44: return (gpio == 44); + case EthClockMode_t::Int_50MHz_GPIO_50: + case EthClockMode_t::Ext_crystal_GPIO_50: return (gpio == 50); + */ + } + return false; +} + +const __FlashStringHelper* toString(EthClockMode_t clockMode) { + switch (clockMode) + { + case EthClockMode_t::Default: return F("default"); + case EthClockMode_t::Ext_crystal: return F("External crystal oscillator"); + case EthClockMode_t::Int_50MHz: return F("50MHz APLL Output"); + + /* + case EthClockMode_t::Ext_crystal_GPIO_32: return F("External crystal oscillator on GPIO-32"); + case EthClockMode_t::Ext_crystal_GPIO_44: return F("External crystal oscillator on GPIO-44"); + case EthClockMode_t::Ext_crystal_GPIO_50: return F("External crystal oscillator on GPIO-50"); + case EthClockMode_t::Int_50MHz_GPIO_32: return F("50MHz APLL Output on GPIO-32"); + case EthClockMode_t::Int_50MHz_GPIO_44: return F("50MHz APLL Output on GPIO-44"); + case EthClockMode_t::Int_50MHz_GPIO_50: return F("50MHz APLL Output on GPIO-50"); + */ + + // Do not use default: as this allows the compiler to detect any missing cases. + } + return F("Unknown"); +} + +} // namespace net +} // namespace ESPEasy + +#endif // if CONFIG_IDF_TARGET_ESP32P4 diff --git a/src/ESPEasy/net/DataTypes/NWPluginID.cpp b/src/ESPEasy/net/DataTypes/NWPluginID.cpp new file mode 100644 index 000000000..2257949cc --- /dev/null +++ b/src/ESPEasy/net/DataTypes/NWPluginID.cpp @@ -0,0 +1,16 @@ +#include "../DataTypes/NWPluginID.h" + +#include "../../../src/Helpers/StringConverter.h" + +namespace ESPEasy { +namespace net { + +String nwpluginID_t::toDisplayString() const { + if (value == 0) { return F("NW---"); } + return strformat(F("NW%03d"), value); +} + +const nwpluginID_t INVALID_NW_PLUGIN_ID{}; + +} // namespace net +} // namespace ESPEasy diff --git a/src/ESPEasy/net/DataTypes/NWPluginID.h b/src/ESPEasy/net/DataTypes/NWPluginID.h new file mode 100644 index 000000000..b8f27244d --- /dev/null +++ b/src/ESPEasy/net/DataTypes/NWPluginID.h @@ -0,0 +1,70 @@ +#pragma once + +#include "../../../ESPEasy_common.h" + +namespace ESPEasy { +namespace net { + +struct __attribute__((__packed__)) nwpluginID_t { + nwpluginID_t() = default; + + nwpluginID_t(const nwpluginID_t& other) + { + value = other.value; + } + + constexpr explicit nwpluginID_t(uint8_t id) : value(id) {} + + static nwpluginID_t toPluginID(unsigned other) + { + nwpluginID_t res; + + if (other <= 255) { res.value = other; } + + return res; + } + + bool isValid() const + { + return value != 0; + } + + operator int() const + { + return value; + } + + operator bool() const + { + return isValid(); + } + + nwpluginID_t& operator=(const nwpluginID_t& other) + { + value = other.value; + return *this; + } + + bool operator==(const nwpluginID_t& other) const + { + return this->value == other.value; + } + + bool operator!=(const nwpluginID_t& other) const + { + return this->value != other.value; + } + + void setInvalid() { value = 0; } + + String toDisplayString() const; + + uint8_t value{}; + +}; + + +extern const nwpluginID_t INVALID_NW_PLUGIN_ID; + +} // namespace net +} // namespace ESPEasy diff --git a/src/ESPEasy/net/DataTypes/NetworkDriverIndex.cpp b/src/ESPEasy/net/DataTypes/NetworkDriverIndex.cpp new file mode 100644 index 000000000..3d432b69b --- /dev/null +++ b/src/ESPEasy/net/DataTypes/NetworkDriverIndex.cpp @@ -0,0 +1,11 @@ +#include "../DataTypes/NetworkDriverIndex.h" + +#include "../../../src/CustomBuild/ESPEasyLimits.h" + +namespace ESPEasy { +namespace net { + +networkDriverIndex_t INVALID_NETWORKDRIVER_INDEX = networkDriverIndex_t::toNetworkDriverIndex(NETWORKDRIVER_INDEX_MAX); + +} // namespace net +} // namespace ESPEasy diff --git a/src/ESPEasy/net/DataTypes/NetworkDriverIndex.h b/src/ESPEasy/net/DataTypes/NetworkDriverIndex.h new file mode 100644 index 000000000..1fd608ff2 --- /dev/null +++ b/src/ESPEasy/net/DataTypes/NetworkDriverIndex.h @@ -0,0 +1,73 @@ +#pragma once + +#include "../../../ESPEasy_common.h" + +#include "../../../src/CustomBuild/ESPEasyLimits.h" + +namespace ESPEasy { +namespace net { + +struct networkDriverIndex_t { + networkDriverIndex_t() = default; + + networkDriverIndex_t(const networkDriverIndex_t& other) + { + value = other.value; + } + + static networkDriverIndex_t toNetworkDriverIndex(unsigned other) + { + networkDriverIndex_t res; + res = other; + return res; + } + + // TD-er: Do not add constructor with int as argument, + // as this makes it impossible for the compiler to typecheck its use. + // networkDriverIndex_t(int other); + + networkDriverIndex_t& operator=(unsigned other) + { + value = (other < NETWORKDRIVER_INDEX_MAX) ? other : NETWORKDRIVER_INDEX_MAX; + return *this; + } + + networkDriverIndex_t& operator=(const networkDriverIndex_t& other) + { + value = other.value; + return *this; + } + + // TD-er: Using operator unsigned() makes it impossible for the compiler to check for types. + // However, since the NetworkDriver[] array is accessed using a networkDriverIndex_t, we need this operator unsigned() + // on ESP8266. + // On ESP32 we have a strongly typed NetworkDriverVector class and thus we can properly check per operator. + #ifndef ESP8266 + bool operator<(unsigned other) const { return value < other; } + + bool operator!=(unsigned other) const { return value != other; } + + bool operator!=(const networkDriverIndex_t& other) const { return value != other.value; } + + #else // ifndef ESP8266 + operator unsigned() const { + return value; + } + #endif // ifndef ESP8266 + + networkDriverIndex_t& operator++() + { + // pre-increment, ++a + ++value; + return *this; + } + + uint8_t value{}; // Init this to 0, so we can easily iterate over it. + +}; + + +extern networkDriverIndex_t INVALID_NETWORKDRIVER_INDEX; + +} // namespace net +} // namespace ESPEasy diff --git a/src/ESPEasy/net/DataTypes/NetworkIndex.cpp b/src/ESPEasy/net/DataTypes/NetworkIndex.cpp new file mode 100644 index 000000000..3a8ff57bf --- /dev/null +++ b/src/ESPEasy/net/DataTypes/NetworkIndex.cpp @@ -0,0 +1,12 @@ +#include "../DataTypes/NetworkIndex.h" + +#include "../../../src/CustomBuild/ESPEasyLimits.h" + +namespace ESPEasy { +namespace net { + +const networkIndex_t INVALID_NETWORK_INDEX = NETWORK_MAX; +const networkStatsVarIndex_t INVALID_NETWORK_STATS_VAR_INDEX = 4; // TODO TD-er: Must see how much values we have to record + +} // namespace net +} // namespace ESPEasy diff --git a/src/ESPEasy/net/DataTypes/NetworkIndex.h b/src/ESPEasy/net/DataTypes/NetworkIndex.h new file mode 100644 index 000000000..6219ac0aa --- /dev/null +++ b/src/ESPEasy/net/DataTypes/NetworkIndex.h @@ -0,0 +1,16 @@ +#pragma once + +#include "../../../ESPEasy_common.h" + +namespace ESPEasy { +namespace net { + +typedef uint8_t networkIndex_t; +typedef uint16_t networkStatsVarIndex_t; + + +extern const networkIndex_t INVALID_NETWORK_INDEX; +extern const networkStatsVarIndex_t INVALID_NETWORK_STATS_VAR_INDEX; + +} // namespace net +} // namespace ESPEasy diff --git a/src/ESPEasy/net/DataTypes/NetworkMedium.cpp b/src/ESPEasy/net/DataTypes/NetworkMedium.cpp new file mode 100644 index 000000000..85b55bf61 --- /dev/null +++ b/src/ESPEasy/net/DataTypes/NetworkMedium.cpp @@ -0,0 +1,41 @@ + +#include "../DataTypes/NetworkMedium.h" + +namespace ESPEasy { +namespace net { + +bool isValid(ESPEasy::net::NetworkMedium_t medium) { + switch (medium) + { + case ESPEasy::net::NetworkMedium_t::WIFI: + case ESPEasy::net::NetworkMedium_t::Ethernet: +#ifdef USES_ESPEASY_NOW + case ESPEasy::net::NetworkMedium_t::ESPEasyNOW_only: +#endif + return true; + + case ESPEasy::net::NetworkMedium_t::NotSet: + return false; + + // Do not use default: as this allows the compiler to detect any missing cases. + } + return false; +} + +const __FlashStringHelper* toString(ESPEasy::net::NetworkMedium_t medium) { + switch (medium) + { + case ESPEasy::net::NetworkMedium_t::WIFI: return F("WiFi"); + case ESPEasy::net::NetworkMedium_t::Ethernet: return F("Ethernet"); +#ifdef USES_ESPEASY_NOW + case ESPEasy::net::NetworkMedium_t::ESPEasyNOW_only: return F(ESPEASY_NOW_NAME " only"); +#endif + case ESPEasy::net::NetworkMedium_t::NotSet: return F("Not Set"); + + // Do not use default: as this allows the compiler to detect any missing cases. + } + return F("Unknown"); +} + +} // namespace net +} // namespace ESPEasy diff --git a/src/ESPEasy/net/DataTypes/NetworkMedium.h b/src/ESPEasy/net/DataTypes/NetworkMedium.h new file mode 100644 index 000000000..704cfe569 --- /dev/null +++ b/src/ESPEasy/net/DataTypes/NetworkMedium.h @@ -0,0 +1,25 @@ +#pragma once + +#include "../../../ESPEasy_common.h" + +namespace ESPEasy { +namespace net { + + +// Is stored in settings +enum class NetworkMedium_t : uint8_t { + WIFI = 0, + Ethernet = 1, +#ifdef USES_ESPEASY_NOW + ESPEasyNOW_only = 2, +#endif + NotSet = 255 + +}; + +bool isValid(NetworkMedium_t medium); + +const __FlashStringHelper* toString(NetworkMedium_t medium); + +} // namespace net +} // namespace ESPEasy diff --git a/src/ESPEasy/net/ESPEasyNetwork.cpp b/src/ESPEasy/net/ESPEasyNetwork.cpp new file mode 100644 index 000000000..4e9d57b08 --- /dev/null +++ b/src/ESPEasy/net/ESPEasyNetwork.cpp @@ -0,0 +1,129 @@ +#include "../net/ESPEasyNetwork.h" + +#include "../net/wifi/ESPEasyWifi.h" +#include "../net/Globals/NetworkState.h" + +#include "../../src/Globals/ESPEasy_time.h" +#include "../../src/Globals/Settings.h" + +#include "../../src/Helpers/Networking.h" +#include "../../src/Helpers/StringConverter.h" +#include "../../src/Helpers/MDNS_Helper.h" + +namespace ESPEasy { +namespace net { + +// ******************************************************************************** +// Determine Wifi AP name to set. (also used for mDNS) +// ******************************************************************************** +String NetworkGetHostNameFromSettings(bool force_add_unitnr) +{ + if (force_add_unitnr) { return Settings.getHostname(true); } + return Settings.getHostname(); +} + +String NetworkCreateRFCCompliantHostname(bool force_add_unitnr) { + // Create hostname with - instead of spaces + return makeRFCCompliantName(NetworkGetHostNameFromSettings(force_add_unitnr)); +} + +String makeRFCCompliantName(const String& name, const char replaceChar, const char allowedChar, const size_t maxlength) { + String hostname(name); + + // See RFC952. (when using the default arguments: '-', '-', 24) + // Allowed chars: + // * letters (a-z, A-Z) + // * numerals (0-9) + // * Hyphen (-) + // * Max length 24 + replaceUnicodeByChar(hostname, replaceChar); + + for (size_t i = 0; i < hostname.length(); ++i) { + const char c = hostname[i]; + + if (!isAlphaNumeric(c) && (c != allowedChar)) { + hostname[i] = replaceChar; + } + } + + // May not start or end with a hyphen + const String dash(replaceChar); + + while (hostname.startsWith(dash)) { + hostname = hostname.substring(1); + } + + while (hostname.endsWith(dash)) { + hostname = hostname.substring(0, hostname.length() - 1); + } + + // May not contain only numerals + bool onlyNumerals = true; + + for (size_t i = 0; onlyNumerals && i < hostname.length(); ++i) { + const char c = hostname[i]; + + if (!isdigit(c)) { + onlyNumerals = false; + } + } + + if (onlyNumerals) { + hostname = strformat(F("ESPEasy%c%s"), replaceChar, hostname.c_str()); + } + + if ((maxlength > 0) && (hostname.length() > maxlength)) { + hostname = hostname.substring(0, maxlength); + } + + return hostname; +} + +void CheckRunningServices() { + // First try to get the time, since that may be used in logs + if (Settings.UseNTP() && (node_time.getTimeSource() > timeSource_t::NTP_time_source)) { + node_time.lastNTPSyncTime_ms = 0; + node_time.initTime(); + } +#if FEATURE_ESPEASY_P2P + updateUDPport(true); +#endif + +#if FEATURE_WIFI +# if FEATURE_SET_WIFI_TX_PWR + + if (active_network_medium == NetworkMedium_t::WIFI) + { + ESPEasy::net::wifi::SetWiFiTXpower(); + } +# endif // if FEATURE_SET_WIFI_TX_PWR +#endif // if FEATURE_WIFI + +#if FEATURE_MDNS + set_mDNS(); + #endif +} + +uint64_t NetworkConnectDuration_ms() +{ + auto data = getDefaultRoute_NWPluginData_static_runtime(); + + if (data == nullptr) { + return 0ull; + } + return data->_connectedStats.getLastOnDuration_ms(); +} + +uint32_t NetworkConnectCount() +{ + auto data = getDefaultRoute_NWPluginData_static_runtime(); + + if (data == nullptr) { + return 0ul; + } + return data->_connectedStats.getCycleCount(); +} + + +} // namespace net +} // namespace ESPEasy diff --git a/src/ESPEasy/net/ESPEasyNetwork.h b/src/ESPEasy/net/ESPEasyNetwork.h new file mode 100644 index 000000000..22534cf74 --- /dev/null +++ b/src/ESPEasy/net/ESPEasyNetwork.h @@ -0,0 +1,76 @@ +#pragma once + +#include "../../ESPEasy_common.h" + +#include "../net/DataStructs/MAC_address.h" + +#include + +#if FEATURE_USE_IPV6 +# include + +// typedef uint8_t ip6_addr_type_t; +// typedef std::vector> IP6Addresses_t; +typedef std::vector IP6Addresses_t; +#endif // if FEATURE_USE_IPV6 + + +#define NETWORK_INDEX_WIFI_STA 0 // Always the first network index +#define NETWORK_INDEX_WIFI_AP 1 // Always the 2nd network index + +namespace ESPEasy { +namespace net { + +void setNetworkMedium(NetworkMedium_t medium); + +// void NetworkConnectRelaxed(); +bool NetworkConnected(bool force = false); +IPAddress NetworkLocalIP(); +IPAddress NetworkID(); +IPAddress NetworkBroadcast(); +IPAddress NetworkSubnetMask(); +IPAddress NetworkGatewayIP(); +IPAddress NetworkDnsIP(uint8_t dns_no); +uint64_t NetworkConnectDuration_ms(); +uint32_t NetworkConnectCount(); + +#if FEATURE_USE_IPV6 + +IPAddress NetworkLocalIP6(); +IPAddress NetworkGlobalIP6(); +IP6Addresses_t NetworkAllIPv6(); + +bool IPv6_link_local_from_MAC(const MAC_address& mac, + IPAddress & ipv6); +bool is_IPv6_link_local_from_MAC(const MAC_address& mac); + +// Assume we're in the same subnet, thus use our own IPv6 global address +bool IPv6_global_from_MAC(const MAC_address& mac, + IPAddress & ipv6); +bool is_IPv6_global_from_MAC(const MAC_address& mac); + +#endif // if FEATURE_USE_IPV6 +MAC_address NetworkMacAddress(); +String NetworkGetHostNameFromSettings(bool force_add_unitnr = false); +String NetworkGetHostname(); +String NetworkCreateRFCCompliantHostname(bool force_add_unitnr = false); +String makeRFCCompliantName(const String& name, + const char replaceChar = '-', + const char allowedChar = '-', + const size_t maxlength = 24); +#if FEATURE_WIFI +MAC_address WifiSoftAPmacAddress(); +MAC_address WifiSTAmacAddress(); +#endif // if FEATURE_WIFI + +void CheckRunningServices(); + +#if FEATURE_ETHERNET +bool EthFullDuplex(); +bool EthLinkUp(); +uint8_t EthLinkSpeed(); +#endif // if FEATURE_ETHERNET + + +} // namespace net +} // namespace ESPEasy diff --git a/src/ESPEasy/net/ESPEasyNetwork_ESP32.cpp b/src/ESPEasy/net/ESPEasyNetwork_ESP32.cpp new file mode 100644 index 000000000..346f53627 --- /dev/null +++ b/src/ESPEasy/net/ESPEasyNetwork_ESP32.cpp @@ -0,0 +1,369 @@ +#ifdef ESP32 + +#include "../net/ESPEasyNetwork.h" + +#include "../../src/ESPEasyCore/ESPEasy_Log.h" +#include "../net/eth/ESPEasyEth.h" +#include "../net/wifi/ESPEasyWifi.h" + +#include "../../src/Globals/ESPEasy_time.h" +#include "../../ESPEasy/net/Globals/ESPEasyWiFiEvent.h" +#include "../../ESPEasy/net/Globals/NetworkState.h" +#include "../../src/Globals/Settings.h" + +#include "../../src/Helpers/NetworkStatusLED.h" +#include "../../src/Helpers/Networking.h" +#include "../../src/Helpers/StringConverter.h" +#include "../../src/Helpers/MDNS_Helper.h" + +#include + +#if FEATURE_ETHERNET +# include +#endif + +#if FEATURE_USE_IPV6 +# include +#endif + + +// Forward declaration to access internal function in NetworkInterface.cpp +// as we don't have a public function in NetworkInterface to access the +// Network_Interface_ID +NetworkInterface* getNetifByID(Network_Interface_ID id); + + +namespace ESPEasy { +namespace net { + +void setNetworkMedium(NetworkMedium_t new_medium) { +#if !(FEATURE_ETHERNET) + + if (new_medium == NetworkMedium_t::Ethernet) { + new_medium = NetworkMedium_t::WIFI; + } +#endif // if !(FEATURE_ETHERNET) + + if (new_medium == active_network_medium) { + return; + } + + switch (active_network_medium) + { + case NetworkMedium_t::WIFI: + WiFi.STA.end(); + break; + case NetworkMedium_t::Ethernet: +#if FEATURE_ETHERNET + ETH.end(); +#endif // if FEATURE_ETHERNET + break; + case NetworkMedium_t::NotSet: + break; + } + + switch (new_medium) + { + case NetworkMedium_t::WIFI: + // WiFi.STA.setDefault(); + break; + case NetworkMedium_t::Ethernet: +#if FEATURE_ETHERNET + + // ETH.setDefault(); +#endif // if FEATURE_ETHERNET + break; + case NetworkMedium_t::NotSet: + break; + } + statusLED(true); + active_network_medium = new_medium; + addLog(LOG_LEVEL_INFO, concat(F("Set Network mode: "), toString(active_network_medium))); +} + +/*********************************************************************************************\ + Ethernet or Wifi Support for ESP32 Build flag FEATURE_ETHERNET +\*********************************************************************************************/ + +/* + void NetworkConnectRelaxed() { + if (ESPEasy::net::NetworkConnected()) { return; } + #if FEATURE_ETHERNET + + if (active_network_medium == NetworkMedium_t::Ethernet) { + if (ESPEasy::net::eth::ETHConnectRelaxed()) { + return; + } + + // Failed to start the Ethernet network, probably not present of wrong parameters. + // So set the runtime active medium to WiFi to try connecting to WiFi or at least start the AP. + setNetworkMedium(NetworkMedium_t::WIFI); + } + #endif // if FEATURE_ETHERNET + + // Failed to start the Ethernet network, probably not present of wrong parameters. + // So set the runtime active medium to WiFi to try connecting to WiFi or at least start the AP. + // ESPEasy::net::wifi::WiFiConnectRelaxed(); + } + */ +NetworkInterface* getDefaultNonAP_interface() +{ + auto network_if = Network.getDefaultInterface(); + + if (network_if != nullptr) { + auto ap_if = getNetifByID(ESP_NETIF_ID_AP); + + if (ap_if != nullptr) { + if (network_if->netif() == ap_if->netif()) { + // This is the AP interface, which we do not want + return nullptr; + } + } + } + return network_if; +} + +bool NetworkConnected(bool force) { + static bool last_result = false; + static uint32_t last_check_millis = 0; + + if (force || (timePassedSince(last_check_millis) > 50) || (last_check_millis == 0)) { + last_check_millis = millis(); + last_result = Network.isOnline(); + + // FIXME TD-er: This is checking for NonAP interfaces, however we also have + // ESPEasy::net::NWPluginCall(NWPlugin::Function::NWPLUGIN_WEBSERVER_SHOULD_RUN); + // So maybe we should keep bitmasks for interfaces that can be used for certain services and update those whenever an interface is + // connected/disconnected + // This way a service may 'subscribe' to changes and stopped/started/restarted when needed. + // For example: + // - updateUDPport() + // - CheckRunningServices() + // - mDNS + // - Webserver + + processNetworkEvents(); + } + return last_result; +} + +IPAddress NetworkLocalIP() { + auto network_if = getDefaultNonAP_interface(); + + if (network_if == nullptr) { + return IPAddress(); + } + return network_if->localIP(); +} + +IPAddress NetworkID() +{ + auto network_if = getDefaultNonAP_interface(); + + if (network_if == nullptr) { + return IPAddress(); + } + return network_if->networkID(); +} + +IPAddress NetworkBroadcast() +{ + auto network_if = getDefaultNonAP_interface(); + + if (network_if == nullptr) { + return IPAddress(); + } + return network_if->broadcastIP(); +} + +IPAddress NetworkSubnetMask() { + auto network_if = getDefaultNonAP_interface(); + + if (network_if == nullptr) { + return IPAddress(); + } + return network_if->subnetMask(); +} + +IPAddress NetworkGatewayIP() { + auto network_if = getDefaultNonAP_interface(); + + if (network_if == nullptr) { + return IPAddress(); + } + return network_if->gatewayIP(); +} + +IPAddress NetworkDnsIP(uint8_t dns_no) { + auto network_if = getDefaultNonAP_interface(); + + if (network_if == nullptr) { + return IPAddress(); + } + return network_if->dnsIP(dns_no); +} + +#if FEATURE_USE_IPV6 + +IPAddress NetworkLocalIP6() { + auto network_if = getDefaultNonAP_interface(); + + if (network_if == nullptr) { + return IN6ADDR_ANY; + } + return network_if->linkLocalIPv6(); +} + +IPAddress NetworkGlobalIP6() { + auto network_if = getDefaultNonAP_interface(); + + if (network_if == nullptr) { + return IN6ADDR_ANY; + } + return network_if->globalIPv6(); +} + +IP6Addresses_t NetworkAllIPv6() { + IP6Addresses_t addresses; + auto network_if = getDefaultNonAP_interface(); + + if (network_if != nullptr) { + esp_ip6_addr_t esp_ip6_addr[LWIP_IPV6_NUM_ADDRESSES]{}; + + int count = esp_netif_get_all_ip6(network_if->netif(), esp_ip6_addr); + + for (int i = 0; i < count; ++i) { + addresses.emplace_back(IPv6, (const uint8_t *)esp_ip6_addr[i].addr, esp_ip6_addr[i].zone); + } + } + + return addresses; +} + +bool IPv6_from_MAC(const MAC_address& mac, IPAddress& ipv6) +{ + if (ipv6 == IN6ADDR_ANY) { return false; } + int index_offset = 8; + + for (int i = 0; i < 6; ++i, ++index_offset) { + ipv6[index_offset] = mac.mac[i]; + + if (i == 0) { + // invert bit 2 + bitToggle(ipv6[index_offset], 1); + } + + if (i == 2) { + ipv6[++index_offset] = 0xFF; + ipv6[++index_offset] = 0xFE; + } + } + + /* + addLog(LOG_LEVEL_INFO, strformat( + F("IPv6_from_MAC: Mac %s IP %s"), + mac.toString().c_str(), + ipv6.toString(true).c_str() + )); + */ + return true; +} + +bool is_IPv6_based_on_MAC(const MAC_address& mac, const IPAddress& ipv6) +{ + IPAddress tmp = ipv6; + + if (IPv6_from_MAC(mac, tmp)) { + return ipv6 == tmp; + } + return false; +} + +bool IPv6_link_local_from_MAC(const MAC_address& mac, IPAddress& ipv6) +{ + ipv6 = NetworkLocalIP6(); + return IPv6_from_MAC(mac, ipv6); +} + +bool is_IPv6_link_local_from_MAC(const MAC_address& mac) { return is_IPv6_based_on_MAC(mac, NetworkLocalIP6()); } + +// Assume we're in the same subnet, thus use our own IPv6 global address +bool IPv6_global_from_MAC(const MAC_address& mac, IPAddress& ipv6) +{ + ipv6 = NetworkGlobalIP6(); + return IPv6_from_MAC(mac, ipv6); +} + +bool is_IPv6_global_from_MAC(const MAC_address& mac) { return is_IPv6_based_on_MAC(mac, NetworkGlobalIP6()); } + +#endif // if FEATURE_USE_IPV6 + +MAC_address NetworkMacAddress() { + MAC_address res; + auto network_if = Network.getDefaultInterface(); + + if (network_if != nullptr) { + network_if->macAddress(res.mac); + } + return res; +} + +String NetworkGetHostname() { + auto network_if = Network.getDefaultInterface(); + + if (network_if != nullptr) { + return network_if->getHostname(); + } + return EMPTY_STRING; +} + +#if FEATURE_WIFI + +MAC_address WifiSoftAPmacAddress() { + MAC_address mac; + + WiFi.AP.macAddress(mac.mac); + return mac; +} + +MAC_address WifiSTAmacAddress() { + MAC_address mac; + + WiFi.macAddress(mac.mac); + return mac; +} + +#endif // if FEATURE_WIFI + +#if FEATURE_ETHERNET + +bool EthFullDuplex() +{ + auto data = getFirst_Enabled_ETH_interface(); + + if (data && data->connected()) { return data->fullDuplex(); } + return false; +} + +bool EthLinkUp() +{ + auto data = getFirst_Enabled_ETH_interface(); + + if (data) { return data->linkUp(); } + return false; +} + +uint8_t EthLinkSpeed() +{ + auto data = getFirst_Enabled_ETH_interface(); + + if (data) { return data->linkSpeed(); } + return 0; +} + +#endif // if FEATURE_ETHERNET + +} // namespace net +} // namespace ESPEasy + +#endif // ifdef ESP32 diff --git a/src/ESPEasy/net/ESPEasyNetwork_ESP8266.cpp b/src/ESPEasy/net/ESPEasyNetwork_ESP8266.cpp new file mode 100644 index 000000000..935ece720 --- /dev/null +++ b/src/ESPEasy/net/ESPEasyNetwork_ESP8266.cpp @@ -0,0 +1,130 @@ +#ifdef ESP8266 + +#include "../../ESPEasy/net/ESPEasyNetwork.h" + +#include "../../src/ESPEasyCore/ESPEasy_Log.h" + +#include "../../src/Globals/ESPEasy_time.h" +#include "../../src/Globals/Settings.h" + +#include "../../src/Helpers/NetworkStatusLED.h" +#include "../../src/Helpers/Networking.h" +#include "../../src/Helpers/StringConverter.h" +#include "../../src/Helpers/MDNS_Helper.h" + +#include "../net/Globals/ESPEasyWiFiEvent.h" +#include "../net/Globals/NetworkState.h" +#include "../net/wifi/ESPEasyWifi.h" + + +namespace ESPEasy { +namespace net { + +void setNetworkMedium(NetworkMedium_t new_medium) { + if (new_medium == NetworkMedium_t::Ethernet) { + new_medium = NetworkMedium_t::WIFI; + } + + if (new_medium == active_network_medium) { + return; + } + + switch (active_network_medium) + { + case NetworkMedium_t::Ethernet: + break; + case NetworkMedium_t::WIFI: +// WiFiEventData.timerAPoff.setMillisFromNow(WIFI_AP_OFF_TIMER_DURATION); +// WiFiEventData.timerAPstart.clear(); + break; + case NetworkMedium_t::NotSet: + break; + } + statusLED(true); + active_network_medium = new_medium; +#ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, concat(F("Set Network mode: "), toString(active_network_medium))); +#endif +} + +/*********************************************************************************************\ + Ethernet or Wifi Support for ESP32 Build flag FEATURE_ETHERNET +\*********************************************************************************************/ + +// void NetworkConnectRelaxed() { +// if (!ESPEasy::net::NetworkConnected()) +// ESPEasy::net::wifi::WiFiConnectRelaxed(); +// } + +bool NetworkConnected(bool force) { + static bool last_result = false; + static uint32_t last_check_millis = 0; + if (force || timePassedSince(last_check_millis) > 50 || last_check_millis == 0) { + last_check_millis = millis(); + processNetworkEvents(); + last_result = ESPEasy::net::wifi::WiFiConnected(); + } + return last_result; +} + +IPAddress NetworkLocalIP() { return WiFi.localIP(); } + +IPAddress NetworkID() +{ + const IPAddress subnet = NetworkSubnetMask(); + IPAddress networkID = NetworkLocalIP(); + + for (uint8_t i = 0; i < 4; ++i) { + networkID[i] &= subnet[i]; + } + return networkID; +} + +IPAddress NetworkBroadcast() +{ + const IPAddress subnet = NetworkSubnetMask(); + IPAddress broadcast = NetworkLocalIP(); + + for (uint8_t i = 0; i < 4; ++i) { + broadcast[i] |= ~subnet[i]; + } + return broadcast; +} + +IPAddress NetworkSubnetMask() { return WiFi.subnetMask(); } + +IPAddress NetworkGatewayIP() { return WiFi.gatewayIP(); } + +IPAddress NetworkDnsIP(uint8_t dns_no) { return WiFi.dnsIP(dns_no); } + +MAC_address NetworkMacAddress() { + MAC_address mac; + + WiFi.macAddress(mac.mac); + return mac; +} + +String NetworkGetHostname() { return String(WiFi.hostname()); } + +#if FEATURE_WIFI + +MAC_address WifiSoftAPmacAddress() { + MAC_address mac; + + WiFi.softAPmacAddress(mac.mac); + return mac; +} + +MAC_address WifiSTAmacAddress() { + MAC_address mac; + + WiFi.macAddress(mac.mac); + return mac; +} + +#endif // if FEATURE_WIFI + +} // namespace net +} // namespace ESPEasy + +#endif // ifdef ESP8266 diff --git a/src/ESPEasy/net/Globals/ESPEasyWiFi.cpp b/src/ESPEasy/net/Globals/ESPEasyWiFi.cpp new file mode 100644 index 000000000..32a06fe4c --- /dev/null +++ b/src/ESPEasy/net/Globals/ESPEasyWiFi.cpp @@ -0,0 +1,7 @@ +#include "../Globals/ESPEasyWiFi.h" + +#if FEATURE_WIFI + +ESPEasy::net::wifi::ESPEasyWiFi_t ESPEasyWiFi; + +#endif // if FEATURE_WIFI diff --git a/src/ESPEasy/net/Globals/ESPEasyWiFi.h b/src/ESPEasy/net/Globals/ESPEasyWiFi.h new file mode 100644 index 000000000..48d641718 --- /dev/null +++ b/src/ESPEasy/net/Globals/ESPEasyWiFi.h @@ -0,0 +1,11 @@ +#pragma once + +#include "../../../ESPEasy_common.h" + +#if FEATURE_WIFI + +# include "../wifi/ESPEasyWifi.h" +# include "../wifi/ESPEasyWiFi_state_machine.h" + +extern ESPEasy::net::wifi::ESPEasyWiFi_t ESPEasyWiFi; +#endif // if FEATURE_WIFI diff --git a/src/src/Globals/ESPEasyWiFiEvent.cpp b/src/ESPEasy/net/Globals/ESPEasyWiFiEvent.cpp similarity index 72% rename from src/src/Globals/ESPEasyWiFiEvent.cpp rename to src/ESPEasy/net/Globals/ESPEasyWiFiEvent.cpp index 9ec0c0a46..5790178c3 100644 --- a/src/src/Globals/ESPEasyWiFiEvent.cpp +++ b/src/ESPEasy/net/Globals/ESPEasyWiFiEvent.cpp @@ -1,9 +1,10 @@ #include "../Globals/ESPEasyWiFiEvent.h" -#include "../../ESPEasy_common.h" +#include "../../../ESPEasy_common.h" +#if FEATURE_WIFI -#ifdef ESP8266 +# ifdef ESP8266 WiFiEventHandler stationConnectedHandler; WiFiEventHandler stationDisconnectedHandler; WiFiEventHandler stationGotIpHandler; @@ -11,8 +12,7 @@ WiFiEventHandler stationModeDHCPTimeoutHandler; WiFiEventHandler stationModeAuthModeChangeHandler; WiFiEventHandler APModeStationConnectedHandler; WiFiEventHandler APModeStationDisconnectedHandler; -#endif // ifdef ESP8266 - -WiFiEventData_t WiFiEventData; +# endif // ifdef ESP8266 +#endif // if FEATURE_WIFI diff --git a/src/ESPEasy/net/Globals/ESPEasyWiFiEvent.h b/src/ESPEasy/net/Globals/ESPEasyWiFiEvent.h new file mode 100644 index 000000000..d3d679d89 --- /dev/null +++ b/src/ESPEasy/net/Globals/ESPEasyWiFiEvent.h @@ -0,0 +1,34 @@ +#pragma once + +#include "../../../ESPEasy_common.h" + +#if FEATURE_WIFI + + +# include +# include + + +# ifdef ESP32 +# include +# include +# include + +# endif // ifdef ESP32 + +# ifdef ESP8266 +# include +# include +class IPAddress; + +extern WiFiEventHandler stationConnectedHandler; +extern WiFiEventHandler stationDisconnectedHandler; +extern WiFiEventHandler stationGotIpHandler; +extern WiFiEventHandler stationModeDHCPTimeoutHandler; +extern WiFiEventHandler stationModeAuthModeChangeHandler; +extern WiFiEventHandler APModeStationConnectedHandler; +extern WiFiEventHandler APModeStationDisconnectedHandler; +# endif // ifdef ESP8266 + + +#endif // if FEATURE_WIFI diff --git a/src/ESPEasy/net/Globals/NWPlugins.cpp b/src/ESPEasy/net/Globals/NWPlugins.cpp new file mode 100644 index 000000000..da640fb6e --- /dev/null +++ b/src/ESPEasy/net/Globals/NWPlugins.cpp @@ -0,0 +1,567 @@ +#include "../Globals/NWPlugins.h" + +#include "../../../_Plugin_Helper.h" +#include "../../../src/DataStructs/ESPEasy_EventStruct.h" +#include "../../../src/DataStructs/TimingStats.h" +#include "../../../src/DataTypes/ESPEasy_plugin_functions.h" +#include "../../../src/ESPEasyCore/ESPEasy_Log.h" +#include "../../../src/Globals/SecuritySettings.h" +#include "../../../src/Globals/Settings.h" +#include "../../../src/Helpers/PrintToString.h" +#include "../Helpers/_NWPlugin_init.h" +#include "../Helpers/NWAccessControl.h" +#include "../_NWPlugin_Helper.h" + +#if FEATURE_ETHERNET +# include "../eth/ETH_NWPluginData_static_runtime.h" +#endif + + +namespace ESPEasy { +namespace net { + +bool NWPluginCall(NWPlugin::Function Function, EventStruct *event) { + #ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; + #endif // ifdef USE_SECOND_HEAP + + String dummy; + + return NWPluginCall(Function, event, dummy); +} + +bool NWPluginCall(NWPlugin::Function Function, EventStruct *event, String& str) +{ + #ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; + #endif // ifdef USE_SECOND_HEAP + + EventStruct TempEvent; + + if (event == 0) { + event = &TempEvent; + } + + switch (Function) + { + case NWPlugin::Function::NWPLUGIN_DRIVER_ADD: + // only called from NWPluginSetup() directly using networkDriverIndex + break; + + case NWPlugin::Function::NWPLUGIN_GET_PARAMETER_DISPLAY_NAME: + // Only called from _NWPlugin_Helper_webform getNetworkParameterName + break; + + // calls to all active networks + case NWPlugin::Function::NWPLUGIN_INIT_ALL: + case NWPlugin::Function::NWPLUGIN_EXIT_ALL: + case NWPlugin::Function::NWPLUGIN_TEN_PER_SECOND: + case NWPlugin::Function::NWPLUGIN_FIFTY_PER_SECOND: + case NWPlugin::Function::NWPLUGIN_WRITE: + case NWPlugin::Function::NWPLUGIN_WEBSERVER_SHOULD_RUN: +#ifdef ESP32 + case NWPlugin::Function::NWPLUGIN_PRIORITY_ROUTE_CHANGED: +#endif + { + // Set to true where return value doesn't matter + bool success = +#ifdef ESP32 + Function != NWPlugin::Function::NWPLUGIN_PRIORITY_ROUTE_CHANGED && +#endif + Function != NWPlugin::Function::NWPLUGIN_WRITE && + Function != NWPlugin::Function::NWPLUGIN_WEBSERVER_SHOULD_RUN; + + if (Function == NWPlugin::Function::NWPLUGIN_EXIT_ALL) { + // Called before shutdown, so must immediately exit network adapters + // Especially PPP modem may no longer respond if not turned off properly before reboot + Function = NWPlugin::Function::NWPLUGIN_EXIT; + } + + for (networkIndex_t x = 0; x < NETWORK_MAX; x++) { + const bool checkedEnabled = + Settings.getNetworkEnabled(x) || + Function == NWPlugin::Function::NWPLUGIN_EXIT; + + if (Settings.getNWPluginID_for_network(x) && checkedEnabled) { + if (Function == NWPlugin::Function::NWPLUGIN_INIT_ALL) { + Scheduler.setNetworkInitTimer(Settings.getNetworkInterfaceStartupDelayAtBoot(x), x); + } else { + event->NetworkIndex = x; + String command; + + if (Function == NWPlugin::Function::NWPLUGIN_WRITE) { + command = str; + } + + if (do_NWPluginCall( + getNetworkDriverIndex_from_NetworkIndex(x), + Function, + event, + command)) { + if ((Function == NWPlugin::Function::NWPLUGIN_WRITE) || + (Function == NWPlugin::Function::NWPLUGIN_WEBSERVER_SHOULD_RUN)) { + // Need to stop when call was handled + return true; + } + success = true; + } + } + } + } +#ifdef ESP32 + if (Function == NWPlugin::Function::NWPLUGIN_PRIORITY_ROUTE_CHANGED) { + CheckRunningServices(); + } +#endif + return success; + } + + // calls to specific network's NWPluginData_base (must be enabled) +#if FEATURE_NETWORK_TRAFFIC_COUNT + case NWPlugin::Function::NWPLUGIN_GET_TRAFFIC_COUNT: +#endif +#if FEATURE_NETWORK_STATS + case NWPlugin::Function::NWPLUGIN_RECORD_STATS: + case NWPlugin::Function::NWPLUGIN_WEBFORM_LOAD_SHOW_STATS: +#endif // if FEATURE_NETWORK_STATS + case NWPlugin::Function::NWPLUGIN_PROCESS_EVENT: + case NWPlugin::Function::NWPLUGIN_GET_CONNECTED_DURATION: + { + bool success = false; + + if (!validNetworkIndex(event->NetworkIndex) || + !Settings.getNetworkEnabled(event->NetworkIndex)) { return false; } + + auto *NW_data = getNWPluginData(event->NetworkIndex); + + if (NW_data) { + switch (Function) + { +#if FEATURE_NETWORK_TRAFFIC_COUNT + case NWPlugin::Function::NWPLUGIN_GET_TRAFFIC_COUNT: + { + TX_RX_traffic_count traffic{}; + + if (NW_data->getTrafficCount(traffic)) { + event->Par64_1 = traffic._tx_count; + event->Par64_2 = traffic._rx_count; + event->Par5 = traffic._tx_packets; + event->Par6 = traffic._rx_packets; + success = true; + + if (event->kvWriter) { + if (event->kvWriter->dataOnlyOutput()) { + { + auto frames = event->kvWriter->createChild(F("Frames")); + + if (frames) { + frames->write({ F("TX"), traffic._tx_packets }); + frames->write({ F("RX"), traffic._rx_packets }); + } + } + { + auto bytes = event->kvWriter->createChild(F("Bytes")); + + if (bytes) { + bytes->write({ F("TX"), traffic._tx_count }); + bytes->write({ F("RX"), traffic._rx_count }); + } + } + } else { + event->kvWriter->write({ + F("TX / RX Frames"), + strformat( + F("%d / %d"), + event->Par5, + event->Par6) }); + + event->kvWriter->write({ + F("TX / RX Frame Bytes"), + strformat( + F("%s%cB / %s%cB"), + formatHumanReadable(event->Par64_1, 1024).c_str(), + event->Par64_1 < 1024 ? ' ' : 'i', + formatHumanReadable(event->Par64_2, 1024).c_str(), + event->Par64_2 < 1024 ? ' ' : 'i' + ) }); + } + } + } + break; + } +#endif // if FEATURE_NETWORK_TRAFFIC_COUNT +#if FEATURE_NETWORK_STATS + case NWPlugin::Function::NWPLUGIN_RECORD_STATS: + { + // NWPluginData_static_runtime& runtime_data = NW_data->getNWPluginData_static_runtime(); + success = NW_data->record_stats(); + break; + } + case NWPlugin::Function::NWPLUGIN_WEBFORM_LOAD_SHOW_STATS: + { + // NWPluginData_static_runtime& runtime_data = NW_data->getNWPluginData_static_runtime(); + success = NW_data->webformLoad_show_stats(event); + break; + } +#endif // if FEATURE_NETWORK_STATS + + case NWPlugin::Function::NWPLUGIN_PROCESS_EVENT: + { + auto runtimeData = NW_data->getNWPluginData_static_runtime(); + + if (runtimeData) { + runtimeData->processEvents(); + } + break; + } + + case NWPlugin::Function::NWPLUGIN_GET_CONNECTED_DURATION: + { + auto runtime_data = NW_data->getNWPluginData_static_runtime(); + + if (runtime_data && runtime_data->_connectedStats.isSet()) { + const bool connected = runtime_data->_connectedStats.isOn(); + auto duration = connected + ? runtime_data->_connectedStats.getLastOnDuration_ms() + : runtime_data->_connectedStats.getLastOffDuration_ms(); + + if (duration > 0) { + event->Par64_1 = duration; + event->Par64_2 = runtime_data->_connectedStats.getCycleCount(); + + if (event->kvWriter) { + event->kvWriter->write({ + connected ? F("Connection Duration") : F("Disconnected Duration"), + format_msec_duration_HMS(duration) }); + + if (!event->kvWriter->summaryValueOnly()) { + event->kvWriter->write({ F("Number of Reconnects"), event->Par64_2 }); + } + } + success = true; + } + } + break; + } + default: break; + } + } + + return success; + } + + + // calls to specific network which need to be enabled before calling + case NWPlugin::Function::NWPLUGIN_INIT: + case NWPlugin::Function::NWPLUGIN_CONNECT_SUCCESS: + case NWPlugin::Function::NWPLUGIN_CONNECT_FAIL: + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_ACTIVE: + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_CONNECTED: + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_EXTENDED: + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_HW_ADDRESS: + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_IP: +#ifndef LIMIT_BUILD_SIZE + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_PORT: +#endif + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_HOSTNAME: + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_NAME: + case NWPlugin::Function::NWPLUGIN_CLIENT_IP_WEB_ACCESS_ALLOWED: + +#ifdef ESP32 + case NWPlugin::Function::NWPLUGIN_GET_INTERFACE: + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_ROUTE_PRIO: +#endif // ifdef ESP32 + + if (!validNetworkIndex(event->NetworkIndex) || + !Settings.getNetworkEnabled(event->NetworkIndex)) { return false; } + + // fall through + + // calls to specific network + case NWPlugin::Function::NWPLUGIN_LOAD_DEFAULTS: + case NWPlugin::Function::NWPLUGIN_EXIT: + case NWPlugin::Function::NWPLUGIN_GET_DEVICENAME: + case NWPlugin::Function::NWPLUGIN_WEBFORM_LOAD: + case NWPlugin::Function::NWPLUGIN_WEBFORM_SAVE: + + case NWPlugin::Function::NWPLUGIN_DRIVER_TEMPLATE: + { + const networkIndex_t networkIndex = event->NetworkIndex; + bool success = false; + + if (validNetworkIndex(networkIndex)) { + if (supportedNWPluginID(Settings.getNWPluginID_for_network(networkIndex))) + { + const networkDriverIndex_t networkDriverIndex = + getNetworkDriverIndex_from_NetworkIndex(networkIndex); + + success = do_NWPluginCall( + networkDriverIndex, + Function, + event, + str); +#ifdef ESP32 + + if (!success && NWPlugin::canQueryViaNetworkInterface(Function)) { + String dummy_str; + + if (do_NWPluginCall( + networkDriverIndex, + NWPlugin::Function::NWPLUGIN_GET_INTERFACE, + event, + dummy_str)) { + if (event->networkInterface != nullptr) { + switch (Function) + { + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_ROUTE_PRIO: +# if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 2) + + // TODO TD-er: Must also add option to set route prio + // See: https://github.com/espressif/arduino-esp32/pull/11617 + event->Par1 = event->networkInterface->getRoutePrio(); +# else // if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) + event->Par1 = event->networkInterface->route_prio(); +# endif // if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) + event->Par2 = event->networkInterface->isDefault(); + success = true; + break; + + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_ACTIVE: + { + success = event->networkInterface->started(); + break; + } + + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_NAME: + + if (event->kvWriter) { + event->kvWriter->write({ F("Name"), event->networkInterface->desc() }); + success = true; + } + break; + + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_HOSTNAME: + + if (event->kvWriter) { + event->kvWriter->write({ F("Hostname"), event->networkInterface->getHostname() }); + success = true; + } + break; + + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_HW_ADDRESS: + + if (event->kvWriter) { + if (event->kvWriter->summaryValueOnly()) { + event->kvWriter->write({ + EMPTY_STRING, + concat(F("MAC: "), event->networkInterface->macAddress()) }); + } + else { + event->kvWriter->write({ + F("MAC"), + event->networkInterface->macAddress(), + KeyValueStruct::Format::PreFormatted + }); + } + success = true; + } + break; + + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_IP: + { + if (event->kvWriter) { + PrintToString prstr; + success = NWPlugin::print_IP_address( + static_cast(event->Par1), + event->networkInterface, + prstr); + event->kvWriter->write({ F("ip"), prstr.getMove() }); + success = true; + } + break; + } + + case NWPlugin::Function::NWPLUGIN_CLIENT_IP_WEB_ACCESS_ALLOWED: + { + IPAddress client_ip; + client_ip.fromString(str); + + if ((SecuritySettings.IPblockLevel == LOCAL_SUBNET_ALLOWED) && + !Settings.getNetworkInterfaceSubnetBlockClientIP(event->NetworkIndex)) { + success = NWPlugin::IP_in_subnet(client_ip, event->networkInterface); + } else if (SecuritySettings.IPblockLevel == ONLY_IP_RANGE_ALLOWED) { + const IPAddress low(SecuritySettings.AllowedIPrangeLow); + const IPAddress high(SecuritySettings.AllowedIPrangeHigh); + success = NWPlugin::ipInRange(client_ip, low, high) && + NWPlugin::IP_in_subnet(low, event->networkInterface) && + NWPlugin::IP_in_subnet(high, event->networkInterface); + } else { + success = true; + } + break; + } + + default: break; + } + } + } + } +#endif // ifdef ESP32 + } +#ifdef ESP32 + + if (Function == NWPlugin::Function::NWPLUGIN_EXIT) { + // Cache.clearNetworkSettings(networkIndex); + auto data = getNWPluginData_static_runtime(event->NetworkIndex); + + if (data) { + delay(100); // Allow some time to process events + data->processEvent_and_clear(); + } + clearNWPluginData(event->NetworkIndex); + } +#endif // ifdef ESP32 + } + return success; + } + + } + return false; +} + +bool validNetworkDriverIndex(networkDriverIndex_t index) { return do_check_validNetworkDriverIndex(index); } + + +// bool getIP(networkDriverIndex_t index, NWPlugin::IP_type ip_type){} + +/* + bool validNetworkIndex(networkIndex_t index) + { + return index < NETWORK_MAX; + } + */ +bool validNWPluginID(nwpluginID_t nwpluginID) { + return do_getNetworkDriverIndex_from_NWPluginID(nwpluginID) != + INVALID_NETWORKDRIVER_INDEX; +} + +bool supportedNWPluginID(nwpluginID_t nwpluginID) { + return validNetworkDriverIndex( + do_getNetworkDriverIndex_from_NWPluginID(nwpluginID)); +} + +networkDriverIndex_t getNetworkDriverIndex_from_NetworkIndex(networkIndex_t index) { + if (validNetworkIndex(index)) { + return do_getNetworkDriverIndex_from_NWPluginID(Settings.getNWPluginID_for_network(index)); + } + return INVALID_NETWORKDRIVER_INDEX; +} + +networkDriverIndex_t getNetworkDriverIndex_from_NWPluginID(nwpluginID_t nwpluginID) { + return do_getNetworkDriverIndex_from_NWPluginID(nwpluginID); +} + +nwpluginID_t getNWPluginID_from_NetworkDriverIndex(networkDriverIndex_t index) { return do_getNWPluginID_from_NetworkDriverIndex(index); +} + +nwpluginID_t getNWPluginID_from_NetworkIndex(networkIndex_t index) { + const networkDriverIndex_t networkDriverIndex = getNetworkDriverIndex_from_NetworkIndex(index); + + return do_getNWPluginID_from_NetworkDriverIndex(networkDriverIndex); +} + +String getNWPluginNameFromNetworkDriverIndex(networkDriverIndex_t NetworkDriverIndex) { + String networkName; + + if (validNetworkDriverIndex(NetworkDriverIndex)) { + do_NWPluginCall(NetworkDriverIndex, NWPlugin::Function::NWPLUGIN_GET_DEVICENAME, nullptr, networkName); + } + return networkName; +} + +String getNWPluginNameFromNWPluginID(nwpluginID_t nwpluginID) { + networkDriverIndex_t networkDriverIndex = getNetworkDriverIndex_from_NWPluginID(nwpluginID); + + if (!validNetworkDriverIndex(networkDriverIndex)) { + return strformat(F("NWPlugin %d not included in build"), nwpluginID.value); + } + return getNWPluginNameFromNetworkDriverIndex(networkDriverIndex); +} + +NWPluginData_static_runtime* getWiFi_STA_NWPluginData_static_runtime() { return getNWPluginData_static_runtime(NETWORK_INDEX_WIFI_STA); } + +NWPluginData_static_runtime* getWiFi_AP_NWPluginData_static_runtime() { return getNWPluginData_static_runtime(NETWORK_INDEX_WIFI_AP); } + +#if FEATURE_ETHERNET + +ETHClass* getFirst_Enabled_ETH_interface() +{ + for (networkIndex_t i = 0; validNetworkIndex(i); ++i) { + if (Settings.getNetworkEnabled(i)) { + const auto nwpluginID = Settings.getNWPluginID_for_network(i); + + if ((nwpluginID.value == 3) || (nwpluginID.value == 4)) { + return ESPEasy::net::eth::ETH_NWPluginData_static_runtime::getInterface(i); + } + } + } + return nullptr; +} + +#endif // if FEATURE_ETHERNET + +NWPluginData_static_runtime* getNWPluginData_static_runtime(networkIndex_t index) +{ + auto NW_data = getNWPluginData(index); + + if (!NW_data) { return nullptr; } + return NW_data->getNWPluginData_static_runtime(); +} + +const NWPluginData_static_runtime* getDefaultRoute_NWPluginData_static_runtime() +{ + #ifdef ESP32 + networkIndex_t index_highest_prio = NETWORK_MAX; + int highest_prio = -1; + #endif // ifdef ESP32 + + for (networkIndex_t i = 0; validNetworkIndex(i); ++i) { + auto NW_data = getNWPluginData_static_runtime(i); + + if (NW_data && NW_data->isDefaultRoute()) { return NW_data; } + #ifdef ESP32 + + if (NW_data) { + const int route_prio = NW_data->_routePrio; + if (route_prio > highest_prio) { + index_highest_prio = i; + highest_prio = route_prio; + } + } + #endif // ifdef ESP32 + } + #ifdef ESP32 + + if (validNetworkIndex(index_highest_prio)) { + return getNWPluginData_static_runtime(index_highest_prio); + } + + #endif // ifdef ESP32 + + return nullptr; +} + +void processNetworkEvents() +{ + START_TIMER; + + for (networkIndex_t i = 0; i < NETWORK_MAX; ++i) { + EventStruct tmpEvent; + tmpEvent.NetworkIndex = i; + NWPluginCall(NWPlugin::Function::NWPLUGIN_PROCESS_EVENT, &tmpEvent); + } + STOP_TIMER(NWPLUGIN_PROCESS_NETWORK_EVENTS); +} + +} // namespace net +} // namespace ESPEasy diff --git a/src/ESPEasy/net/Globals/NWPlugins.h b/src/ESPEasy/net/Globals/NWPlugins.h new file mode 100644 index 000000000..e89915f48 --- /dev/null +++ b/src/ESPEasy/net/Globals/NWPlugins.h @@ -0,0 +1,61 @@ +#pragma once + +#include "../../../ESPEasy_common.h" + +#include "../../../src/CustomBuild/ESPEasyLimits.h" +#include "../../../src/DataStructs/ESPEasy_EventStruct.h" +#include "../../../src/DataTypes/ESPEasy_plugin_functions.h" +#include "../DataTypes/NWPluginID.h" +#include "../DataTypes/NetworkIndex.h" +#include "../DataTypes/NetworkDriverIndex.h" +#include "../DataStructs/NWPluginData_static_runtime.h" +#if FEATURE_ETHERNET +#include "../eth/ETH_NWPluginData_static_runtime.h" +#endif + +namespace ESPEasy { +namespace net { + + +bool NWPluginCall(NWPlugin::Function Function, + EventStruct *event = nullptr); +bool NWPluginCall(NWPlugin::Function Function, + EventStruct *event, + String & str); + + +bool validNetworkDriverIndex(networkDriverIndex_t index); + +// bool getIP(networkDriverIndex_t index, NWPlugin::IP_type ip_type); + + +// bool validNetworkIndex(networkIndex_t index); +#define validNetworkIndex(C_X) ((C_X) < NETWORK_MAX) + +// Check whether NWPlugin is included in build. +bool validNWPluginID(nwpluginID_t nwpluginID); + +// Check if nwplugin is included in build. +// N.B. Invalid nwplugin is also not considered supported. +// This is essentially (validNWPluginID && validNetworkDriverIndex) +bool supportedNWPluginID(nwpluginID_t nwpluginID); +networkDriverIndex_t getNetworkDriverIndex_from_NetworkIndex(networkIndex_t index); +networkDriverIndex_t getNetworkDriverIndex_from_NWPluginID(nwpluginID_t nwpluginID); +nwpluginID_t getNWPluginID_from_NetworkDriverIndex(networkDriverIndex_t index); +nwpluginID_t getNWPluginID_from_NetworkIndex(networkIndex_t index); + +String getNWPluginNameFromNetworkDriverIndex(networkDriverIndex_t NetworkDriverIndex); +String getNWPluginNameFromNWPluginID(nwpluginID_t nwpluginID); + +NWPluginData_static_runtime* getWiFi_STA_NWPluginData_static_runtime(); +NWPluginData_static_runtime* getWiFi_AP_NWPluginData_static_runtime(); +#if FEATURE_ETHERNET +ETHClass* getFirst_Enabled_ETH_interface(); +#endif +NWPluginData_static_runtime* getNWPluginData_static_runtime(networkIndex_t index); +const NWPluginData_static_runtime* getDefaultRoute_NWPluginData_static_runtime(); + +void processNetworkEvents(); + +} // namespace net +} // namespace ESPEasy diff --git a/src/src/Globals/NetworkState.cpp b/src/ESPEasy/net/Globals/NetworkState.cpp similarity index 61% rename from src/src/Globals/NetworkState.cpp rename to src/ESPEasy/net/Globals/NetworkState.cpp index e5c286c85..1be5bf28c 100644 --- a/src/src/Globals/NetworkState.cpp +++ b/src/ESPEasy/net/Globals/NetworkState.cpp @@ -1,10 +1,10 @@ #include "../Globals/NetworkState.h" -#include "../../ESPEasy_common.h" +#include "../../../ESPEasy_common.h" // Ethernet Connection status -NetworkMedium_t active_network_medium = NetworkMedium_t::NotSet; +ESPEasy::net::NetworkMedium_t active_network_medium = ESPEasy::net::NetworkMedium_t::NotSet; bool webserverRunning(false); bool webserver_init(false); @@ -20,9 +20,12 @@ bool statusNTPInitialized = false; // Setup DNS, only used if the ESP has no valid WiFi config const uint8_t DNS_PORT = 53; -IPAddress apIP(DEFAULT_AP_IP); - +IPAddress apIP(DEFAULT_AP_IP); // udp protocol stuff (syslog, global sync, node info list, ntp time) WiFiUDP portUDP; +#ifdef ESP32 +bool nonDefaultNetworkInterface_gotIP{}; +#endif + diff --git a/src/src/Globals/NetworkState.h b/src/ESPEasy/net/Globals/NetworkState.h similarity index 54% rename from src/src/Globals/NetworkState.h rename to src/ESPEasy/net/Globals/NetworkState.h index b7664cb84..91af0e97a 100644 --- a/src/src/Globals/NetworkState.h +++ b/src/ESPEasy/net/Globals/NetworkState.h @@ -1,16 +1,16 @@ -#ifndef GLOBALS_NETWORKSTATE_H -#define GLOBALS_NETWORKSTATE_H +#pragma once -#include "../../ESPEasy_common.h" +#include "../../../ESPEasy_common.h" #include #include -#include "../DataTypes/ESPEasy_plugin_functions.h" -#include "../DataTypes/NetworkMedium.h" +#include "../../../src/DataTypes/ESPEasy_plugin_functions.h" +#include "../../../src/DataTypes/NetworkMedium.h" + // Ethernet Connectiopn status -extern NetworkMedium_t active_network_medium; +extern ESPEasy::net::NetworkMedium_t active_network_medium; extern bool webserverRunning; extern bool webserver_init; @@ -25,10 +25,10 @@ extern bool statusNTPInitialized; // Setup DNS, only used if the ESP has no valid WiFi config extern const uint8_t DNS_PORT; -extern IPAddress apIP; +extern IPAddress apIP; // udp protocol stuff (syslog, global sync, node info list, ntp time) extern WiFiUDP portUDP; - - -#endif // GLOBALS_NETWORKSTATE_H +#ifdef ESP32 +extern bool nonDefaultNetworkInterface_gotIP; +#endif diff --git a/src/ESPEasy/net/Globals/WiFi_AP_Candidates.cpp b/src/ESPEasy/net/Globals/WiFi_AP_Candidates.cpp new file mode 100644 index 000000000..8932549d1 --- /dev/null +++ b/src/ESPEasy/net/Globals/WiFi_AP_Candidates.cpp @@ -0,0 +1,16 @@ +#include "../Globals/WiFi_AP_Candidates.h" + +#if FEATURE_WIFI + +namespace ESPEasy { +namespace net { +namespace wifi { + + +WiFi_AP_CandidatesList WiFi_AP_Candidates; + +} // namespace wifi +} // namespace net +} // namespace ESPEasy + +#endif // if FEATURE_WIFI diff --git a/src/ESPEasy/net/Globals/WiFi_AP_Candidates.h b/src/ESPEasy/net/Globals/WiFi_AP_Candidates.h new file mode 100644 index 000000000..11f8e5c4f --- /dev/null +++ b/src/ESPEasy/net/Globals/WiFi_AP_Candidates.h @@ -0,0 +1,18 @@ +#pragma once + +#include "../Helpers/WiFi_AP_CandidatesList.h" + +#if FEATURE_WIFI + +namespace ESPEasy { +namespace net { +namespace wifi { + + +extern WiFi_AP_CandidatesList WiFi_AP_Candidates; + +} // namespace wifi +} // namespace net +} // namespace ESPEasy + +#endif // if FEATURE_WIFI diff --git a/src/ESPEasy/net/Helpers/NWAccessControl.cpp b/src/ESPEasy/net/Helpers/NWAccessControl.cpp new file mode 100644 index 000000000..ed0d8b9f0 --- /dev/null +++ b/src/ESPEasy/net/Helpers/NWAccessControl.cpp @@ -0,0 +1,73 @@ +#include "../Helpers/NWAccessControl.h" + +#include "../ESPEasyNetwork.h" +#include "../Globals/NWPlugins.h" + +#include "../../../src/Globals/SecuritySettings.h" +#include "../../../src/Helpers/StringConverter.h" + +namespace ESPEasy { +namespace net { + + +// ******************************************************************************** +// Allowed IP range check +// ******************************************************************************** + +bool ipInAllowedSubnet(const IPAddress& ip) +{ + String ip_str = ip.toString(); + + for (networkIndex_t x = 0; x < NETWORK_MAX; x++) { + EventStruct tempEvent; + tempEvent.NetworkIndex = x; + + if (NWPluginCall(NWPlugin::Function::NWPLUGIN_CLIENT_IP_WEB_ACCESS_ALLOWED, &tempEvent, ip_str)) { + return true; + } + } + return false; +} + +String describeAllowedIPrange() { + String reply; + + switch (SecuritySettings.IPblockLevel) + { + case ALL_ALLOWED: + reply += F("All Allowed"); + break; + default: + { + IPAddress low, high; + getIPallowedRange(low, high); + reply += formatIP(low); + reply += F(" - "); + reply += formatIP(high); + } + } + return reply; +} + +bool getIPallowedRange(IPAddress& low, IPAddress& high) +{ + switch (SecuritySettings.IPblockLevel) + { + case LOCAL_SUBNET_ALLOWED: + low = NetworkID(); + high = NetworkBroadcast(); + return true; + case ONLY_IP_RANGE_ALLOWED: + low = IPAddress(SecuritySettings.AllowedIPrangeLow); + high = IPAddress(SecuritySettings.AllowedIPrangeHigh); + break; + default: + low = IPAddress(0, 0, 0, 0); + high = IPAddress(255, 255, 255, 255); + return false; + } + return true; +} + +} // namespace net +} // namespace ESPEasy diff --git a/src/ESPEasy/net/Helpers/NWAccessControl.h b/src/ESPEasy/net/Helpers/NWAccessControl.h new file mode 100644 index 000000000..035ce428d --- /dev/null +++ b/src/ESPEasy/net/Helpers/NWAccessControl.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +namespace ESPEasy { +namespace net { + +// ******************************************************************************** +// Allowed IP range check +// ******************************************************************************** +#define ALL_ALLOWED 0 +#define LOCAL_SUBNET_ALLOWED 1 +#define ONLY_IP_RANGE_ALLOWED 2 + +bool ipInAllowedSubnet(const IPAddress& ip); + +String describeAllowedIPrange(); + +bool getIPallowedRange(IPAddress& low, IPAddress& high); + + +} // namespace net +} // namespace ESPEasy diff --git a/src/ESPEasy/net/Helpers/NWPlugin_import_export.cpp b/src/ESPEasy/net/Helpers/NWPlugin_import_export.cpp new file mode 100644 index 000000000..113bbbf0e --- /dev/null +++ b/src/ESPEasy/net/Helpers/NWPlugin_import_export.cpp @@ -0,0 +1,244 @@ +#include "../Helpers/NWPlugin_import_export.h" + +#if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + +# include "../../../src/Globals/Settings.h" +# include "../../../src/Helpers/_ESPEasy_key_value_store.h" +# include "../../../src/Helpers/ESPEasy_key_value_store_import_export.h" + +# include "../../../src/Helpers/StringConverter.h" + +# include "../DataTypes/NWPluginID.h" +# include "../Globals/NWPlugins.h" + + +# ifdef USES_NW001 +# include "../NWPluginStructs/NW001_data_struct_WiFi_STA.h" +# endif +# ifdef USES_NW002 +# include "../NWPluginStructs/NW002_data_struct_WiFi_AP.h" +# endif +# ifdef USES_NW003 +# include "../NWPluginStructs/NW003_data_struct_ETH_RMII.h" +# endif +# ifdef USES_NW004 +# include "../NWPluginStructs/NW004_data_struct_ETH_SPI.h" +# endif +# ifdef USES_NW005 +# include "../NWPluginStructs/NW005_data_struct_PPP_modem.h" +# endif + +namespace ESPEasy { +namespace net { + +ESPEasy_key_value_store_import_export::LabelStringFunction getLabelFnc(ESPEasy::net::nwpluginID_t nwpluginID) +{ + + switch (nwpluginID.value) + { +# ifdef USES_NW003 + case 3: + return ESPEasy::net::eth::NW003_data_struct_ETH_RMII::getLabelString; +# endif +# ifdef USES_NW004 + case 4: + return ESPEasy::net::eth::NW004_data_struct_ETH_SPI::getLabelString; +# endif + +# ifdef USES_NW005 + case 5: + { + return ESPEasy::net::ppp::NW005_data_struct_PPP_modem::getLabelString; + } +# endif // ifdef USES_NW005 + +# ifdef USES_NW001 + case 1: +# endif +# ifdef USES_NW002 + case 2: +# endif + default: + break; + + } + return nullptr; +} + +ESPEasy_key_value_store_import_export::NextKeyFunction getNextKeyFnc(ESPEasy::net::nwpluginID_t nwpluginID, bool includeCredentials) +{ + + switch (nwpluginID.value) + { +# ifdef USES_NW003 + case 3: + return ESPEasy::net::eth::NW003_data_struct_ETH_RMII::getNextKey; +# endif +# ifdef USES_NW004 + case 4: + return ESPEasy::net::eth::NW004_data_struct_ETH_SPI::getNextKey; +# endif +# ifdef USES_NW005 + case 5: + { + if (includeCredentials) { + return ESPEasy::net::ppp::NW005_data_struct_PPP_modem::getNextKey; + } + return ESPEasy::net::ppp::NW005_data_struct_PPP_modem::getNextKey_noCredentials; + } +# endif // ifdef USES_NW005 + +# ifdef USES_NW001 + case 1: +# endif +# ifdef USES_NW002 + case 2: +# endif + default: + break; + + } + return nullptr; +} + +String NWPlugin_import_export::exportConfig( + networkIndex_t networkIndex, + KeyValueWriter *writer, + bool includeCredentials) +{ + if (writer == nullptr) { return F("KVS : No writer"); } + + if (!validNetworkIndex(networkIndex)) { return F("KVS : Invalid Network Index"); } + + const ESPEasy::net::nwpluginID_t nwpluginID(Settings.NWPluginID[networkIndex]); + + if (!nwpluginID.isValid()) { return F("KVS : Invalid NW-Plugin ID"); } + + ESPEasy_key_value_store kvs; + + if (!kvs.load( + SettingsType::Enum::NetworkInterfaceSettings_Type, + networkIndex, + 0, + nwpluginID.value)) { return F("KVS : Failed loading"); } + auto labelFnc = getLabelFnc(nwpluginID); + auto nextKeyFnc = getNextKeyFnc(nwpluginID, includeCredentials); + + if ((labelFnc == nullptr) || (nextKeyFnc == nullptr)) { + return F("KVS : NWPlugin ID does not support import/export"); + } + + auto child = writer->createChild(); + + if (child) { + child->write({ F("nwpluginID"), nwpluginID.value }); + child->write({ F("enabled"), Settings.getNetworkEnabled(networkIndex) }); + child->write({ F("route_prio"), Settings.getRoutePrio_for_network(networkIndex) }); + child->write({ F("sn_block"), Settings.getNetworkInterfaceSubnetBlockClientIP(networkIndex) }); + child->write({ F("start_delay"), Settings.getNetworkInterfaceStartupDelayAtBoot(networkIndex) }); + +# if FEATURE_USE_IPV6 + child->write({ F("en_ipv6"), Settings.getNetworkEnabled_IPv6(networkIndex) }); +# endif + + ESPEasy_key_value_store_import_export e(&kvs); + + int32_t key = nextKeyFnc(-1); + + while (key >= 0) { + e.do_export(key, child.get(), labelFnc); + key = nextKeyFnc(key); + } + } + return EMPTY_STRING; +} + +String NWPlugin_import_export::importConfig( + networkIndex_t networkIndex, + const String & json) +{ + if (!validNetworkIndex(networkIndex)) { return F("KVS : Invalid Network Index"); } + + ESPEasy::net::nwpluginID_t nwpluginID(Settings.NWPluginID[networkIndex]); + + if (nwpluginID.isValid()) { return F("KVS : Network Index is already in use"); } + + addLog(LOG_LEVEL_INFO, concat(F("KVS : JSON: "), json)); + + ESPEasy_key_value_store kvs; + ESPEasy_key_value_store_import_export e(&kvs, json); + { + String value; + + if (!e.getParsedJSON(F("nwpluginID"), value)) { return F("KVS : No NWPlugin ID"); } + nwpluginID.value = value.toInt(); + } + + if (!nwpluginID.isValid()) { return F("KVS : No valid NWPlugin ID"); } + + auto labelFnc = getLabelFnc(nwpluginID); + auto nextKeyFnc = getNextKeyFnc(nwpluginID, true); + + if ((labelFnc == nullptr) || (nextKeyFnc == nullptr)) { + return F("KVS : NWPlugin ID does not support import/export"); + } + + String res = e.do_import(labelFnc, nextKeyFnc); + + if (res.isEmpty()) { + // Add entry, calls NWPLUGIN_LOAD_DEFAULTS + Settings.setNWPluginID_for_network(networkIndex, nwpluginID); + + if (!kvs.store( + SettingsType::Enum::NetworkInterfaceSettings_Type, + networkIndex, + 0, + nwpluginID.value)) + { + return F("KVS : Error saving, see log for more details"); + } + + const String non_kvs_keys[] = { + F("enabled"), + F("route_prio"), + F("sn_block"), + F("start_delay") +# if FEATURE_USE_IPV6 + , F("en_ipv6") +# endif + }; + + for (size_t i = 0; i < NR_ELEMENTS(non_kvs_keys); ++i) { + String value; + + if (e.getParsedJSON(non_kvs_keys[i], value)) + { + const bool bool_val = !(value.equalsIgnoreCase(F("false")) || value.equals(F("0"))); + + switch (i) + { + case 0: Settings.setNetworkEnabled(networkIndex, bool_val); + break; + case 1: Settings.setRoutePrio_for_network(networkIndex, value.toInt()); + break; + case 2: Settings.setNetworkInterfaceSubnetBlockClientIP(networkIndex, bool_val); + break; + case 3: Settings.setNetworkInterfaceStartupDelayAtBoot(networkIndex, value.toInt()); + break; +# if FEATURE_USE_IPV6 + case 4: Settings.setNetworkEnabled_IPv6(networkIndex, bool_val); + break; +# endif // if FEATURE_USE_IPV6 + } + } + } + } + + return res; +} + +} // namespace net + +} // namespace ESPEasy + +#endif // if FEATURE_ESPEASY_KEY_VALUE_STORE diff --git a/src/ESPEasy/net/Helpers/NWPlugin_import_export.h b/src/ESPEasy/net/Helpers/NWPlugin_import_export.h new file mode 100644 index 000000000..c619a6ade --- /dev/null +++ b/src/ESPEasy/net/Helpers/NWPlugin_import_export.h @@ -0,0 +1,31 @@ +#pragma once + +#include "../../../ESPEasy_common.h" +#if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + +# include "../DataTypes/NetworkIndex.h" +# include "../../../src/Helpers/KeyValueWriter.h" + +namespace ESPEasy { +namespace net { + +class NWPlugin_import_export +{ +public: + + static String exportConfig( + networkIndex_t networkIndex, + KeyValueWriter *writer, + bool includeCredentials = false); + + static String importConfig( + networkIndex_t networkIndex, + const String & json); + + +}; // class NWPlugin_import_export + +} // namespace net +} // namespace ESPEasy + +#endif // if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS diff --git a/src/ESPEasy/net/Helpers/NW_info_writer.cpp b/src/ESPEasy/net/Helpers/NW_info_writer.cpp new file mode 100644 index 000000000..9829c453b --- /dev/null +++ b/src/ESPEasy/net/Helpers/NW_info_writer.cpp @@ -0,0 +1,236 @@ +#include "../Helpers/NW_info_writer.h" + +#include "../../../src/DataStructs/ESPEasy_EventStruct.h" +#include "../../../src/DataTypes/ESPEasy_plugin_functions.h" +#include "../../../src/Globals/Settings.h" +#include "../../../src/Helpers/ESPEasy_UnitOfMeasure.h" +#include "../../../src/Helpers/StringConverter.h" + +#include "../Globals/NWPlugins.h" + +#include "../Helpers/NW_info_writer.h" + +namespace ESPEasy { +namespace net { + +#ifdef ESP32 + +bool write_NetworkAdapterFlags(ESPEasy::net::networkIndex_t networkindex, KeyValueWriter*writer) +{ + if (writer == nullptr) { return false; } + struct EventStruct TempEvent; + TempEvent.kvWriter = writer; + + TempEvent.NetworkIndex = networkindex; + + String str; + + if (!NWPluginCall(NWPlugin::Function::NWPLUGIN_GET_INTERFACE, &TempEvent, str)) { + return false; + } + + { + String str; + NWPluginCall(NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_HW_ADDRESS, &TempEvent, str); + } + + { + KeyValueStruct kv(F("Flags"), KeyValueStruct::Format::PreFormatted); + + const NWPlugin::NetforkFlags flags[] = { + NWPlugin::NetforkFlags::DHCP_client, + NWPlugin::NetforkFlags::DHCP_server, + NWPlugin::NetforkFlags::AutoUp, + NWPlugin::NetforkFlags::GratuituousArp, + NWPlugin::NetforkFlags::EventIPmodified, + NWPlugin::NetforkFlags::isPPP, + NWPlugin::NetforkFlags::isBridge, +# if CONFIG_LWIP_IPV6 + NWPlugin::NetforkFlags::MLD_v6_report, + NWPlugin::NetforkFlags::IPv6_autoconf_enabled, +# endif // if CONFIG_LWIP_IPV6 + }; + + for (size_t i = 0; i < NR_ELEMENTS(flags); ++i) { + if (NWPlugin::isFlagSet(flags[i], TempEvent.networkInterface)) { + String labels_str = NWPlugin::toString(flags[i]); + + if ((flags[i] == NWPlugin::NetforkFlags::DHCP_client) && + (TempEvent.networkInterface->getStatusBits() & ESP_NETIF_HAS_STATIC_IP_BIT)) { + kv.appendValue(concat(NWPlugin::toString(flags[i]), F(" (static IP)"))); + } else { + kv.appendValue(NWPlugin::toString(flags[i])); + } + } + } + writer->write(kv); + } + return true; +} +#ifndef LIMIT_BUILD_SIZE +bool write_NetworkAdapterPort(ESPEasy::net::networkIndex_t networkindex, + KeyValueWriter *writer) +{ + if (writer == nullptr) { return false; } + struct EventStruct TempEvent; + TempEvent.kvWriter = writer; + + TempEvent.NetworkIndex = networkindex; + + String str; + NWPluginCall(NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_PORT, &TempEvent, str); + return true; +} +#endif + +bool write_IP_config(ESPEasy::net::networkIndex_t networkindex, KeyValueWriter*writer) +{ + if (writer == nullptr) { return false; } + struct EventStruct TempEvent; + + TempEvent.NetworkIndex = networkindex; + String str; + + if (!NWPluginCall(NWPlugin::Function::NWPLUGIN_GET_INTERFACE, &TempEvent, str)) { + return false; + } + const NWPlugin::IP_type ip_types[] = { + NWPlugin::IP_type::inet, + NWPlugin::IP_type::network_id_cdr, + NWPlugin::IP_type::netmask, + NWPlugin::IP_type::broadcast, + NWPlugin::IP_type::gateway, + NWPlugin::IP_type::dns1, + NWPlugin::IP_type::dns2, +# if CONFIG_LWIP_IPV6 + NWPlugin::IP_type::ipv6_unknown, + NWPlugin::IP_type::ipv6_global, + NWPlugin::IP_type::ipv6_link_local, + NWPlugin::IP_type::ipv6_site_local, + NWPlugin::IP_type::ipv6_unique_local, + NWPlugin::IP_type::ipv4_mapped_ipv6, +# endif // if CONFIG_LWIP_IPV6 + + }; + + for (size_t i = 0; i < NR_ELEMENTS(ip_types); ++i) { + + PrintToString str; + + if (NWPlugin::print_IP_address(ip_types[i], TempEvent.networkInterface, str)) { + KeyValueStruct kv(NWPlugin::toString(ip_types[i]), str.get(), KeyValueStruct::Format::PreFormatted); + writer->write(kv); + } + } + return true; +} + +#endif // ifdef ESP32 + +bool write_NetworkConnectionInfo(ESPEasy::net::networkIndex_t networkindex, KeyValueWriter*writer) +{ + if (!writer) { return false; } + struct EventStruct TempEvent; + TempEvent.NetworkIndex = networkindex; + TempEvent.kvWriter = writer; + + const bool res = NWPluginCall(NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_CONNECTED, &TempEvent); + + if (res) { + NWPluginCall(NWPlugin::Function::NWPLUGIN_GET_CONNECTED_DURATION, &TempEvent); + +#if FEATURE_NETWORK_TRAFFIC_COUNT + NWPluginCall(NWPlugin::Function::NWPLUGIN_GET_TRAFFIC_COUNT, &TempEvent); +#endif // if FEATURE_NETWORK_TRAFFIC_COUNT + } + return res; +} + +#if defined(USES_NW003) || defined(USES_NW004) + +bool write_Eth_Show_Connected(const ETHClass& eth, KeyValueWriter *writer) +{ + if ((writer == nullptr) || !eth.connected() || !eth.linkUp()) { return false; } + + if (writer->summaryValueOnly()) { + String s = concat( + String(eth.linkSpeed()), + eth.fullDuplex() ? F("Mbps FD") : F("Mbps HD")); + + if (!eth.autoNegotiation()) { s += F("(manual)"); } + writer->write({ EMPTY_STRING, s }); + } else { + KeyValueStruct kv(F("Link Speed"), eth.linkSpeed()); +# if FEATURE_TASKVALUE_UNIT_OF_MEASURE + kv.setUnit(UOM_Mbps); +# endif + writer->write(kv); + writer->write({ F("Duplex Mode"), eth.fullDuplex() ? F("Full Duplex") : F("Half Duplex") }); + writer->write({ F("Negotiation Mode"), eth.autoNegotiation() ? F("Auto") : F("Manual") }); + } + + return true; +} + +bool write_Eth_HW_Address( + const ESPEasy::net::EthPhyType_t phyType, + const ETHClass* eth, KeyValueWriter *writer) +{ + if (writer == nullptr) { return false; } + + if (writer->summaryValueOnly()) { + KeyValueStruct kv(EMPTY_STRING); + kv.appendValue(toString(phyType)); + kv.appendValue(concat(F("MAC: "), eth->macAddress())); + + writer->write(kv); + } else { + writer->write({ + F("Adapter"), + toString(phyType) }); + writer->write({ + F("MAC"), + eth->macAddress(), + KeyValueStruct::Format::PreFormatted }); + } + + return true; +} + +#endif // if defined(USES_NW003) || defined(USES_NW004) + +bool write_NetworkPort(const __FlashStringHelper*labels[], const int pins[], size_t nrElements, KeyValueWriter *writer) +{ + if (writer == nullptr) { return false; } + bool success = false; + + if (writer->summaryValueOnly()) { + KeyValueStruct kv(EMPTY_STRING); + + for (size_t i = 0; i < nrElements; ++i) { + if (pins[i] >= 0) { + kv.appendValue(concat(labels[i], F(": ")) + formatGpioLabel(pins[i], false)); + } + } + + if (kv._values.size()) { + success = true; + writer->write(kv); + } + } else { + for (size_t i = 0; i < nrElements; ++i) { + if (pins[i] >= 0) { + success = true; + writer->write({ + labels[i], + pins[i], + KeyValueStruct::Format::PreFormatted }); + } + } + } + + return success; +} + +} // namespace net +} // namespace ESPEasy diff --git a/src/ESPEasy/net/Helpers/NW_info_writer.h b/src/ESPEasy/net/Helpers/NW_info_writer.h new file mode 100644 index 000000000..2ae6f5de1 --- /dev/null +++ b/src/ESPEasy/net/Helpers/NW_info_writer.h @@ -0,0 +1,46 @@ +#pragma once + +#include "../../../ESPEasy_common.h" + +#include "../../../src/Helpers/KeyValueWriter.h" + +#include "../DataTypes/NetworkIndex.h" + +#if defined(USES_NW003) || defined(USES_NW004) +#include "../DataTypes/EthernetParameters.h" +#include +#endif + +namespace ESPEasy { +namespace net { + +#ifdef ESP32 +bool write_NetworkAdapterFlags(ESPEasy::net::networkIndex_t networkindex, + KeyValueWriter *writer); +#ifndef LIMIT_BUILD_SIZE +bool write_NetworkAdapterPort(ESPEasy::net::networkIndex_t networkindex, + KeyValueWriter *writer); +#endif + +bool write_IP_config(ESPEasy::net::networkIndex_t networkindex, + KeyValueWriter *writer); +#endif // ifdef ESP32 + +bool write_NetworkConnectionInfo(ESPEasy::net::networkIndex_t networkindex, + KeyValueWriter *writer); + +#if defined(USES_NW003) || defined(USES_NW004) + +bool write_Eth_Show_Connected(const ETHClass& eth, + KeyValueWriter *writer); + +bool write_Eth_HW_Address(const ESPEasy::net::EthPhyType_t phyType, + const ETHClass* eth, + KeyValueWriter *writer); + +#endif // if defined(USES_NW003) || defined(USES_NW004) + +bool write_NetworkPort(const __FlashStringHelper*labels[], const int pins[], size_t nrElements, KeyValueWriter *writer); + +} // namespace net +} // namespace ESPEasy diff --git a/src/src/Helpers/WiFi_AP_CandidatesList.cpp b/src/ESPEasy/net/Helpers/WiFi_AP_CandidatesList.cpp similarity index 64% rename from src/src/Helpers/WiFi_AP_CandidatesList.cpp rename to src/ESPEasy/net/Helpers/WiFi_AP_CandidatesList.cpp index d30d4039b..6da973d5f 100644 --- a/src/src/Helpers/WiFi_AP_CandidatesList.cpp +++ b/src/ESPEasy/net/Helpers/WiFi_AP_CandidatesList.cpp @@ -1,16 +1,17 @@ #include "../Helpers/WiFi_AP_CandidatesList.h" #ifdef ESP32 -#include "../DataStructs/WiFi_AP_Candidates_NVS.h" +# include "../DataStructs/WiFi_AP_Candidates_NVS.h" #endif -#include "../ESPEasyCore/ESPEasy_Log.h" #include "../Globals/ESPEasyWiFiEvent.h" -#include "../Globals/RTC.h" -#include "../Globals/SecuritySettings.h" -#include "../Globals/Settings.h" -#include "../Helpers/Misc.h" -#include "../Helpers/StringConverter.h" + +#include "../../../src/ESPEasyCore/ESPEasy_Log.h" +#include "../../../src/Globals/RTC.h" +#include "../../../src/Globals/SecuritySettings.h" +#include "../../../src/Globals/Settings.h" +#include "../../../src/Helpers/Misc.h" +#include "../../../src/Helpers/StringConverter.h" #if defined(ESP8266) # include @@ -19,17 +20,35 @@ # include #endif // if defined(ESP32) -#define WIFI_CUSTOM_DEPLOYMENT_KEY_INDEX 3 -#define WIFI_CUSTOM_SUPPORT_KEY_INDEX 4 -#define WIFI_CREDENTIALS_FALLBACK_SSID_INDEX 5 + +#if FEATURE_WIFI + +namespace ESPEasy { +namespace net { +namespace wifi { + +# define WIFI_CUSTOM_DEPLOYMENT_KEY_INDEX 3 +# define WIFI_CUSTOM_SUPPORT_KEY_INDEX 4 +# define WIFI_CREDENTIALS_FALLBACK_SSID_INDEX 5 +# if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE +# define WIFI_CREDENTIALs_SEPARATE_FILE_FIRST_INDEX 6 +# define WIFI_CREDENTIALS_MAX_INDEX (WIFI_CREDENTIALs_SEPARATE_FILE_FIRST_INDEX + MAX_EXTRA_WIFI_CREDENTIALS_SEPARATE_FILE) +# else // if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE +# define WIFI_CREDENTIALS_MAX_INDEX 6 +# endif // if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + +static LongTermOnOffTimer _last_scan; + WiFi_AP_CandidatesList::WiFi_AP_CandidatesList() { + _last_scan.clear(); known.clear(); candidates.clear(); known_it = known.begin(); } WiFi_AP_CandidatesList::~WiFi_AP_CandidatesList() { + _last_scan.clear(); candidates.clear(); known.clear(); scanned.clear(); @@ -41,39 +60,35 @@ void WiFi_AP_CandidatesList::load_knownCredentials() { _mustLoadCredentials = false; known.clear(); candidates.clear(); -// attemptsLeft = 1; + + // attemptsLeft = WiFi_CONNECT_ATTEMPTS; _addedKnownCandidate = false; -// addFromRTC(); + + // addFromRTC(); { // Add the known SSIDs - String ssid; - uint8_t index = 1; // Index 0 is the "unset" value + uint8_t index = 1; // Index 0 is the "unset" value - bool done = false; + for (; index < WIFI_CREDENTIALS_MAX_INDEX; ++index) { + String ssid; - while (!done) { if (get_SSID(index, ssid)) { // Make sure emplace_back is not done on the 2nd heap # ifdef USE_SECOND_HEAP HeapSelectDram ephemeral; # endif // ifdef USE_SECOND_HEAP - known.emplace_back(index, ssid); + WiFi_AP_Candidate tmp_known(index, ssid); if (SettingsIndexMatchCustomCredentials(index)) { if (SettingsIndexMatchEmergencyFallback(index)) { - known.back().bits.isEmergencyFallback = true; + tmp_known.bits.isEmergencyFallback = true; } else { - known.back().bits.lowPriority = true; + tmp_known.bits.lowPriority = true; } } + known.push_back(tmp_known); ++index; - } else { - if (SettingsIndexMatchCustomCredentials(index)) { - ++index; - } else { - done = true; - } } } } @@ -87,21 +102,22 @@ void WiFi_AP_CandidatesList::clearCache() { known_it = known.begin(); } - void WiFi_AP_CandidatesList::force_reload() { clearCache(); RTC.clearLastWiFi(); // Invalidate the RTC WiFi data. candidates.clear(); - loadCandidatesFromScanned(); + load_knownCredentials(); } -void WiFi_AP_CandidatesList::begin_sync_scan() { +void WiFi_AP_CandidatesList::begin_scan(uint8_t channel) { + _last_scan.setOn(); + _last_scan_channel = channel; candidates.clear(); _addedKnownCandidate = false; } void WiFi_AP_CandidatesList::purge_expired() { - for (auto it = scanned.begin(); it != scanned.end(); ) { + for (auto it = scanned.begin(); it != scanned.end();) { if (it->expired()) { it = scanned.erase(it); } else { @@ -110,47 +126,39 @@ void WiFi_AP_CandidatesList::purge_expired() { } } -#if !FEATURE_ESP8266_DIRECT_WIFI_SCAN -void WiFi_AP_CandidatesList::process_WiFiscan(uint8_t scancount) { +void WiFi_AP_CandidatesList::process_WiFiscan() { + // if (_last_scan.isOn()) { // Append or update found APs from scan. - for (uint8_t i = 0; i < scancount; ++i) { - const WiFi_AP_Candidate tmp(i); + int scancount = WiFi.scanComplete(); - scanned_new.push_back(tmp); + if (scancount > 0) { + for (int i = 0; i < scancount; ++i) { + const WiFi_AP_Candidate tmp(i); + + scanned_new.push_back(tmp); + } + + after_process_WiFiscan(); } - after_process_WiFiscan(); + // } } -#endif - -#ifdef ESP8266 -#if FEATURE_ESP8266_DIRECT_WIFI_SCAN -void WiFi_AP_CandidatesList::process_WiFiscan(const bss_info& ap) { - WiFi_AP_Candidate tmp(ap); - scanned_new.push_back(tmp); -} -#endif -#endif void WiFi_AP_CandidatesList::after_process_WiFiscan() { + _last_scan.setOff(); scanned_new.sort(); scanned_new.unique(); _mustLoadCredentials = true; load_knownCredentials(); WiFi.scanDelete(); - attemptsLeft = 1; + attemptsLeft = WiFi_CONNECT_ATTEMPTS; } bool WiFi_AP_CandidatesList::getNext(bool scanAllowed) { load_knownCredentials(); - if (candidates.empty()) { - if (scanAllowed) { - return false; - } - loadCandidatesFromScanned(); - attemptsLeft = 1; - if (candidates.empty()) { return false; } + if (candidates.empty()) { + return false; } currentCandidate = candidates.front(); @@ -172,9 +180,9 @@ bool WiFi_AP_CandidatesList::getNext(bool scanAllowed) { if (mustPop) { if (attemptsLeft == 0) { - if (currentCandidate.bits.isHidden) { + if (currentCandidate.bits.isHidden && !Settings.HiddenSSID_SlowConnectPerBSSID()) { // We tried to connect to hidden SSIDs in 1 run, so pop all hidden candidates. - for (auto cand_it = candidates.begin(); cand_it != candidates.end() && cand_it->bits.isHidden; ) { + for (auto cand_it = candidates.begin(); cand_it != candidates.end() && cand_it->bits.isHidden;) { cand_it = candidates.erase(cand_it); } } else { @@ -183,8 +191,8 @@ bool WiFi_AP_CandidatesList::getNext(bool scanAllowed) { } } - known_it = known.begin(); - attemptsLeft = 1; + known_it = known.begin(); + attemptsLeft = WiFi_CONNECT_ATTEMPTS; } else { markAttempt(); } @@ -196,13 +204,12 @@ const WiFi_AP_Candidate& WiFi_AP_CandidatesList::getCurrent() const { return currentCandidate; } -void WiFi_AP_CandidatesList::markAttempt() { - if (attemptsLeft > 0) attemptsLeft--; +void WiFi_AP_CandidatesList::markAttempt() { if (attemptsLeft > 0) { attemptsLeft--; } } WiFi_AP_Candidate WiFi_AP_CandidatesList::getBestCandidate() const { for (auto it = candidates.begin(); it != candidates.end(); ++it) { - if (it->rssi < -1) { return *it; } + if (it->rssi < -1 && it->usable()) { return *it; } } return WiFi_AP_Candidate(); } @@ -213,33 +220,40 @@ bool WiFi_AP_CandidatesList::hasCandidateCredentials() { } bool WiFi_AP_CandidatesList::hasCandidates() const { - return !candidates.empty(); +// return !candidates.empty(); + return getBestCandidate().usable(); } void WiFi_AP_CandidatesList::markCurrentConnectionStable() { clearCache(); + if (currentCandidate.enc_type == 0) { bool matchfound = false; + for (auto it = candidates.begin(); !matchfound && it != candidates.end(); ++it) { if (currentCandidate == *it) { // We may have gotten the enc_type of the active used candidate // Make sure to store the enc type before clearing the candidates list currentCandidate.enc_type = it->enc_type; - matchfound = true; + matchfound = true; } } } + if (currentCandidate.usable()) { // Store in RTC RTC.lastWiFiChannel = currentCandidate.channel; currentCandidate.bssid.get(RTC.lastBSSID); RTC.lastWiFiSettingsIndex = currentCandidate.index; -#ifdef ESP32 - if (Settings.UseLastWiFiFromRTC()) +# ifdef ESP32 + + if (Settings.UseLastWiFiFromRTC()) { WiFi_AP_Candidates_NVS::currentConnection_to_NVS(currentCandidate); - else + } + else { WiFi_AP_Candidates_NVS::clear_from_NVS(); -#endif + } +# endif // ifdef ESP32 } candidates.clear(); @@ -248,23 +262,30 @@ void WiFi_AP_CandidatesList::markCurrentConnectionStable() { } int8_t WiFi_AP_CandidatesList::scanComplete() const { + // if (!_last_scan.isOn() || + // (_last_scan.getLastOnDuration_ms() > WIFI_AP_CANDIDATE_MAX_AGE)) return -3; + const int8_t scanCompleteStatus = WiFi.scanComplete(); + if (scanCompleteStatus == -1) { // Still scanning return scanCompleteStatus; } size_t found = 0; + for (auto scan = scanned.begin(); scan != scanned.end(); ++scan) { if (!scan->expired()) { ++found; } } + for (auto scan = scanned_new.begin(); scan != scanned_new.end(); ++scan) { if (!scan->expired()) { ++found; } } + if (found == 0) { if (scanCompleteStatus == -2) { // Not triggered @@ -276,16 +297,22 @@ int8_t WiFi_AP_CandidatesList::scanComplete() const { bool WiFi_AP_CandidatesList::SettingsIndexMatchCustomCredentials(uint8_t index) { - return (WIFI_CUSTOM_DEPLOYMENT_KEY_INDEX == index || - WIFI_CUSTOM_SUPPORT_KEY_INDEX == index || - SettingsIndexMatchEmergencyFallback(index)); + return WIFI_CUSTOM_DEPLOYMENT_KEY_INDEX == index || + WIFI_CUSTOM_SUPPORT_KEY_INDEX == index || + SettingsIndexMatchEmergencyFallback(index); } -bool WiFi_AP_CandidatesList::SettingsIndexMatchEmergencyFallback(uint8_t index) +bool WiFi_AP_CandidatesList::SettingsIndexMatchEmergencyFallback(uint8_t index) { return WIFI_CREDENTIALS_FALLBACK_SSID_INDEX == index; } + +# if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + +bool WiFi_AP_CandidatesList::SettingsIndexMatchCredentialsSeparateFile(uint8_t index) { - return (WIFI_CREDENTIALS_FALLBACK_SSID_INDEX == index); + return index >= WIFI_CREDENTIALs_SEPARATE_FILE_FIRST_INDEX && + index < WIFI_CREDENTIALS_MAX_INDEX; } +# endif // if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE void WiFi_AP_CandidatesList::loadCandidatesFromScanned() { // Make sure list operations are not done on the 2nd heap @@ -296,12 +323,14 @@ void WiFi_AP_CandidatesList::loadCandidatesFromScanned() { if (scanned_new.size() > 0) { // We have new scans to process. purge_expired(); + for (auto scan = scanned_new.begin(); scan != scanned_new.end();) { - #ifndef BUILD_NO_DEBUG + # ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { addLogMove(LOG_LEVEL_DEBUG, concat(F("WiFi : Scan result: "), scan->toString())); } - #endif // ifndef BUILD_NO_DEBUG + # endif // ifndef BUILD_NO_DEBUG // Check to see if it is already present, if so, remove existing one. for (auto tmp = scanned.begin(); tmp != scanned.end();) { @@ -324,6 +353,7 @@ void WiFi_AP_CandidatesList::loadCandidatesFromScanned() { // Do not mess with the current candidates order if > 1 present return; } + // Purge unusable from known list. for (auto it = known.begin(); it != known.end();) { if (it->usable()) { @@ -342,16 +372,16 @@ void WiFi_AP_CandidatesList::loadCandidatesFromScanned() { } else { if (scan->bits.isHidden) { if (Settings.IncludeHiddenSSID()) { - if (SecuritySettings.hasWiFiCredentials()) { + if (WiFi_AP_CandidatesList::hasWiFiCredentials()) { candidates.push_back(*scan); } } } else if (scan->ssid.length() > 0) { for (auto kn_it = known.begin(); kn_it != known.end(); ++kn_it) { if (scan->ssid.equals(kn_it->ssid)) { - WiFi_AP_Candidate tmp = *scan; - tmp.index = kn_it->index; - tmp.bits.lowPriority = kn_it->bits.lowPriority; + WiFi_AP_Candidate tmp(*scan); + tmp.index = kn_it->index; + tmp.bits.lowPriority = kn_it->bits.lowPriority; tmp.bits.isEmergencyFallback = kn_it->bits.isEmergencyFallback; if (tmp.usable()) { @@ -367,13 +397,15 @@ void WiFi_AP_CandidatesList::loadCandidatesFromScanned() { } } # ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_INFO)) { const WiFi_AP_Candidate bestCandidate = getBestCandidate(); + if (bestCandidate.usable()) { addLogMove(LOG_LEVEL_INFO, concat(F("WiFi : Best AP candidate: "), bestCandidate.toString())); } } - #endif + # endif // ifndef BUILD_NO_DEBUG candidates.sort(); candidates.unique(); addFromRTC(); @@ -381,11 +413,14 @@ void WiFi_AP_CandidatesList::loadCandidatesFromScanned() { } void WiFi_AP_CandidatesList::addFromRTC() { - if (!Settings.UseLastWiFiFromRTC()) return; - if (!RTC.lastWiFi_set()) { - #ifdef ESP32 + if (!Settings.UseLastWiFiFromRTC()) { return; } + + if (!RTC.lastWiFi_set()) { + # ifdef ESP32 + // Try to load from NVS and store in RTC WiFi_AP_Candidate fromNVS; + if (WiFi_AP_Candidates_NVS::loadCandidate_from_NVS(fromNVS)) { RTC.lastWiFiChannel = currentCandidate.channel; currentCandidate.bssid.get(RTC.lastBSSID); @@ -393,13 +428,13 @@ void WiFi_AP_CandidatesList::addFromRTC() { } else { return; } - #else + # else // ifdef ESP32 return; - #endif + # endif // ifdef ESP32 } - if (SettingsIndexMatchCustomCredentials(RTC.lastWiFiSettingsIndex)) - { + if (SettingsIndexMatchCustomCredentials(RTC.lastWiFiSettingsIndex)) + { return; } @@ -417,17 +452,22 @@ void WiFi_AP_CandidatesList::addFromRTC() { return; } - if (candidates.size() > 0 && candidates.front().ssid.equals(fromRTC.ssid)) { + if ((candidates.size() > 0) && candidates.front().ssid.equals(fromRTC.ssid)) { // Front candidate was already from RTC. + // Copy all flags, before removing it from list + fromRTC = candidates.front(); + fromRTC.index = RTC.lastWiFiSettingsIndex; candidates.pop_front(); } // See if we may have a better candidate for the current network, with a significant better RSSI. auto bestMatch = candidates.end(); auto lastUsed = bestMatch; + for (auto it = candidates.begin(); lastUsed == candidates.end() && it != candidates.end(); ++it) { if (it->usable() && it->ssid.equals(fromRTC.ssid)) { const bool foundLastUsed = fromRTC.bssid_match(it->bssid); + if (foundLastUsed) { lastUsed = it; } else if (bestMatch == candidates.end()) { @@ -436,9 +476,10 @@ void WiFi_AP_CandidatesList::addFromRTC() { } } bool matchAdded = false; + if (bestMatch != candidates.end()) { // Found a best match, possibly better than the last used. - if (lastUsed == candidates.end() || (bestMatch->rssi > (lastUsed->rssi + 10))) { + if ((lastUsed == candidates.end()) || (bestMatch->rssi > (lastUsed->rssi + 10))) { // Last used was not found or // Other candidate has significant better RSSI matchAdded = true; @@ -448,12 +489,14 @@ void WiFi_AP_CandidatesList::addFromRTC() { matchAdded = true; candidates.push_front(*lastUsed); } + if (!matchAdded) { candidates.push_front(fromRTC); + // This is not taken from a scan, so no idea of the used encryption. // Try to find a matching BSSID to get the encryption. for (auto it = candidates.begin(); it != candidates.end(); ++it) { - if ((it->rssi != -1) && candidates.front() == *it) { + if ((it->rssi != -1) && (candidates.front() == *it)) { candidates.front().enc_type = it->enc_type; return; } @@ -480,6 +523,7 @@ void WiFi_AP_CandidatesList::purge_unusable() { it = candidates.erase(it); } } + if (candidates.size() > 1) { candidates.sort(); candidates.unique(); @@ -487,71 +531,102 @@ void WiFi_AP_CandidatesList::purge_unusable() { } bool WiFi_AP_CandidatesList::get_SSID_key(uint8_t index, String& ssid, String& key) { - switch (index) { + ssid.clear(); + key.clear(); + + switch (index) + { case 1: - ssid = SecuritySettings.WifiSSID; + ssid = SecuritySettings.getSSID(SecurityStruct::WiFiCredentialsSlot::first); key = SecuritySettings.WifiKey; break; case 2: - ssid = SecuritySettings.WifiSSID2; + ssid = SecuritySettings.getSSID(SecurityStruct::WiFiCredentialsSlot::second); key = SecuritySettings.WifiKey2; break; case WIFI_CUSTOM_DEPLOYMENT_KEY_INDEX: - #if !defined(CUSTOM_DEPLOYMENT_SSID) || !defined(CUSTOM_DEPLOYMENT_KEY) + # if !defined(CUSTOM_DEPLOYMENT_SSID) || !defined(CUSTOM_DEPLOYMENT_KEY) return false; - #else + # else ssid = F(CUSTOM_DEPLOYMENT_SSID); key = F(CUSTOM_DEPLOYMENT_KEY); - #endif + # endif // if !defined(CUSTOM_DEPLOYMENT_SSID) || !defined(CUSTOM_DEPLOYMENT_KEY) break; case WIFI_CUSTOM_SUPPORT_KEY_INDEX: - #if !defined(CUSTOM_SUPPORT_SSID) || !defined(CUSTOM_SUPPORT_KEY) + # if !defined(CUSTOM_SUPPORT_SSID) || !defined(CUSTOM_SUPPORT_KEY) return false; - #else + # else ssid = F(CUSTOM_SUPPORT_SSID); key = F(CUSTOM_SUPPORT_KEY); - #endif + # endif // if !defined(CUSTOM_SUPPORT_SSID) || !defined(CUSTOM_SUPPORT_KEY) break; case WIFI_CREDENTIALS_FALLBACK_SSID_INDEX: { - #if !defined(CUSTOM_EMERGENCY_FALLBACK_SSID) || !defined(CUSTOM_EMERGENCY_FALLBACK_KEY) + # if !defined(CUSTOM_EMERGENCY_FALLBACK_SSID) || !defined(CUSTOM_EMERGENCY_FALLBACK_KEY) return false; - #else + # else int allowedUptimeMinutes = 10; - #ifdef CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME + # ifdef CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME allowedUptimeMinutes = CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME; - #endif - if (getUptimeMinutes() < allowedUptimeMinutes && SecuritySettings.hasWiFiCredentials()) { + # endif + + if ((getUptimeMinutes() < allowedUptimeMinutes) && SecuritySettings.hasWiFiCredentials()) { ssid = F(CUSTOM_EMERGENCY_FALLBACK_SSID); key = F(CUSTOM_EMERGENCY_FALLBACK_KEY); } else { return false; } - #endif + # endif // if !defined(CUSTOM_EMERGENCY_FALLBACK_SSID) || !defined(CUSTOM_EMERGENCY_FALLBACK_KEY) break; } - default: - return false; + default: break; } - // TODO TD-er: Read other credentials from extra file. - +# if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + if (SettingsIndexMatchCredentialsSeparateFile(index)) + { + return SecuritySettings_deviceSpecific.getWiFiCredentials( + index - WIFI_CREDENTIALs_SEPARATE_FILE_FIRST_INDEX, + ssid, + key); + } +# endif // if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE // Spaces are allowed in both SSID and pass phrase, so make sure to not trim the ssid and key. - return true; + return !ssid.isEmpty() && key.length() >= 8; +} + +bool WiFi_AP_CandidatesList::hasWiFiCredentials() +{ + uint8_t index = 1; // Index 0 is the "unset" value + + for (; index < WIFI_CREDENTIALS_MAX_INDEX; ++index) { + String ssid; + if (get_SSID(index, ssid)) return true; + } + return false; } bool WiFi_AP_CandidatesList::get_SSID(uint8_t index, String& ssid) { String key; + return get_SSID_key(index, ssid, key); } String WiFi_AP_CandidatesList::get_key(uint8_t index) { String ssid, key; - if (get_SSID_key(index, ssid, key)) + + if (get_SSID_key(index, ssid, key)) { return key; + } return EMPTY_STRING; -} \ No newline at end of file +} + +} // namespace wifi +} // namespace net +} // namespace ESPEasy + +#endif // if FEATURE_WIFI diff --git a/src/src/Helpers/WiFi_AP_CandidatesList.h b/src/ESPEasy/net/Helpers/WiFi_AP_CandidatesList.h similarity index 54% rename from src/src/Helpers/WiFi_AP_CandidatesList.h rename to src/ESPEasy/net/Helpers/WiFi_AP_CandidatesList.h index 2c76e04e4..c2bb3e38e 100644 --- a/src/src/Helpers/WiFi_AP_CandidatesList.h +++ b/src/ESPEasy/net/Helpers/WiFi_AP_CandidatesList.h @@ -1,12 +1,21 @@ -#ifndef HELPERS_WIFI_AP_CANDIDATESLIST_H -#define HELPERS_WIFI_AP_CANDIDATESLIST_H +#pragma once -#include "../../ESPEasy_common.h" +#include "../../../ESPEasy_common.h" #include "../DataStructs/WiFi_AP_Candidate.h" +#include "../../../src/Helpers/LongTermOnOffTimer.h" #include +#define WiFi_CONNECT_ATTEMPTS 1 + +#if FEATURE_WIFI + +namespace ESPEasy { +namespace net { +namespace wifi { + + typedef std::list::const_iterator WiFi_AP_Candidate_const_iterator; struct WiFi_AP_CandidatesList { @@ -22,20 +31,12 @@ struct WiFi_AP_CandidatesList { // Called after WiFi credentials have changed. void force_reload(); - void begin_sync_scan(); + void begin_scan(uint8_t channel = 0); void purge_expired(); -#if !FEATURE_ESP8266_DIRECT_WIFI_SCAN // Add found WiFi access points to the list if they are possible candidates. - void process_WiFiscan(uint8_t scancount); -#endif - -#ifdef ESP8266 -#if FEATURE_ESP8266_DIRECT_WIFI_SCAN - void process_WiFiscan(const bss_info& ap); -#endif -#endif + void process_WiFiscan(); void after_process_WiFiscan(); @@ -44,7 +45,7 @@ struct WiFi_AP_CandidatesList { bool getNext(bool scanAllowed); const WiFi_AP_Candidate& getCurrent() const; - + // Decrease attemptsLeft void markAttempt(); @@ -56,11 +57,15 @@ struct WiFi_AP_CandidatesList { // Make sure the current connection (from RTC) is set as first next candidate. // This will force a reconnect to the current AP if connection is lost. - void markCurrentConnectionStable(); + void markCurrentConnectionStable(); - bool addedKnownCandidate() const { return _addedKnownCandidate; } + bool addedKnownCandidate() const { return _addedKnownCandidate; } - int8_t scanComplete() const; + // Return number of found access points + // @retval -1: WIFI_SCAN_RUNNING + // @retval -2: WIFI_SCAN_FAILED + // @retval -3: No scan performed (recently) + int8_t scanComplete() const; WiFi_AP_Candidate_const_iterator scanned_begin() const { return scanned.begin(); @@ -73,35 +78,41 @@ struct WiFi_AP_CandidatesList { static bool SettingsIndexMatchCustomCredentials(uint8_t index); static bool SettingsIndexMatchEmergencyFallback(uint8_t index); +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + static bool SettingsIndexMatchCredentialsSeparateFile(uint8_t index); +#endif private: - // Pick the possible - void loadCandidatesFromScanned(); + // Pick the possible + void loadCandidatesFromScanned(); - void addFromRTC(); + void addFromRTC(); - void purge_unusable(); + void purge_unusable(); // Load SSID and pass/key from the settings. - static bool get_SSID_key(uint8_t index, - String& ssid, - String& key); + static bool get_SSID_key(uint8_t index, + String& ssid, + String& key); + + static bool hasWiFiCredentials(); public: - static bool get_SSID(uint8_t index, String& ssid); + static bool get_SSID(uint8_t index, + String& ssid); static String get_key(uint8_t index); private: - std::list candidates; + std::listcandidates; - std::list known; + std::listknown; - std::list scanned; - std::list scanned_new; + std::listscanned; + std::listscanned_new; WiFi_AP_Candidate_const_iterator known_it; @@ -109,8 +120,16 @@ private: bool _mustLoadCredentials = true; bool _addedKnownCandidate = false; + uint8_t _last_scan_channel{}; + public: - int attemptsLeft = 1; + + int attemptsLeft = WiFi_CONNECT_ATTEMPTS; + }; -#endif // ifndef HELPERS_WIFI_AP_CANDIDATESLIST_H +} // namespace wifi +} // namespace net +} // namespace ESPEasy + +#endif // if FEATURE_WIFI diff --git a/src/ESPEasy/net/Helpers/_NWPlugin_Helper_webform.cpp b/src/ESPEasy/net/Helpers/_NWPlugin_Helper_webform.cpp new file mode 100644 index 000000000..5fd57e880 --- /dev/null +++ b/src/ESPEasy/net/Helpers/_NWPlugin_Helper_webform.cpp @@ -0,0 +1,225 @@ +#include "../Helpers/_NWPlugin_Helper_webform.h" + +#include "../../../ESPEasy_common.h" + +#include "../../../src/DataStructs/ESPEasy_EventStruct.h" +#include "../../../src/DataTypes/ESPEasy_plugin_functions.h" +#include "../../../src/Globals/SecuritySettings.h" +#include "../../../src/Globals/Settings.h" +#include "../../../src/Helpers/Networking.h" +#include "../../../src/WebServer/ESPEasy_WebServer.h" +#include "../../../src/WebServer/Markup.h" +#include "../../../src/WebServer/Markup_Forms.h" +#include "../Globals/NWPlugins.h" +#include "../Helpers/_NWPlugin_init.h" + +namespace ESPEasy { +namespace net { + +/*********************************************************************************************\ +* Functions to load and store network settings on the web page. +\*********************************************************************************************/ +const __FlashStringHelper* toString(NetworkSettingsStruct::VarType parameterIdx, bool displayName) +{ + switch (parameterIdx) + { + case NetworkSettingsStruct::NETWORK_IP: return F("IP"); + case NetworkSettingsStruct::NETWORK_PORT: return F("Port"); + case NetworkSettingsStruct::NETWORK_USER: return F("User"); + case NetworkSettingsStruct::NETWORK_PASS: return F("Password"); + case NetworkSettingsStruct::NETWORK_ENABLED: + + if (displayName) { return F("Enabled"); } + else { return F("networkenabled"); } + + + default: + return F("Undefined"); + } +} + +String getNetworkParameterName(networkDriverIndex_t NetworkDriverIndex, + NetworkSettingsStruct::VarType parameterIdx, + bool displayName, + bool & isAlternative) { + String name; + + if (displayName) { + EventStruct tmpEvent; + tmpEvent.idx = parameterIdx; + + // Only Network Driver specific call, so may call do_NWPluginCall directly + if (do_NWPluginCall(NetworkDriverIndex, NWPlugin::Function::NWPLUGIN_GET_PARAMETER_DISPLAY_NAME, &tmpEvent, name)) { + // Found an alternative name for it. + isAlternative = true; + return name; + } + } + isAlternative = false; + + name = toString(parameterIdx, displayName); + + if (!displayName) { + // Change name to lower case and remove spaces to make it an internal name. + name.toLowerCase(); + removeChar(name, ' '); + } + return name; +} + +String getNetworkParameterInternalName(networkDriverIndex_t NetworkDriverIndex, NetworkSettingsStruct::VarType parameterIdx) { + bool isAlternative; // Dummy, not needed for internal name + bool displayName = false; + + return getNetworkParameterName(NetworkDriverIndex, parameterIdx, displayName, isAlternative); +} + +String getNetworkParameterDisplayName(networkDriverIndex_t NetworkDriverIndex, + NetworkSettingsStruct::VarType parameterIdx, + bool & isAlternative) { + bool displayName = true; + + return getNetworkParameterName(NetworkDriverIndex, parameterIdx, displayName, isAlternative); +} + +void addNetworkEnabledForm(networkIndex_t networkindex) { + networkDriverIndex_t NetworkDriverIndex = getNetworkDriverIndex_from_NetworkIndex(networkindex); + + if (!validNetworkDriverIndex(NetworkDriverIndex)) { + return; + } + + NetworkSettingsStruct::VarType varType = NetworkSettingsStruct::NETWORK_ENABLED; + + bool isAlternativeDisplayName = false; + const String displayName = getNetworkParameterDisplayName(NetworkDriverIndex, varType, isAlternativeDisplayName); + const String internalName = getNetworkParameterInternalName(NetworkDriverIndex, varType); + addFormCheckBox(displayName, internalName, Settings.getNetworkEnabled(networkindex)); +} + +void addNetworkParameterForm(const NetworkSettingsStruct & NetworkSettings, + networkIndex_t networkindex, + NetworkSettingsStruct::VarType varType) { + networkDriverIndex_t NetworkDriverIndex = getNetworkDriverIndex_from_NetworkIndex(networkindex); + + if (!validNetworkDriverIndex(NetworkDriverIndex)) { + return; + } + + bool isAlternativeDisplayName = false; + const String displayName = getNetworkParameterDisplayName(NetworkDriverIndex, varType, isAlternativeDisplayName); + const String internalName = getNetworkParameterInternalName(NetworkDriverIndex, varType); + + switch (varType) + { + case NetworkSettingsStruct::NETWORK_IP: + { + addFormIPBox(displayName, internalName, NetworkSettings.IP); + break; + } + case NetworkSettingsStruct::NETWORK_PORT: + { + addFormNumericBox(displayName, internalName, NetworkSettings.Port, 1, 65535); + break; + } + + case NetworkSettingsStruct::NETWORK_USER: + { + /* + const size_t fieldMaxLength = + NetworkSettings.useExtendedCredentials() ? EXT_SECURITY_MAX_USER_LENGTH : sizeof(SecuritySettings.NetworkUser[0]) - 1; + addFormTextBox(displayName, + internalName, + getNetworkUser(networkindex, NetworkSettings, false), + fieldMaxLength); + */ + break; + } + case NetworkSettingsStruct::NETWORK_PASS: + { + /* + const size_t fieldMaxLength = + NetworkSettings.useExtendedCredentials() ? EXT_SECURITY_MAX_PASS_LENGTH : sizeof(SecuritySettings.NetworkPassword[0]) - 1; + + if (isAlternativeDisplayName) { + // It is not a regular password, thus use normal text field. + addFormTextBox(displayName, internalName, + getNetworkPass(networkindex, NetworkSettings), + fieldMaxLength); + } else { + addFormPasswordBox(displayName, internalName, + getNetworkPass(networkindex, NetworkSettings), + fieldMaxLength); + } + */ + break; + } + + case NetworkSettingsStruct::NETWORK_TIMEOUT: + { + break; + } + + case NetworkSettingsStruct::NETWORK_ENABLED: + addFormCheckBox(displayName, internalName, Settings.getNetworkEnabled(networkindex)); + break; + } +} + +void saveNetworkParameterForm(NetworkSettingsStruct & NetworkSettings, + networkIndex_t networkindex, + NetworkSettingsStruct::VarType varType) { + const networkDriverIndex_t NetworkDriverIndex = + getNetworkDriverIndex_from_NetworkIndex(networkindex); + + if (!validNetworkDriverIndex(NetworkDriverIndex)) { + return; + } + const String internalName = getNetworkParameterInternalName(NetworkDriverIndex, varType); + + switch (varType) + { + + case NetworkSettingsStruct::NETWORK_IP: + /* + if (!NetworkSettings.UseDNS) + { + str2ip(webArg(internalName), NetworkSettings.IP); + } + */ + break; + case NetworkSettingsStruct::NETWORK_PORT: + NetworkSettings.Port = getFormItemInt(internalName, NetworkSettings.Port); + break; + + case NetworkSettingsStruct::NETWORK_USER: + // setNetworkUser(networkindex, NetworkSettings, webArg(internalName)); + break; + case NetworkSettingsStruct::NETWORK_PASS: + { + String password; + + if (getFormPassword(internalName, password)) { + // setNetworkPass(networkindex, NetworkSettings, password); + } + break; + } + + case NetworkSettingsStruct::NETWORK_TIMEOUT: + { + break; + } + + case NetworkSettingsStruct::NETWORK_ENABLED: + Settings.setNetworkEnabled(networkindex, isFormItemChecked(internalName)); +#ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, + concat(F("Save NW_Enabled: "), + internalName) + ' ' + isFormItemChecked(internalName) + ' ' + Settings.getNetworkEnabled(networkindex)); +#endif + break; + } +} + +} // namespace net +} // namespace ESPEasy diff --git a/src/ESPEasy/net/Helpers/_NWPlugin_Helper_webform.h b/src/ESPEasy/net/Helpers/_NWPlugin_Helper_webform.h new file mode 100644 index 000000000..0adfe3684 --- /dev/null +++ b/src/ESPEasy/net/Helpers/_NWPlugin_Helper_webform.h @@ -0,0 +1,40 @@ +#pragma once + +#include "../../../ESPEasy_common.h" + +#include "../../../src/Helpers/ESPEasy_UnitOfMeasure.h" + +#include "../DataStructs/NetworkSettingsStruct.h" +#include "../Globals/NWPlugins.h" + +namespace ESPEasy { +namespace net { + + +/*********************************************************************************************\ +* Functions to load and store network settings on the web page. +\*********************************************************************************************/ +String getNetworkParameterName(networkDriverIndex_t NetworkDriverIndex, + NetworkSettingsStruct::VarType parameterIdx, + bool displayName, + bool & isAlternative); + +String getNetworkParameterInternalName(networkDriverIndex_t NetworkDriverIndex, + NetworkSettingsStruct::VarType parameterIdx); + +String getNetworkParameterDisplayName(networkDriverIndex_t NetworkDriverIndex, + NetworkSettingsStruct::VarType parameterIdx, + bool & isAlternative); + +void addNetworkEnabledForm(networkIndex_t networkindex); + +void addNetworkParameterForm(const NetworkSettingsStruct & NetworkSettings, + networkIndex_t networkindex, + NetworkSettingsStruct::VarType varType); + +void saveNetworkParameterForm(NetworkSettingsStruct & NetworkSettings, + networkIndex_t networkindex, + NetworkSettingsStruct::VarType varType); + +} // namespace net +} // namespace ESPEasy diff --git a/src/ESPEasy/net/Helpers/_NWPlugin_init.cpp b/src/ESPEasy/net/Helpers/_NWPlugin_init.cpp new file mode 100644 index 000000000..c01d2cfc5 --- /dev/null +++ b/src/ESPEasy/net/Helpers/_NWPlugin_init.cpp @@ -0,0 +1,2212 @@ +#include "../Helpers/_NWPlugin_init.h" + +#include "../../../src/DataStructs/ESPEasy_EventStruct.h" +#include "../../../src/DataStructs/TimingStats.h" +#include "../../../src/DataTypes/ESPEasy_plugin_functions.h" +#include "../../../src/Globals/Settings.h" +#include "../../../src/Helpers/Misc.h" +#include "../../../src/Helpers/StringConverter.h" +#include "../Globals/NWPlugins.h" + +namespace ESPEasy { +namespace net { + +// ******************************************************************************** +// Initialize all Network NWPlugins that where defined earlier +// and initialize the function call pointer into the NWPlugin array +// ******************************************************************************** + +constexpr /*nwpluginID_t*/ uint8_t NetworkDriverIndex_to_NWPlugin_id[] PROGMEM = +{ +#ifdef USES_NW001 + 1, +#endif // ifdef USES_NW001 + +#ifdef USES_NW002 + 2, +#endif // ifdef USES_NW002 + +#ifdef USES_NW003 + 3, +#endif // ifdef USES_NW003 + +#ifdef USES_NW004 + 4, +#endif // ifdef USES_NW004 + +#ifdef USES_NW005 + 5, +#endif // ifdef USES_NW005 + +#ifdef USES_NW006 + 6, +#endif // ifdef USES_NW006 + +#ifdef USES_NW007 + 7, +#endif // ifdef USES_NW007 + +#ifdef USES_NW008 + 8, +#endif // ifdef USES_NW008 + +#ifdef USES_NW009 + 9, +#endif // ifdef USES_NW009 + +#ifdef USES_NW010 + 10, +#endif // ifdef USES_NW010 + +#ifdef USES_NW011 + 11, +#endif // ifdef USES_NW011 + +#ifdef USES_NW012 + 12, +#endif // ifdef USES_NW012 + +#ifdef USES_NW013 + 13, +#endif // ifdef USES_NW013 + +#ifdef USES_NW014 + 14, +#endif // ifdef USES_NW014 + +#ifdef USES_NW015 + 15, +#endif // ifdef USES_NW015 + +#ifdef USES_NW016 + 16, +#endif // ifdef USES_NW016 + +#ifdef USES_NW017 + 17, +#endif // ifdef USES_NW017 + +#ifdef USES_NW018 + 18, +#endif // ifdef USES_NW018 + +#ifdef USES_NW019 + 19, +#endif // ifdef USES_NW019 + +#ifdef USES_NW020 + 20, +#endif // ifdef USES_NW020 + +#ifdef USES_NW021 + 21, +#endif // ifdef USES_NW021 + +#ifdef USES_NW022 + 22, +#endif // ifdef USES_NW022 + +#ifdef USES_NW023 + 23, +#endif // ifdef USES_NW023 + +#ifdef USES_NW024 + 24, +#endif // ifdef USES_NW024 + +#ifdef USES_NW025 + 25, +#endif // ifdef USES_NW025 + +#ifdef USES_NW026 + 26, +#endif // ifdef USES_NW026 + +#ifdef USES_NW027 + 27, +#endif // ifdef USES_NW027 + +#ifdef USES_NW028 + 28, +#endif // ifdef USES_NW028 + +#ifdef USES_NW029 + 29, +#endif // ifdef USES_NW029 + +#ifdef USES_NW030 + 30, +#endif // ifdef USES_NW030 + +#ifdef USES_NW031 + 31, +#endif // ifdef USES_NW031 + +#ifdef USES_NW032 + 32, +#endif // ifdef USES_NW032 + +#ifdef USES_NW033 + 33, +#endif // ifdef USES_NW033 + +#ifdef USES_NW034 + 34, +#endif // ifdef USES_NW034 + +#ifdef USES_NW035 + 35, +#endif // ifdef USES_NW035 + +#ifdef USES_NW036 + 36, +#endif // ifdef USES_NW036 + +#ifdef USES_NW037 + 37, +#endif // ifdef USES_NW037 + +#ifdef USES_NW038 + 38, +#endif // ifdef USES_NW038 + +#ifdef USES_NW039 + 39, +#endif // ifdef USES_NW039 + +#ifdef USES_NW040 + 40, +#endif // ifdef USES_NW040 + +#ifdef USES_NW041 + 41, +#endif // ifdef USES_NW041 + +#ifdef USES_NW042 + 42, +#endif // ifdef USES_NW042 + +#ifdef USES_NW043 + 43, +#endif // ifdef USES_NW043 + +#ifdef USES_NW044 + 44, +#endif // ifdef USES_NW044 + +#ifdef USES_NW045 + 45, +#endif // ifdef USES_NW045 + +#ifdef USES_NW046 + 46, +#endif // ifdef USES_NW046 + +#ifdef USES_NW047 + 47, +#endif // ifdef USES_NW047 + +#ifdef USES_NW048 + 48, +#endif // ifdef USES_NW048 + +#ifdef USES_NW049 + 49, +#endif // ifdef USES_NW049 + +#ifdef USES_NW050 + 50, +#endif // ifdef USES_NW050 + +#ifdef USES_NW051 + 51, +#endif // ifdef USES_NW051 + +#ifdef USES_NW052 + 52, +#endif // ifdef USES_NW052 + +#ifdef USES_NW053 + 53, +#endif // ifdef USES_NW053 + +#ifdef USES_NW054 + 54, +#endif // ifdef USES_NW054 + +#ifdef USES_NW055 + 55, +#endif // ifdef USES_NW055 + +#ifdef USES_NW056 + 56, +#endif // ifdef USES_NW056 + +#ifdef USES_NW057 + 57, +#endif // ifdef USES_NW057 + +#ifdef USES_NW058 + 58, +#endif // ifdef USES_NW058 + +#ifdef USES_NW059 + 59, +#endif // ifdef USES_NW059 + +#ifdef USES_NW060 + 60, +#endif // ifdef USES_NW060 + +#ifdef USES_NW061 + 61, +#endif // ifdef USES_NW061 + +#ifdef USES_NW062 + 62, +#endif // ifdef USES_NW062 + +#ifdef USES_NW063 + 63, +#endif // ifdef USES_NW063 + +#ifdef USES_NW064 + 64, +#endif // ifdef USES_NW064 + +#ifdef USES_NW065 + 65, +#endif // ifdef USES_NW065 + +#ifdef USES_NW066 + 66, +#endif // ifdef USES_NW066 + +#ifdef USES_NW067 + 67, +#endif // ifdef USES_NW067 + +#ifdef USES_NW068 + 68, +#endif // ifdef USES_NW068 + +#ifdef USES_NW069 + 69, +#endif // ifdef USES_NW069 + +#ifdef USES_NW070 + 70, +#endif // ifdef USES_NW070 + +#ifdef USES_NW071 + 71, +#endif // ifdef USES_NW071 + +#ifdef USES_NW072 + 72, +#endif // ifdef USES_NW072 + +#ifdef USES_NW073 + 73, +#endif // ifdef USES_NW073 + +#ifdef USES_NW074 + 74, +#endif // ifdef USES_NW074 + +#ifdef USES_NW075 + 75, +#endif // ifdef USES_NW075 + +#ifdef USES_NW076 + 76, +#endif // ifdef USES_NW076 + +#ifdef USES_NW077 + 77, +#endif // ifdef USES_NW077 + +#ifdef USES_NW078 + 78, +#endif // ifdef USES_NW078 + +#ifdef USES_NW079 + 79, +#endif // ifdef USES_NW079 + +#ifdef USES_NW080 + 80, +#endif // ifdef USES_NW080 + +#ifdef USES_NW081 + 81, +#endif // ifdef USES_NW081 + +#ifdef USES_NW082 + 82, +#endif // ifdef USES_NW082 + +#ifdef USES_NW083 + 83, +#endif // ifdef USES_NW083 + +#ifdef USES_NW084 + 84, +#endif // ifdef USES_NW084 + +#ifdef USES_NW085 + 85, +#endif // ifdef USES_NW085 + +#ifdef USES_NW086 + 86, +#endif // ifdef USES_NW086 + +#ifdef USES_NW087 + 87, +#endif // ifdef USES_NW087 + +#ifdef USES_NW088 + 88, +#endif // ifdef USES_NW088 + +#ifdef USES_NW089 + 89, +#endif // ifdef USES_NW089 + +#ifdef USES_NW090 + 90, +#endif // ifdef USES_NW090 + +#ifdef USES_NW091 + 91, +#endif // ifdef USES_NW091 + +#ifdef USES_NW092 + 92, +#endif // ifdef USES_NW092 + +#ifdef USES_NW093 + 93, +#endif // ifdef USES_NW093 + +#ifdef USES_NW094 + 94, +#endif // ifdef USES_NW094 + +#ifdef USES_NW095 + 95, +#endif // ifdef USES_NW095 + +#ifdef USES_NW096 + 96, +#endif // ifdef USES_NW096 + +#ifdef USES_NW097 + 97, +#endif // ifdef USES_NW097 + +#ifdef USES_NW098 + 98, +#endif // ifdef USES_NW098 + +#ifdef USES_NW099 + 99, +#endif // ifdef USES_NW099 + +#ifdef USES_NW100 + 100, +#endif // ifdef USES_NW100 + +#ifdef USES_NW101 + 101, +#endif // ifdef USES_NW101 + +#ifdef USES_NW102 + 102, +#endif // ifdef USES_NW102 + +#ifdef USES_NW103 + 103, +#endif // ifdef USES_NW103 + +#ifdef USES_NW104 + 104, +#endif // ifdef USES_NW104 + +#ifdef USES_NW105 + 105, +#endif // ifdef USES_NW105 + +#ifdef USES_NW106 + 106, +#endif // ifdef USES_NW106 + +#ifdef USES_NW107 + 107, +#endif // ifdef USES_NW107 + +#ifdef USES_NW108 + 108, +#endif // ifdef USES_NW108 + +#ifdef USES_NW109 + 109, +#endif // ifdef USES_NW109 + +#ifdef USES_NW110 + 110, +#endif // ifdef USES_NW110 + +#ifdef USES_NW111 + 111, +#endif // ifdef USES_NW111 + +#ifdef USES_NW112 + 112, +#endif // ifdef USES_NW112 + +#ifdef USES_NW113 + 113, +#endif // ifdef USES_NW113 + +#ifdef USES_NW114 + 114, +#endif // ifdef USES_NW114 + +#ifdef USES_NW115 + 115, +#endif // ifdef USES_NW115 + +#ifdef USES_NW116 + 116, +#endif // ifdef USES_NW116 + +#ifdef USES_NW117 + 117, +#endif // ifdef USES_NW117 + +#ifdef USES_NW118 + 118, +#endif // ifdef USES_NW118 + +#ifdef USES_NW119 + 119, +#endif // ifdef USES_NW119 + +#ifdef USES_NW120 + 120, +#endif // ifdef USES_NW120 + +#ifdef USES_NW121 + 121, +#endif // ifdef USES_NW121 + +#ifdef USES_NW122 + 122, +#endif // ifdef USES_NW122 + +#ifdef USES_NW123 + 123, +#endif // ifdef USES_NW123 + +#ifdef USES_NW124 + 124, +#endif // ifdef USES_NW124 + +#ifdef USES_NW125 + 125, +#endif // ifdef USES_NW125 + +#ifdef USES_NW126 + 126, +#endif // ifdef USES_NW126 + +#ifdef USES_NW127 + 127, +#endif // ifdef USES_NW127 + +#ifdef USES_NW128 + 128, +#endif // ifdef USES_NW128 + +#ifdef USES_NW129 + 129, +#endif // ifdef USES_NW129 + +#ifdef USES_NW130 + 130, +#endif // ifdef USES_NW130 + +#ifdef USES_NW131 + 131, +#endif // ifdef USES_NW131 + +#ifdef USES_NW132 + 132, +#endif // ifdef USES_NW132 + +#ifdef USES_NW133 + 133, +#endif // ifdef USES_NW133 + +#ifdef USES_NW134 + 134, +#endif // ifdef USES_NW134 + +#ifdef USES_NW135 + 135, +#endif // ifdef USES_NW135 + +#ifdef USES_NW136 + 136, +#endif // ifdef USES_NW136 + +#ifdef USES_NW137 + 137, +#endif // ifdef USES_NW137 + +#ifdef USES_NW138 + 138, +#endif // ifdef USES_NW138 + +#ifdef USES_NW139 + 139, +#endif // ifdef USES_NW139 + +#ifdef USES_NW140 + 140, +#endif // ifdef USES_NW140 + +#ifdef USES_NW141 + 141, +#endif // ifdef USES_NW141 + +#ifdef USES_NW142 + 142, +#endif // ifdef USES_NW142 + +#ifdef USES_NW143 + 143, +#endif // ifdef USES_NW143 + +#ifdef USES_NW144 + 144, +#endif // ifdef USES_NW144 + +#ifdef USES_NW145 + 145, +#endif // ifdef USES_NW145 + +#ifdef USES_NW146 + 146, +#endif // ifdef USES_NW146 + +#ifdef USES_NW147 + 147, +#endif // ifdef USES_NW147 + +#ifdef USES_NW148 + 148, +#endif // ifdef USES_NW148 + +#ifdef USES_NW149 + 149, +#endif // ifdef USES_NW149 + +#ifdef USES_NW150 + 150, +#endif // ifdef USES_NW150 + +#ifdef USES_NW151 + 151, +#endif // ifdef USES_NW151 + +#ifdef USES_NW152 + 152, +#endif // ifdef USES_NW152 + +#ifdef USES_NW153 + 153, +#endif // ifdef USES_NW153 + +#ifdef USES_NW154 + 154, +#endif // ifdef USES_NW154 + +#ifdef USES_NW155 + 155, +#endif // ifdef USES_NW155 + +#ifdef USES_NW156 + 156, +#endif // ifdef USES_NW156 + +#ifdef USES_NW157 + 157, +#endif // ifdef USES_NW157 + +#ifdef USES_NW158 + 158, +#endif // ifdef USES_NW158 + +#ifdef USES_NW159 + 159, +#endif // ifdef USES_NW159 + +#ifdef USES_NW160 + 160, +#endif // ifdef USES_NW160 + +#ifdef USES_NW161 + 161, +#endif // ifdef USES_NW161 + +#ifdef USES_NW162 + 162, +#endif // ifdef USES_NW162 + +#ifdef USES_NW163 + 163, +#endif // ifdef USES_NW163 + +#ifdef USES_NW164 + 164, +#endif // ifdef USES_NW164 + +#ifdef USES_NW165 + 165, +#endif // ifdef USES_NW165 + +#ifdef USES_NW166 + 166, +#endif // ifdef USES_NW166 + +#ifdef USES_NW167 + 167, +#endif // ifdef USES_NW167 + +#ifdef USES_NW168 + 168, +#endif // ifdef USES_NW168 + +#ifdef USES_NW169 + 169, +#endif // ifdef USES_NW169 + +#ifdef USES_NW170 + 170, +#endif // ifdef USES_NW170 + +#ifdef USES_NW171 + 171, +#endif // ifdef USES_NW171 + +#ifdef USES_NW172 + 172, +#endif // ifdef USES_NW172 + +#ifdef USES_NW173 + 173, +#endif // ifdef USES_NW173 + +#ifdef USES_NW174 + 174, +#endif // ifdef USES_NW174 + +#ifdef USES_NW175 + 175, +#endif // ifdef USES_NW175 + +#ifdef USES_NW176 + 176, +#endif // ifdef USES_NW176 + +#ifdef USES_NW177 + 177, +#endif // ifdef USES_NW177 + +#ifdef USES_NW178 + 178, +#endif // ifdef USES_NW178 + +#ifdef USES_NW179 + 179, +#endif // ifdef USES_NW179 + +#ifdef USES_NW180 + 180, +#endif // ifdef USES_NW180 + +#ifdef USES_NW181 + 181, +#endif // ifdef USES_NW181 + +#ifdef USES_NW182 + 182, +#endif // ifdef USES_NW182 + +#ifdef USES_NW183 + 183, +#endif // ifdef USES_NW183 + +#ifdef USES_NW184 + 184, +#endif // ifdef USES_NW184 + +#ifdef USES_NW185 + 185, +#endif // ifdef USES_NW185 + +#ifdef USES_NW186 + 186, +#endif // ifdef USES_NW186 + +#ifdef USES_NW187 + 187, +#endif // ifdef USES_NW187 + +#ifdef USES_NW188 + 188, +#endif // ifdef USES_NW188 + +#ifdef USES_NW189 + 189, +#endif // ifdef USES_NW189 + +#ifdef USES_NW190 + 190, +#endif // ifdef USES_NW190 + +#ifdef USES_NW191 + 191, +#endif // ifdef USES_NW191 + +#ifdef USES_NW192 + 192, +#endif // ifdef USES_NW192 + +#ifdef USES_NW193 + 193, +#endif // ifdef USES_NW193 + +#ifdef USES_NW194 + 194, +#endif // ifdef USES_NW194 + +#ifdef USES_NW195 + 195, +#endif // ifdef USES_NW195 + +#ifdef USES_NW196 + 196, +#endif // ifdef USES_NW196 + +#ifdef USES_NW197 + 197, +#endif // ifdef USES_NW197 + +#ifdef USES_NW198 + 198, +#endif // ifdef USES_NW198 + +#ifdef USES_NW199 + 199, +#endif // ifdef USES_NW199 + +#ifdef USES_NW200 + 200, +#endif // ifdef USES_NW200 + +#ifdef USES_NW201 + 201, +#endif // ifdef USES_NW201 + +#ifdef USES_NW202 + 202, +#endif // ifdef USES_NW202 + +#ifdef USES_NW203 + 203, +#endif // ifdef USES_NW203 + +#ifdef USES_NW204 + 204, +#endif // ifdef USES_NW204 + +#ifdef USES_NW205 + 205, +#endif // ifdef USES_NW205 + +#ifdef USES_NW206 + 206, +#endif // ifdef USES_NW206 + +#ifdef USES_NW207 + 207, +#endif // ifdef USES_NW207 + +#ifdef USES_NW208 + 208, +#endif // ifdef USES_NW208 + +#ifdef USES_NW209 + 209, +#endif // ifdef USES_NW209 + +#ifdef USES_NW210 + 210, +#endif // ifdef USES_NW210 + +#ifdef USES_NW211 + 211, +#endif // ifdef USES_NW211 + +#ifdef USES_NW212 + 212, +#endif // ifdef USES_NW212 + +#ifdef USES_NW213 + 213, +#endif // ifdef USES_NW213 + +#ifdef USES_NW214 + 214, +#endif // ifdef USES_NW214 + +#ifdef USES_NW215 + 215, +#endif // ifdef USES_NW215 + +#ifdef USES_NW216 + 216, +#endif // ifdef USES_NW216 + +#ifdef USES_NW217 + 217, +#endif // ifdef USES_NW217 + +#ifdef USES_NW218 + 218, +#endif // ifdef USES_NW218 + +#ifdef USES_NW219 + 219, +#endif // ifdef USES_NW219 + +#ifdef USES_NW220 + 220, +#endif // ifdef USES_NW220 + +#ifdef USES_NW221 + 221, +#endif // ifdef USES_NW221 + +#ifdef USES_NW222 + 222, +#endif // ifdef USES_NW222 + +#ifdef USES_NW223 + 223, +#endif // ifdef USES_NW223 + +#ifdef USES_NW224 + 224, +#endif // ifdef USES_NW224 + +#ifdef USES_NW225 + 225, +#endif // ifdef USES_NW225 + +#ifdef USES_NW226 + 226, +#endif // ifdef USES_NW226 + +#ifdef USES_NW227 + 227, +#endif // ifdef USES_NW227 + +#ifdef USES_NW228 + 228, +#endif // ifdef USES_NW228 + +#ifdef USES_NW229 + 229, +#endif // ifdef USES_NW229 + +#ifdef USES_NW230 + 230, +#endif // ifdef USES_NW230 + +#ifdef USES_NW231 + 231, +#endif // ifdef USES_NW231 + +#ifdef USES_NW232 + 232, +#endif // ifdef USES_NW232 + +#ifdef USES_NW233 + 233, +#endif // ifdef USES_NW233 + +#ifdef USES_NW234 + 234, +#endif // ifdef USES_NW234 + +#ifdef USES_NW235 + 235, +#endif // ifdef USES_NW235 + +#ifdef USES_NW236 + 236, +#endif // ifdef USES_NW236 + +#ifdef USES_NW237 + 237, +#endif // ifdef USES_NW237 + +#ifdef USES_NW238 + 238, +#endif // ifdef USES_NW238 + +#ifdef USES_NW239 + 239, +#endif // ifdef USES_NW239 + +#ifdef USES_NW240 + 240, +#endif // ifdef USES_NW240 + +#ifdef USES_NW241 + 241, +#endif // ifdef USES_NW241 + +#ifdef USES_NW242 + 242, +#endif // ifdef USES_NW242 + +#ifdef USES_NW243 + 243, +#endif // ifdef USES_NW243 + +#ifdef USES_NW244 + 244, +#endif // ifdef USES_NW244 + +#ifdef USES_NW245 + 245, +#endif // ifdef USES_NW245 + +#ifdef USES_NW246 + 246, +#endif // ifdef USES_NW246 + +#ifdef USES_NW247 + 247, +#endif // ifdef USES_NW247 + +#ifdef USES_NW248 + 248, +#endif // ifdef USES_NW248 + +#ifdef USES_NW249 + 249, +#endif // ifdef USES_NW249 + +#ifdef USES_NW250 + 250, +#endif // ifdef USES_NW250 + +#ifdef USES_NW251 + 251, +#endif // ifdef USES_NW251 + +#ifdef USES_NW252 + 252, +#endif // ifdef USES_NW252 + +#ifdef USES_NW253 + 253, +#endif // ifdef USES_NW253 + +#ifdef USES_NW254 + 254, +#endif // ifdef USES_NW254 + +#ifdef USES_NW255 + 255, +#endif // ifdef USES_NW255 +}; + + +typedef bool (*NWPlugin_ptr_t)(NWPlugin::Function, + EventStruct *, + String&); + +const NWPlugin_ptr_t PROGMEM NWPlugin_ptr[] = +{ +#ifdef USES_NW001 + &NWPlugin_001, +#endif // ifdef USES_NW001 + +#ifdef USES_NW002 + &NWPlugin_002, +#endif // ifdef USES_NW002 + +#ifdef USES_NW003 + &NWPlugin_003, +#endif // ifdef USES_NW003 + +#ifdef USES_NW004 + &NWPlugin_004, +#endif // ifdef USES_NW004 + +#ifdef USES_NW005 + &NWPlugin_005, +#endif // ifdef USES_NW005 + +#ifdef USES_NW006 + &NWPlugin_006, +#endif // ifdef USES_NW006 + +#ifdef USES_NW007 + &NWPlugin_007, +#endif // ifdef USES_NW007 + +#ifdef USES_NW008 + &NWPlugin_008, +#endif // ifdef USES_NW008 + +#ifdef USES_NW009 + &NWPlugin_009, +#endif // ifdef USES_NW009 + +#ifdef USES_NW010 + &NWPlugin_010, +#endif // ifdef USES_NW010 + +#ifdef USES_NW011 + &NWPlugin_011, +#endif // ifdef USES_NW011 + +#ifdef USES_NW012 + &NWPlugin_012, +#endif // ifdef USES_NW012 + +#ifdef USES_NW013 + &NWPlugin_013, +#endif // ifdef USES_NW013 + +#ifdef USES_NW014 + &NWPlugin_014, +#endif // ifdef USES_NW014 + +#ifdef USES_NW015 + &NWPlugin_015, +#endif // ifdef USES_NW015 + +#ifdef USES_NW016 + &NWPlugin_016, +#endif // ifdef USES_NW016 + +#ifdef USES_NW017 + &NWPlugin_017, +#endif // ifdef USES_NW017 + +#ifdef USES_NW018 + &NWPlugin_018, +#endif // ifdef USES_NW018 + +#ifdef USES_NW019 + &NWPlugin_019, +#endif // ifdef USES_NW019 + +#ifdef USES_NW020 + &NWPlugin_020, +#endif // ifdef USES_NW020 + +#ifdef USES_NW021 + &NWPlugin_021, +#endif // ifdef USES_NW021 + +#ifdef USES_NW022 + &NWPlugin_022, +#endif // ifdef USES_NW022 + +#ifdef USES_NW023 + &NWPlugin_023, +#endif // ifdef USES_NW023 + +#ifdef USES_NW024 + &NWPlugin_024, +#endif // ifdef USES_NW024 + +#ifdef USES_NW025 + &NWPlugin_025, +#endif // ifdef USES_NW025 + +#ifdef USES_NW026 + &NWPlugin_026, +#endif // ifdef USES_NW026 + +#ifdef USES_NW027 + &NWPlugin_027, +#endif // ifdef USES_NW027 + +#ifdef USES_NW028 + &NWPlugin_028, +#endif // ifdef USES_NW028 + +#ifdef USES_NW029 + &NWPlugin_029, +#endif // ifdef USES_NW029 + +#ifdef USES_NW030 + &NWPlugin_030, +#endif // ifdef USES_NW030 + +#ifdef USES_NW031 + &NWPlugin_031, +#endif // ifdef USES_NW031 + +#ifdef USES_NW032 + &NWPlugin_032, +#endif // ifdef USES_NW032 + +#ifdef USES_NW033 + &NWPlugin_033, +#endif // ifdef USES_NW033 + +#ifdef USES_NW034 + &NWPlugin_034, +#endif // ifdef USES_NW034 + +#ifdef USES_NW035 + &NWPlugin_035, +#endif // ifdef USES_NW035 + +#ifdef USES_NW036 + &NWPlugin_036, +#endif // ifdef USES_NW036 + +#ifdef USES_NW037 + &NWPlugin_037, +#endif // ifdef USES_NW037 + +#ifdef USES_NW038 + &NWPlugin_038, +#endif // ifdef USES_NW038 + +#ifdef USES_NW039 + &NWPlugin_039, +#endif // ifdef USES_NW039 + +#ifdef USES_NW040 + &NWPlugin_040, +#endif // ifdef USES_NW040 + +#ifdef USES_NW041 + &NWPlugin_041, +#endif // ifdef USES_NW041 + +#ifdef USES_NW042 + &NWPlugin_042, +#endif // ifdef USES_NW042 + +#ifdef USES_NW043 + &NWPlugin_043, +#endif // ifdef USES_NW043 + +#ifdef USES_NW044 + &NWPlugin_044, +#endif // ifdef USES_NW044 + +#ifdef USES_NW045 + &NWPlugin_045, +#endif // ifdef USES_NW045 + +#ifdef USES_NW046 + &NWPlugin_046, +#endif // ifdef USES_NW046 + +#ifdef USES_NW047 + &NWPlugin_047, +#endif // ifdef USES_NW047 + +#ifdef USES_NW048 + &NWPlugin_048, +#endif // ifdef USES_NW048 + +#ifdef USES_NW049 + &NWPlugin_049, +#endif // ifdef USES_NW049 + +#ifdef USES_NW050 + &NWPlugin_050, +#endif // ifdef USES_NW050 + +#ifdef USES_NW051 + &NWPlugin_051, +#endif // ifdef USES_NW051 + +#ifdef USES_NW052 + &NWPlugin_052, +#endif // ifdef USES_NW052 + +#ifdef USES_NW053 + &NWPlugin_053, +#endif // ifdef USES_NW053 + +#ifdef USES_NW054 + &NWPlugin_054, +#endif // ifdef USES_NW054 + +#ifdef USES_NW055 + &NWPlugin_055, +#endif // ifdef USES_NW055 + +#ifdef USES_NW056 + &NWPlugin_056, +#endif // ifdef USES_NW056 + +#ifdef USES_NW057 + &NWPlugin_057, +#endif // ifdef USES_NW057 + +#ifdef USES_NW058 + &NWPlugin_058, +#endif // ifdef USES_NW058 + +#ifdef USES_NW059 + &NWPlugin_059, +#endif // ifdef USES_NW059 + +#ifdef USES_NW060 + &NWPlugin_060, +#endif // ifdef USES_NW060 + +#ifdef USES_NW061 + &NWPlugin_061, +#endif // ifdef USES_NW061 + +#ifdef USES_NW062 + &NWPlugin_062, +#endif // ifdef USES_NW062 + +#ifdef USES_NW063 + &NWPlugin_063, +#endif // ifdef USES_NW063 + +#ifdef USES_NW064 + &NWPlugin_064, +#endif // ifdef USES_NW064 + +#ifdef USES_NW065 + &NWPlugin_065, +#endif // ifdef USES_NW065 + +#ifdef USES_NW066 + &NWPlugin_066, +#endif // ifdef USES_NW066 + +#ifdef USES_NW067 + &NWPlugin_067, +#endif // ifdef USES_NW067 + +#ifdef USES_NW068 + &NWPlugin_068, +#endif // ifdef USES_NW068 + +#ifdef USES_NW069 + &NWPlugin_069, +#endif // ifdef USES_NW069 + +#ifdef USES_NW070 + &NWPlugin_070, +#endif // ifdef USES_NW070 + +#ifdef USES_NW071 + &NWPlugin_071, +#endif // ifdef USES_NW071 + +#ifdef USES_NW072 + &NWPlugin_072, +#endif // ifdef USES_NW072 + +#ifdef USES_NW073 + &NWPlugin_073, +#endif // ifdef USES_NW073 + +#ifdef USES_NW074 + &NWPlugin_074, +#endif // ifdef USES_NW074 + +#ifdef USES_NW075 + &NWPlugin_075, +#endif // ifdef USES_NW075 + +#ifdef USES_NW076 + &NWPlugin_076, +#endif // ifdef USES_NW076 + +#ifdef USES_NW077 + &NWPlugin_077, +#endif // ifdef USES_NW077 + +#ifdef USES_NW078 + &NWPlugin_078, +#endif // ifdef USES_NW078 + +#ifdef USES_NW079 + &NWPlugin_079, +#endif // ifdef USES_NW079 + +#ifdef USES_NW080 + &NWPlugin_080, +#endif // ifdef USES_NW080 + +#ifdef USES_NW081 + &NWPlugin_081, +#endif // ifdef USES_NW081 + +#ifdef USES_NW082 + &NWPlugin_082, +#endif // ifdef USES_NW082 + +#ifdef USES_NW083 + &NWPlugin_083, +#endif // ifdef USES_NW083 + +#ifdef USES_NW084 + &NWPlugin_084, +#endif // ifdef USES_NW084 + +#ifdef USES_NW085 + &NWPlugin_085, +#endif // ifdef USES_NW085 + +#ifdef USES_NW086 + &NWPlugin_086, +#endif // ifdef USES_NW086 + +#ifdef USES_NW087 + &NWPlugin_087, +#endif // ifdef USES_NW087 + +#ifdef USES_NW088 + &NWPlugin_088, +#endif // ifdef USES_NW088 + +#ifdef USES_NW089 + &NWPlugin_089, +#endif // ifdef USES_NW089 + +#ifdef USES_NW090 + &NWPlugin_090, +#endif // ifdef USES_NW090 + +#ifdef USES_NW091 + &NWPlugin_091, +#endif // ifdef USES_NW091 + +#ifdef USES_NW092 + &NWPlugin_092, +#endif // ifdef USES_NW092 + +#ifdef USES_NW093 + &NWPlugin_093, +#endif // ifdef USES_NW093 + +#ifdef USES_NW094 + &NWPlugin_094, +#endif // ifdef USES_NW094 + +#ifdef USES_NW095 + &NWPlugin_095, +#endif // ifdef USES_NW095 + +#ifdef USES_NW096 + &NWPlugin_096, +#endif // ifdef USES_NW096 + +#ifdef USES_NW097 + &NWPlugin_097, +#endif // ifdef USES_NW097 + +#ifdef USES_NW098 + &NWPlugin_098, +#endif // ifdef USES_NW098 + +#ifdef USES_NW099 + &NWPlugin_099, +#endif // ifdef USES_NW099 + +#ifdef USES_NW100 + &NWPlugin_100, +#endif // ifdef USES_NW100 + +#ifdef USES_NW101 + &NWPlugin_101, +#endif // ifdef USES_NW101 + +#ifdef USES_NW102 + &NWPlugin_102, +#endif // ifdef USES_NW102 + +#ifdef USES_NW103 + &NWPlugin_103, +#endif // ifdef USES_NW103 + +#ifdef USES_NW104 + &NWPlugin_104, +#endif // ifdef USES_NW104 + +#ifdef USES_NW105 + &NWPlugin_105, +#endif // ifdef USES_NW105 + +#ifdef USES_NW106 + &NWPlugin_106, +#endif // ifdef USES_NW106 + +#ifdef USES_NW107 + &NWPlugin_107, +#endif // ifdef USES_NW107 + +#ifdef USES_NW108 + &NWPlugin_108, +#endif // ifdef USES_NW108 + +#ifdef USES_NW109 + &NWPlugin_109, +#endif // ifdef USES_NW109 + +#ifdef USES_NW110 + &NWPlugin_110, +#endif // ifdef USES_NW110 + +#ifdef USES_NW111 + &NWPlugin_111, +#endif // ifdef USES_NW111 + +#ifdef USES_NW112 + &NWPlugin_112, +#endif // ifdef USES_NW112 + +#ifdef USES_NW113 + &NWPlugin_113, +#endif // ifdef USES_NW113 + +#ifdef USES_NW114 + &NWPlugin_114, +#endif // ifdef USES_NW114 + +#ifdef USES_NW115 + &NWPlugin_115, +#endif // ifdef USES_NW115 + +#ifdef USES_NW116 + &NWPlugin_116, +#endif // ifdef USES_NW116 + +#ifdef USES_NW117 + &NWPlugin_117, +#endif // ifdef USES_NW117 + +#ifdef USES_NW118 + &NWPlugin_118, +#endif // ifdef USES_NW118 + +#ifdef USES_NW119 + &NWPlugin_119, +#endif // ifdef USES_NW119 + +#ifdef USES_NW120 + &NWPlugin_120, +#endif // ifdef USES_NW120 + +#ifdef USES_NW121 + &NWPlugin_121, +#endif // ifdef USES_NW121 + +#ifdef USES_NW122 + &NWPlugin_122, +#endif // ifdef USES_NW122 + +#ifdef USES_NW123 + &NWPlugin_123, +#endif // ifdef USES_NW123 + +#ifdef USES_NW124 + &NWPlugin_124, +#endif // ifdef USES_NW124 + +#ifdef USES_NW125 + &NWPlugin_125, +#endif // ifdef USES_NW125 + +#ifdef USES_NW126 + &NWPlugin_126, +#endif // ifdef USES_NW126 + +#ifdef USES_NW127 + &NWPlugin_127, +#endif // ifdef USES_NW127 + +#ifdef USES_NW128 + &NWPlugin_128, +#endif // ifdef USES_NW128 + +#ifdef USES_NW129 + &NWPlugin_129, +#endif // ifdef USES_NW129 + +#ifdef USES_NW130 + &NWPlugin_130, +#endif // ifdef USES_NW130 + +#ifdef USES_NW131 + &NWPlugin_131, +#endif // ifdef USES_NW131 + +#ifdef USES_NW132 + &NWPlugin_132, +#endif // ifdef USES_NW132 + +#ifdef USES_NW133 + &NWPlugin_133, +#endif // ifdef USES_NW133 + +#ifdef USES_NW134 + &NWPlugin_134, +#endif // ifdef USES_NW134 + +#ifdef USES_NW135 + &NWPlugin_135, +#endif // ifdef USES_NW135 + +#ifdef USES_NW136 + &NWPlugin_136, +#endif // ifdef USES_NW136 + +#ifdef USES_NW137 + &NWPlugin_137, +#endif // ifdef USES_NW137 + +#ifdef USES_NW138 + &NWPlugin_138, +#endif // ifdef USES_NW138 + +#ifdef USES_NW139 + &NWPlugin_139, +#endif // ifdef USES_NW139 + +#ifdef USES_NW140 + &NWPlugin_140, +#endif // ifdef USES_NW140 + +#ifdef USES_NW141 + &NWPlugin_141, +#endif // ifdef USES_NW141 + +#ifdef USES_NW142 + &NWPlugin_142, +#endif // ifdef USES_NW142 + +#ifdef USES_NW143 + &NWPlugin_143, +#endif // ifdef USES_NW143 + +#ifdef USES_NW144 + &NWPlugin_144, +#endif // ifdef USES_NW144 + +#ifdef USES_NW145 + &NWPlugin_145, +#endif // ifdef USES_NW145 + +#ifdef USES_NW146 + &NWPlugin_146, +#endif // ifdef USES_NW146 + +#ifdef USES_NW147 + &NWPlugin_147, +#endif // ifdef USES_NW147 + +#ifdef USES_NW148 + &NWPlugin_148, +#endif // ifdef USES_NW148 + +#ifdef USES_NW149 + &NWPlugin_149, +#endif // ifdef USES_NW149 + +#ifdef USES_NW150 + &NWPlugin_150, +#endif // ifdef USES_NW150 + +#ifdef USES_NW151 + &NWPlugin_151, +#endif // ifdef USES_NW151 + +#ifdef USES_NW152 + &NWPlugin_152, +#endif // ifdef USES_NW152 + +#ifdef USES_NW153 + &NWPlugin_153, +#endif // ifdef USES_NW153 + +#ifdef USES_NW154 + &NWPlugin_154, +#endif // ifdef USES_NW154 + +#ifdef USES_NW155 + &NWPlugin_155, +#endif // ifdef USES_NW155 + +#ifdef USES_NW156 + &NWPlugin_156, +#endif // ifdef USES_NW156 + +#ifdef USES_NW157 + &NWPlugin_157, +#endif // ifdef USES_NW157 + +#ifdef USES_NW158 + &NWPlugin_158, +#endif // ifdef USES_NW158 + +#ifdef USES_NW159 + &NWPlugin_159, +#endif // ifdef USES_NW159 + +#ifdef USES_NW160 + &NWPlugin_160, +#endif // ifdef USES_NW160 + +#ifdef USES_NW161 + &NWPlugin_161, +#endif // ifdef USES_NW161 + +#ifdef USES_NW162 + &NWPlugin_162, +#endif // ifdef USES_NW162 + +#ifdef USES_NW163 + &NWPlugin_163, +#endif // ifdef USES_NW163 + +#ifdef USES_NW164 + &NWPlugin_164, +#endif // ifdef USES_NW164 + +#ifdef USES_NW165 + &NWPlugin_165, +#endif // ifdef USES_NW165 + +#ifdef USES_NW166 + &NWPlugin_166, +#endif // ifdef USES_NW166 + +#ifdef USES_NW167 + &NWPlugin_167, +#endif // ifdef USES_NW167 + +#ifdef USES_NW168 + &NWPlugin_168, +#endif // ifdef USES_NW168 + +#ifdef USES_NW169 + &NWPlugin_169, +#endif // ifdef USES_NW169 + +#ifdef USES_NW170 + &NWPlugin_170, +#endif // ifdef USES_NW170 + +#ifdef USES_NW171 + &NWPlugin_171, +#endif // ifdef USES_NW171 + +#ifdef USES_NW172 + &NWPlugin_172, +#endif // ifdef USES_NW172 + +#ifdef USES_NW173 + &NWPlugin_173, +#endif // ifdef USES_NW173 + +#ifdef USES_NW174 + &NWPlugin_174, +#endif // ifdef USES_NW174 + +#ifdef USES_NW175 + &NWPlugin_175, +#endif // ifdef USES_NW175 + +#ifdef USES_NW176 + &NWPlugin_176, +#endif // ifdef USES_NW176 + +#ifdef USES_NW177 + &NWPlugin_177, +#endif // ifdef USES_NW177 + +#ifdef USES_NW178 + &NWPlugin_178, +#endif // ifdef USES_NW178 + +#ifdef USES_NW179 + &NWPlugin_179, +#endif // ifdef USES_NW179 + +#ifdef USES_NW180 + &NWPlugin_180, +#endif // ifdef USES_NW180 + +#ifdef USES_NW181 + &NWPlugin_181, +#endif // ifdef USES_NW181 + +#ifdef USES_NW182 + &NWPlugin_182, +#endif // ifdef USES_NW182 + +#ifdef USES_NW183 + &NWPlugin_183, +#endif // ifdef USES_NW183 + +#ifdef USES_NW184 + &NWPlugin_184, +#endif // ifdef USES_NW184 + +#ifdef USES_NW185 + &NWPlugin_185, +#endif // ifdef USES_NW185 + +#ifdef USES_NW186 + &NWPlugin_186, +#endif // ifdef USES_NW186 + +#ifdef USES_NW187 + &NWPlugin_187, +#endif // ifdef USES_NW187 + +#ifdef USES_NW188 + &NWPlugin_188, +#endif // ifdef USES_NW188 + +#ifdef USES_NW189 + &NWPlugin_189, +#endif // ifdef USES_NW189 + +#ifdef USES_NW190 + &NWPlugin_190, +#endif // ifdef USES_NW190 + +#ifdef USES_NW191 + &NWPlugin_191, +#endif // ifdef USES_NW191 + +#ifdef USES_NW192 + &NWPlugin_192, +#endif // ifdef USES_NW192 + +#ifdef USES_NW193 + &NWPlugin_193, +#endif // ifdef USES_NW193 + +#ifdef USES_NW194 + &NWPlugin_194, +#endif // ifdef USES_NW194 + +#ifdef USES_NW195 + &NWPlugin_195, +#endif // ifdef USES_NW195 + +#ifdef USES_NW196 + &NWPlugin_196, +#endif // ifdef USES_NW196 + +#ifdef USES_NW197 + &NWPlugin_197, +#endif // ifdef USES_NW197 + +#ifdef USES_NW198 + &NWPlugin_198, +#endif // ifdef USES_NW198 + +#ifdef USES_NW199 + &NWPlugin_199, +#endif // ifdef USES_NW199 + +#ifdef USES_NW200 + &NWPlugin_200, +#endif // ifdef USES_NW200 + +#ifdef USES_NW201 + &NWPlugin_201, +#endif // ifdef USES_NW201 + +#ifdef USES_NW202 + &NWPlugin_202, +#endif // ifdef USES_NW202 + +#ifdef USES_NW203 + &NWPlugin_203, +#endif // ifdef USES_NW203 + +#ifdef USES_NW204 + &NWPlugin_204, +#endif // ifdef USES_NW204 + +#ifdef USES_NW205 + &NWPlugin_205, +#endif // ifdef USES_NW205 + +#ifdef USES_NW206 + &NWPlugin_206, +#endif // ifdef USES_NW206 + +#ifdef USES_NW207 + &NWPlugin_207, +#endif // ifdef USES_NW207 + +#ifdef USES_NW208 + &NWPlugin_208, +#endif // ifdef USES_NW208 + +#ifdef USES_NW209 + &NWPlugin_209, +#endif // ifdef USES_NW209 + +#ifdef USES_NW210 + &NWPlugin_210, +#endif // ifdef USES_NW210 + +#ifdef USES_NW211 + &NWPlugin_211, +#endif // ifdef USES_NW211 + +#ifdef USES_NW212 + &NWPlugin_212, +#endif // ifdef USES_NW212 + +#ifdef USES_NW213 + &NWPlugin_213, +#endif // ifdef USES_NW213 + +#ifdef USES_NW214 + &NWPlugin_214, +#endif // ifdef USES_NW214 + +#ifdef USES_NW215 + &NWPlugin_215, +#endif // ifdef USES_NW215 + +#ifdef USES_NW216 + &NWPlugin_216, +#endif // ifdef USES_NW216 + +#ifdef USES_NW217 + &NWPlugin_217, +#endif // ifdef USES_NW217 + +#ifdef USES_NW218 + &NWPlugin_218, +#endif // ifdef USES_NW218 + +#ifdef USES_NW219 + &NWPlugin_219, +#endif // ifdef USES_NW219 + +#ifdef USES_NW220 + &NWPlugin_220, +#endif // ifdef USES_NW220 + +#ifdef USES_NW221 + &NWPlugin_221, +#endif // ifdef USES_NW221 + +#ifdef USES_NW222 + &NWPlugin_222, +#endif // ifdef USES_NW222 + +#ifdef USES_NW223 + &NWPlugin_223, +#endif // ifdef USES_NW223 + +#ifdef USES_NW224 + &NWPlugin_224, +#endif // ifdef USES_NW224 + +#ifdef USES_NW225 + &NWPlugin_225, +#endif // ifdef USES_NW225 + +#ifdef USES_NW226 + &NWPlugin_226, +#endif // ifdef USES_NW226 + +#ifdef USES_NW227 + &NWPlugin_227, +#endif // ifdef USES_NW227 + +#ifdef USES_NW228 + &NWPlugin_228, +#endif // ifdef USES_NW228 + +#ifdef USES_NW229 + &NWPlugin_229, +#endif // ifdef USES_NW229 + +#ifdef USES_NW230 + &NWPlugin_230, +#endif // ifdef USES_NW230 + +#ifdef USES_NW231 + &NWPlugin_231, +#endif // ifdef USES_NW231 + +#ifdef USES_NW232 + &NWPlugin_232, +#endif // ifdef USES_NW232 + +#ifdef USES_NW233 + &NWPlugin_233, +#endif // ifdef USES_NW233 + +#ifdef USES_NW234 + &NWPlugin_234, +#endif // ifdef USES_NW234 + +#ifdef USES_NW235 + &NWPlugin_235, +#endif // ifdef USES_NW235 + +#ifdef USES_NW236 + &NWPlugin_236, +#endif // ifdef USES_NW236 + +#ifdef USES_NW237 + &NWPlugin_237, +#endif // ifdef USES_NW237 + +#ifdef USES_NW238 + &NWPlugin_238, +#endif // ifdef USES_NW238 + +#ifdef USES_NW239 + &NWPlugin_239, +#endif // ifdef USES_NW239 + +#ifdef USES_NW240 + &NWPlugin_240, +#endif // ifdef USES_NW240 + +#ifdef USES_NW241 + &NWPlugin_241, +#endif // ifdef USES_NW241 + +#ifdef USES_NW242 + &NWPlugin_242, +#endif // ifdef USES_NW242 + +#ifdef USES_NW243 + &NWPlugin_243, +#endif // ifdef USES_NW243 + +#ifdef USES_NW244 + &NWPlugin_244, +#endif // ifdef USES_NW244 + +#ifdef USES_NW245 + &NWPlugin_245, +#endif // ifdef USES_NW245 + +#ifdef USES_NW246 + &NWPlugin_246, +#endif // ifdef USES_NW246 + +#ifdef USES_NW247 + &NWPlugin_247, +#endif // ifdef USES_NW247 + +#ifdef USES_NW248 + &NWPlugin_248, +#endif // ifdef USES_NW248 + +#ifdef USES_NW249 + &NWPlugin_249, +#endif // ifdef USES_NW249 + +#ifdef USES_NW250 + &NWPlugin_250, +#endif // ifdef USES_NW250 + +#ifdef USES_NW251 + &NWPlugin_251, +#endif // ifdef USES_NW251 + +#ifdef USES_NW252 + &NWPlugin_252, +#endif // ifdef USES_NW252 + +#ifdef USES_NW253 + &NWPlugin_253, +#endif // ifdef USES_NW253 + +#ifdef USES_NW254 + &NWPlugin_254, +#endif // ifdef USES_NW254 + +#ifdef USES_NW255 + &NWPlugin_255, +#endif // ifdef USES_NW255 +}; + + +constexpr size_t NetworkDriverIndex_to_NWPlugin_id_size = sizeof(NetworkDriverIndex_to_NWPlugin_id); + +// Highest NWPlugin ID included in the build +constexpr size_t Highest_NWPlugin_id = NetworkDriverIndex_to_NWPlugin_id_size == + 0 ? 0 : NetworkDriverIndex_to_NWPlugin_id[NetworkDriverIndex_to_NWPlugin_id_size - 1]; + +constexpr size_t NWPlugin_id_to_NetworkDriverIndex_size = Highest_NWPlugin_id + 1; + +// Array filled during init. +// Valid index: 1 ... Highest_NWPlugin_id +// Returns index to the NetworkDriverIndex_to_NWPlugin_id array +networkDriverIndex_t NWPlugin_id_to_NetworkDriverIndex[NWPlugin_id_to_NetworkDriverIndex_size]{}; + + +NetworkDriverStruct NetworkDriverArray[NetworkDriverIndex_to_NWPlugin_id_size + 1]{}; + +NetworkDriverStruct& getNetworkDriverStruct(networkDriverIndex_t networkDriverIndex) +{ + if (networkDriverIndex.value >= NetworkDriverIndex_to_NWPlugin_id_size) { + networkDriverIndex = NetworkDriverIndex_to_NWPlugin_id_size; + } + return NetworkDriverArray[networkDriverIndex.value]; +} + +networkDriverIndex_t do_getNetworkDriverIndex_from_NWPluginID(nwpluginID_t nwpluginID) +{ + if (nwpluginID.value < NWPlugin_id_to_NetworkDriverIndex_size) + { + return static_cast(NWPlugin_id_to_NetworkDriverIndex[nwpluginID.value]); + } + return INVALID_NETWORKDRIVER_INDEX; +} + +nwpluginID_t do_getNWPluginID_from_NetworkDriverIndex(networkDriverIndex_t networkDriverIndex) +{ + if (networkDriverIndex.value < NetworkDriverIndex_to_NWPlugin_id_size) + { + // return static_cast(NetworkDriverIndex_to_NWPlugin_id[networkDriverIndex]); + return nwpluginID_t::toPluginID(pgm_read_byte(NetworkDriverIndex_to_NWPlugin_id + networkDriverIndex.value)); + } + return INVALID_NW_PLUGIN_ID; +} + +bool do_check_validNetworkDriverIndex(networkDriverIndex_t networkDriverIndex) +{ + return networkDriverIndex.value < NetworkDriverIndex_to_NWPlugin_id_size; +} + +nwpluginID_t getHighestIncludedNWPluginID() { return nwpluginID_t::toPluginID(Highest_NWPlugin_id); } + +bool do_NWPluginCall(networkDriverIndex_t networkDriverIndex, NWPlugin::Function Function, EventStruct *event, String& string) +{ + static uint32_t networkIndex_initialized{}; + + if (networkDriverIndex.value < NetworkDriverIndex_to_NWPlugin_id_size) + { + if (Function == NWPlugin::Function::NWPLUGIN_INIT) { + if (bitRead(networkIndex_initialized, event->NetworkIndex)) { + // FIXME TD-er: What to do here? Was already initialized + addLog(LOG_LEVEL_ERROR, strformat(F("Network %d was already initialized"), event->NetworkIndex + 1)); + return false; + } + bitSet(networkIndex_initialized, event->NetworkIndex); + } else if (Function == NWPlugin::Function::NWPLUGIN_EXIT) { + if (!bitRead(networkIndex_initialized, event->NetworkIndex)) { + // FIXME TD-er: What to do here? Was not (yet) initialized + // addLog(LOG_LEVEL_ERROR, strformat(F("Network %d was not (yet) initialized"), event->NetworkIndex + 1)); + return false; + } + bitClear(networkIndex_initialized, event->NetworkIndex); + } + + + START_TIMER; + NWPlugin_ptr_t nwplugin_call = (NWPlugin_ptr_t)pgm_read_ptr(NWPlugin_ptr + networkDriverIndex.value); + const bool res = nwplugin_call(Function, event, string); + STOP_TIMER_NETWORK(networkDriverIndex, Function); + return res; + } + return false; +} + +void NWPluginSetup() +{ + static bool setupDone = false; + + if (setupDone) { return; } + + for (size_t id = 0; id < NWPlugin_id_to_NetworkDriverIndex_size; ++id) + { + NWPlugin_id_to_NetworkDriverIndex[id] = INVALID_NETWORKDRIVER_INDEX; + } + + networkDriverIndex_t networkDriverIndex{}; + + for (; networkDriverIndex.value < NetworkDriverIndex_to_NWPlugin_id_size; ++networkDriverIndex) + { + const nwpluginID_t nwpluginID = getNWPluginID_from_NetworkDriverIndex(networkDriverIndex); + + if (nwpluginID) { + NWPlugin_id_to_NetworkDriverIndex[nwpluginID.value] = networkDriverIndex; + EventStruct TempEvent; + TempEvent.idx = networkDriverIndex.value; + String dummy; + do_NWPluginCall(networkDriverIndex, NWPlugin::Function::NWPLUGIN_DRIVER_ADD, &TempEvent, dummy); + } + } + setupDone = true; +} + +void NWPluginInit() +{ + // Set all not supported nwplugins to disabled. + for (ESPEasy::net::networkIndex_t network = 0; network < NETWORK_MAX; ++network) { + if (!supportedNWPluginID(Settings.getNWPluginID_for_network(network))) { + Settings.setNetworkEnabled(network, false); + } + } + NWPluginCall(NWPlugin::Function::NWPLUGIN_INIT_ALL, 0); +} + +void NWPlugin_Exit_Init(networkIndex_t networkIndex) +{ + const networkDriverIndex_t NetworkDriverIndex = + getNetworkDriverIndex_from_NetworkIndex(networkIndex); + + if (validNetworkDriverIndex(NetworkDriverIndex)) { + struct EventStruct TempEvent; + TempEvent.NetworkIndex = networkIndex; + String dummy; + + // May need to call init later, so make sure exit is called first + NWPluginCall(NWPlugin::Function::NWPLUGIN_EXIT, &TempEvent, dummy); + + if (Settings.getNetworkEnabled(networkIndex)) { + NWPluginCall(NWPlugin::Function::NWPLUGIN_INIT, &TempEvent, dummy); + } + } +} + +} // namespace net +} // namespace ESPEasy diff --git a/src/ESPEasy/net/Helpers/_NWPlugin_init.h b/src/ESPEasy/net/Helpers/_NWPlugin_init.h new file mode 100644 index 000000000..3683220e1 --- /dev/null +++ b/src/ESPEasy/net/Helpers/_NWPlugin_init.h @@ -0,0 +1,1067 @@ +#pragma once + +#include "../../../ESPEasy_common.h" + +#include "../../../src/DataStructs/ESPEasy_EventStruct.h" +#include "../DataStructs/NetworkDriverStruct.h" +#include "../../../src/DataTypes/ESPEasy_plugin_functions.h" +#include "../DataTypes/NWPluginID.h" +#include "../DataTypes/NetworkDriverIndex.h" + + +namespace ESPEasy { +namespace net { + +networkDriverIndex_t do_getNetworkDriverIndex_from_NWPluginID(nwpluginID_t pluginID); +nwpluginID_t do_getNWPluginID_from_NetworkDriverIndex(networkDriverIndex_t networkDriverIndex); +bool do_check_validNetworkDriverIndex(networkDriverIndex_t networkDriverIndex); + +nwpluginID_t getHighestIncludedNWPluginID(); + +NetworkDriverStruct& getNetworkDriverStruct(networkDriverIndex_t networkDriverIndex); + +// Should only be called from NWPluginCall, or maybe for very special occasions +bool do_NWPluginCall(networkDriverIndex_t networkDriverIndex, NWPlugin::Function Function, EventStruct *event, String& string); + + +void NWPluginSetup(); +void NWPluginInit(); + +// Start or stop network as how it is set in the Settings +void NWPlugin_Exit_Init(networkIndex_t networkIndex); + + +// Macro to forward declare the NWPlugin_NNN functions. +// N.B. Some controllers also have a do_process_cNNN_delay_queue function. +// Forward declaration of these is done in ControllerQueue/ControllerDelayHandlerStruct.h +// +// Uncrustify must not be used on macros, so turn it off. +// *INDENT-OFF* +#define ADDNWPLUGIN_H(NNN) bool NWPlugin_##NNN(NWPlugin::Function function, EventStruct *event, String& string); +// Uncrustify must not be used on macros, but we're now done, so turn Uncrustify on again. +// *INDENT-ON* + +#ifdef USES_NW001 + ADDNWPLUGIN_H(001) +#endif + +#ifdef USES_NW002 + ADDNWPLUGIN_H(002) +#endif + +#ifdef USES_NW003 + ADDNWPLUGIN_H(003) +#endif + +#ifdef USES_NW004 + ADDNWPLUGIN_H(004) +#endif + +#ifdef USES_NW005 + ADDNWPLUGIN_H(005) +#endif + +#ifdef USES_NW006 + ADDNWPLUGIN_H(006) +#endif + +#ifdef USES_NW007 + ADDNWPLUGIN_H(007) +#endif + +#ifdef USES_NW008 + ADDNWPLUGIN_H(008) +#endif + +#ifdef USES_NW009 + ADDNWPLUGIN_H(009) +#endif + +#ifdef USES_NW010 + ADDNWPLUGIN_H(010) +#endif + +#ifdef USES_NW011 + ADDNWPLUGIN_H(011) +#endif + +#ifdef USES_NW012 + ADDNWPLUGIN_H(012) +#endif + +#ifdef USES_NW013 + ADDNWPLUGIN_H(013) +#endif + +#ifdef USES_NW014 + ADDNWPLUGIN_H(014) +#endif + +#ifdef USES_NW015 + ADDNWPLUGIN_H(015) +#endif + +#ifdef USES_NW016 + ADDNWPLUGIN_H(016) +#endif + +#ifdef USES_NW017 + ADDNWPLUGIN_H(017) +#endif + +#ifdef USES_NW018 + ADDNWPLUGIN_H(018) +#endif + +#ifdef USES_NW019 + ADDNWPLUGIN_H(019) +#endif + +#ifdef USES_NW020 + ADDNWPLUGIN_H(020) +#endif + +#ifdef USES_NW021 + ADDNWPLUGIN_H(021) +#endif + +#ifdef USES_NW022 + ADDNWPLUGIN_H(022) +#endif + +#ifdef USES_NW023 + ADDNWPLUGIN_H(023) +#endif + +#ifdef USES_NW024 + ADDNWPLUGIN_H(024) +#endif + +#ifdef USES_NW025 + ADDNWPLUGIN_H(025) +#endif + +#ifdef USES_NW026 + ADDNWPLUGIN_H(026) +#endif + +#ifdef USES_NW027 + ADDNWPLUGIN_H(027) +#endif + +#ifdef USES_NW028 + ADDNWPLUGIN_H(028) +#endif + +#ifdef USES_NW029 + ADDNWPLUGIN_H(029) +#endif + +#ifdef USES_NW030 + ADDNWPLUGIN_H(030) +#endif + +#ifdef USES_NW031 + ADDNWPLUGIN_H(031) +#endif + +#ifdef USES_NW032 + ADDNWPLUGIN_H(032) +#endif + +#ifdef USES_NW033 + ADDNWPLUGIN_H(033) +#endif + +#ifdef USES_NW034 + ADDNWPLUGIN_H(034) +#endif + +#ifdef USES_NW035 + ADDNWPLUGIN_H(035) +#endif + +#ifdef USES_NW036 + ADDNWPLUGIN_H(036) +#endif + +#ifdef USES_NW037 + ADDNWPLUGIN_H(037) +#endif + +#ifdef USES_NW038 + ADDNWPLUGIN_H(038) +#endif + +#ifdef USES_NW039 + ADDNWPLUGIN_H(039) +#endif + +#ifdef USES_NW040 + ADDNWPLUGIN_H(040) +#endif + +#ifdef USES_NW041 + ADDNWPLUGIN_H(041) +#endif + +#ifdef USES_NW042 + ADDNWPLUGIN_H(042) +#endif + +#ifdef USES_NW043 + ADDNWPLUGIN_H(043) +#endif + +#ifdef USES_NW044 + ADDNWPLUGIN_H(044) +#endif + +#ifdef USES_NW045 + ADDNWPLUGIN_H(045) +#endif + +#ifdef USES_NW046 + ADDNWPLUGIN_H(046) +#endif + +#ifdef USES_NW047 + ADDNWPLUGIN_H(047) +#endif + +#ifdef USES_NW048 + ADDNWPLUGIN_H(048) +#endif + +#ifdef USES_NW049 + ADDNWPLUGIN_H(049) +#endif + +#ifdef USES_NW050 + ADDNWPLUGIN_H(050) +#endif + +#ifdef USES_NW051 + ADDNWPLUGIN_H(051) +#endif + +#ifdef USES_NW052 + ADDNWPLUGIN_H(052) +#endif + +#ifdef USES_NW053 + ADDNWPLUGIN_H(053) +#endif + +#ifdef USES_NW054 + ADDNWPLUGIN_H(054) +#endif + +#ifdef USES_NW055 + ADDNWPLUGIN_H(055) +#endif + +#ifdef USES_NW056 + ADDNWPLUGIN_H(056) +#endif + +#ifdef USES_NW057 + ADDNWPLUGIN_H(057) +#endif + +#ifdef USES_NW058 + ADDNWPLUGIN_H(058) +#endif + +#ifdef USES_NW059 + ADDNWPLUGIN_H(059) +#endif + +#ifdef USES_NW060 + ADDNWPLUGIN_H(060) +#endif + +#ifdef USES_NW061 + ADDNWPLUGIN_H(061) +#endif + +#ifdef USES_NW062 + ADDNWPLUGIN_H(062) +#endif + +#ifdef USES_NW063 + ADDNWPLUGIN_H(063) +#endif + +#ifdef USES_NW064 + ADDNWPLUGIN_H(064) +#endif + +#ifdef USES_NW065 + ADDNWPLUGIN_H(065) +#endif + +#ifdef USES_NW066 + ADDNWPLUGIN_H(066) +#endif + +#ifdef USES_NW067 + ADDNWPLUGIN_H(067) +#endif + +#ifdef USES_NW068 + ADDNWPLUGIN_H(068) +#endif + +#ifdef USES_NW069 + ADDNWPLUGIN_H(069) +#endif + +#ifdef USES_NW070 + ADDNWPLUGIN_H(070) +#endif + +#ifdef USES_NW071 + ADDNWPLUGIN_H(071) +#endif + +#ifdef USES_NW072 + ADDNWPLUGIN_H(072) +#endif + +#ifdef USES_NW073 + ADDNWPLUGIN_H(073) +#endif + +#ifdef USES_NW074 + ADDNWPLUGIN_H(074) +#endif + +#ifdef USES_NW075 + ADDNWPLUGIN_H(075) +#endif + +#ifdef USES_NW076 + ADDNWPLUGIN_H(076) +#endif + +#ifdef USES_NW077 + ADDNWPLUGIN_H(077) +#endif + +#ifdef USES_NW078 + ADDNWPLUGIN_H(078) +#endif + +#ifdef USES_NW079 + ADDNWPLUGIN_H(079) +#endif + +#ifdef USES_NW080 + ADDNWPLUGIN_H(080) +#endif + +#ifdef USES_NW081 + ADDNWPLUGIN_H(081) +#endif + +#ifdef USES_NW082 + ADDNWPLUGIN_H(082) +#endif + +#ifdef USES_NW083 + ADDNWPLUGIN_H(083) +#endif + +#ifdef USES_NW084 + ADDNWPLUGIN_H(084) +#endif + +#ifdef USES_NW085 + ADDNWPLUGIN_H(085) +#endif + +#ifdef USES_NW086 + ADDNWPLUGIN_H(086) +#endif + +#ifdef USES_NW087 + ADDNWPLUGIN_H(087) +#endif + +#ifdef USES_NW088 + ADDNWPLUGIN_H(088) +#endif + +#ifdef USES_NW089 + ADDNWPLUGIN_H(089) +#endif + +#ifdef USES_NW090 + ADDNWPLUGIN_H(090) +#endif + +#ifdef USES_NW091 + ADDNWPLUGIN_H(091) +#endif + +#ifdef USES_NW092 + ADDNWPLUGIN_H(092) +#endif + +#ifdef USES_NW093 + ADDNWPLUGIN_H(093) +#endif + +#ifdef USES_NW094 + ADDNWPLUGIN_H(094) +#endif + +#ifdef USES_NW095 + ADDNWPLUGIN_H(095) +#endif + +#ifdef USES_NW096 + ADDNWPLUGIN_H(096) +#endif + +#ifdef USES_NW097 + ADDNWPLUGIN_H(097) +#endif + +#ifdef USES_NW098 + ADDNWPLUGIN_H(098) +#endif + +#ifdef USES_NW099 + ADDNWPLUGIN_H(099) +#endif + +#ifdef USES_NW100 + ADDNWPLUGIN_H(100) +#endif + +#ifdef USES_NW101 + ADDNWPLUGIN_H(101) +#endif + +#ifdef USES_NW102 + ADDNWPLUGIN_H(102) +#endif + +#ifdef USES_NW103 + ADDNWPLUGIN_H(103) +#endif + +#ifdef USES_NW104 + ADDNWPLUGIN_H(104) +#endif + +#ifdef USES_NW105 + ADDNWPLUGIN_H(105) +#endif + +#ifdef USES_NW106 + ADDNWPLUGIN_H(106) +#endif + +#ifdef USES_NW107 + ADDNWPLUGIN_H(107) +#endif + +#ifdef USES_NW108 + ADDNWPLUGIN_H(108) +#endif + +#ifdef USES_NW109 + ADDNWPLUGIN_H(109) +#endif + +#ifdef USES_NW110 + ADDNWPLUGIN_H(110) +#endif + +#ifdef USES_NW111 + ADDNWPLUGIN_H(111) +#endif + +#ifdef USES_NW112 + ADDNWPLUGIN_H(112) +#endif + +#ifdef USES_NW113 + ADDNWPLUGIN_H(113) +#endif + +#ifdef USES_NW114 + ADDNWPLUGIN_H(114) +#endif + +#ifdef USES_NW115 + ADDNWPLUGIN_H(115) +#endif + +#ifdef USES_NW116 + ADDNWPLUGIN_H(116) +#endif + +#ifdef USES_NW117 + ADDNWPLUGIN_H(117) +#endif + +#ifdef USES_NW118 + ADDNWPLUGIN_H(118) +#endif + +#ifdef USES_NW119 + ADDNWPLUGIN_H(119) +#endif + +#ifdef USES_NW120 + ADDNWPLUGIN_H(120) +#endif + +#ifdef USES_NW121 + ADDNWPLUGIN_H(121) +#endif + +#ifdef USES_NW122 + ADDNWPLUGIN_H(122) +#endif + +#ifdef USES_NW123 + ADDNWPLUGIN_H(123) +#endif + +#ifdef USES_NW124 + ADDNWPLUGIN_H(124) +#endif + +#ifdef USES_NW125 + ADDNWPLUGIN_H(125) +#endif + +#ifdef USES_NW126 + ADDNWPLUGIN_H(126) +#endif + +#ifdef USES_NW127 + ADDNWPLUGIN_H(127) +#endif + +#ifdef USES_NW128 + ADDNWPLUGIN_H(128) +#endif + +#ifdef USES_NW129 + ADDNWPLUGIN_H(129) +#endif + +#ifdef USES_NW130 + ADDNWPLUGIN_H(130) +#endif + +#ifdef USES_NW131 + ADDNWPLUGIN_H(131) +#endif + +#ifdef USES_NW132 + ADDNWPLUGIN_H(132) +#endif + +#ifdef USES_NW133 + ADDNWPLUGIN_H(133) +#endif + +#ifdef USES_NW134 + ADDNWPLUGIN_H(134) +#endif + +#ifdef USES_NW135 + ADDNWPLUGIN_H(135) +#endif + +#ifdef USES_NW136 + ADDNWPLUGIN_H(136) +#endif + +#ifdef USES_NW137 + ADDNWPLUGIN_H(137) +#endif + +#ifdef USES_NW138 + ADDNWPLUGIN_H(138) +#endif + +#ifdef USES_NW139 + ADDNWPLUGIN_H(139) +#endif + +#ifdef USES_NW140 + ADDNWPLUGIN_H(140) +#endif + +#ifdef USES_NW141 + ADDNWPLUGIN_H(141) +#endif + +#ifdef USES_NW142 + ADDNWPLUGIN_H(142) +#endif + +#ifdef USES_NW143 + ADDNWPLUGIN_H(143) +#endif + +#ifdef USES_NW144 + ADDNWPLUGIN_H(144) +#endif + +#ifdef USES_NW145 + ADDNWPLUGIN_H(145) +#endif + +#ifdef USES_NW146 + ADDNWPLUGIN_H(146) +#endif + +#ifdef USES_NW147 + ADDNWPLUGIN_H(147) +#endif + +#ifdef USES_NW148 + ADDNWPLUGIN_H(148) +#endif + +#ifdef USES_NW149 + ADDNWPLUGIN_H(149) +#endif + +#ifdef USES_NW150 + ADDNWPLUGIN_H(150) +#endif + +#ifdef USES_NW151 + ADDNWPLUGIN_H(151) +#endif + +#ifdef USES_NW152 + ADDNWPLUGIN_H(152) +#endif + +#ifdef USES_NW153 + ADDNWPLUGIN_H(153) +#endif + +#ifdef USES_NW154 + ADDNWPLUGIN_H(154) +#endif + +#ifdef USES_NW155 + ADDNWPLUGIN_H(155) +#endif + +#ifdef USES_NW156 + ADDNWPLUGIN_H(156) +#endif + +#ifdef USES_NW157 + ADDNWPLUGIN_H(157) +#endif + +#ifdef USES_NW158 + ADDNWPLUGIN_H(158) +#endif + +#ifdef USES_NW159 + ADDNWPLUGIN_H(159) +#endif + +#ifdef USES_NW160 + ADDNWPLUGIN_H(160) +#endif + +#ifdef USES_NW161 + ADDNWPLUGIN_H(161) +#endif + +#ifdef USES_NW162 + ADDNWPLUGIN_H(162) +#endif + +#ifdef USES_NW163 + ADDNWPLUGIN_H(163) +#endif + +#ifdef USES_NW164 + ADDNWPLUGIN_H(164) +#endif + +#ifdef USES_NW165 + ADDNWPLUGIN_H(165) +#endif + +#ifdef USES_NW166 + ADDNWPLUGIN_H(166) +#endif + +#ifdef USES_NW167 + ADDNWPLUGIN_H(167) +#endif + +#ifdef USES_NW168 + ADDNWPLUGIN_H(168) +#endif + +#ifdef USES_NW169 + ADDNWPLUGIN_H(169) +#endif + +#ifdef USES_NW170 + ADDNWPLUGIN_H(170) +#endif + +#ifdef USES_NW171 + ADDNWPLUGIN_H(171) +#endif + +#ifdef USES_NW172 + ADDNWPLUGIN_H(172) +#endif + +#ifdef USES_NW173 + ADDNWPLUGIN_H(173) +#endif + +#ifdef USES_NW174 + ADDNWPLUGIN_H(174) +#endif + +#ifdef USES_NW175 + ADDNWPLUGIN_H(175) +#endif + +#ifdef USES_NW176 + ADDNWPLUGIN_H(176) +#endif + +#ifdef USES_NW177 + ADDNWPLUGIN_H(177) +#endif + +#ifdef USES_NW178 + ADDNWPLUGIN_H(178) +#endif + +#ifdef USES_NW179 + ADDNWPLUGIN_H(179) +#endif + +#ifdef USES_NW180 + ADDNWPLUGIN_H(180) +#endif + +#ifdef USES_NW181 + ADDNWPLUGIN_H(181) +#endif + +#ifdef USES_NW182 + ADDNWPLUGIN_H(182) +#endif + +#ifdef USES_NW183 + ADDNWPLUGIN_H(183) +#endif + +#ifdef USES_NW184 + ADDNWPLUGIN_H(184) +#endif + +#ifdef USES_NW185 + ADDNWPLUGIN_H(185) +#endif + +#ifdef USES_NW186 + ADDNWPLUGIN_H(186) +#endif + +#ifdef USES_NW187 + ADDNWPLUGIN_H(187) +#endif + +#ifdef USES_NW188 + ADDNWPLUGIN_H(188) +#endif + +#ifdef USES_NW189 + ADDNWPLUGIN_H(189) +#endif + +#ifdef USES_NW190 + ADDNWPLUGIN_H(190) +#endif + +#ifdef USES_NW191 + ADDNWPLUGIN_H(191) +#endif + +#ifdef USES_NW192 + ADDNWPLUGIN_H(192) +#endif + +#ifdef USES_NW193 + ADDNWPLUGIN_H(193) +#endif + +#ifdef USES_NW194 + ADDNWPLUGIN_H(194) +#endif + +#ifdef USES_NW195 + ADDNWPLUGIN_H(195) +#endif + +#ifdef USES_NW196 + ADDNWPLUGIN_H(196) +#endif + +#ifdef USES_NW197 + ADDNWPLUGIN_H(197) +#endif + +#ifdef USES_NW198 + ADDNWPLUGIN_H(198) +#endif + +#ifdef USES_NW199 + ADDNWPLUGIN_H(199) +#endif + +#ifdef USES_NW200 + ADDNWPLUGIN_H(200) +#endif + +#ifdef USES_NW201 + ADDNWPLUGIN_H(201) +#endif + +#ifdef USES_NW202 + ADDNWPLUGIN_H(202) +#endif + +#ifdef USES_NW203 + ADDNWPLUGIN_H(203) +#endif + +#ifdef USES_NW204 + ADDNWPLUGIN_H(204) +#endif + +#ifdef USES_NW205 + ADDNWPLUGIN_H(205) +#endif + +#ifdef USES_NW206 + ADDNWPLUGIN_H(206) +#endif + +#ifdef USES_NW207 + ADDNWPLUGIN_H(207) +#endif + +#ifdef USES_NW208 + ADDNWPLUGIN_H(208) +#endif + +#ifdef USES_NW209 + ADDNWPLUGIN_H(209) +#endif + +#ifdef USES_NW210 + ADDNWPLUGIN_H(210) +#endif + +#ifdef USES_NW211 + ADDNWPLUGIN_H(211) +#endif + +#ifdef USES_NW212 + ADDNWPLUGIN_H(212) +#endif + +#ifdef USES_NW213 + ADDNWPLUGIN_H(213) +#endif + +#ifdef USES_NW214 + ADDNWPLUGIN_H(214) +#endif + +#ifdef USES_NW215 + ADDNWPLUGIN_H(215) +#endif + +#ifdef USES_NW216 + ADDNWPLUGIN_H(216) +#endif + +#ifdef USES_NW217 + ADDNWPLUGIN_H(217) +#endif + +#ifdef USES_NW218 + ADDNWPLUGIN_H(218) +#endif + +#ifdef USES_NW219 + ADDNWPLUGIN_H(219) +#endif + +#ifdef USES_NW220 + ADDNWPLUGIN_H(220) +#endif + +#ifdef USES_NW221 + ADDNWPLUGIN_H(221) +#endif + +#ifdef USES_NW222 + ADDNWPLUGIN_H(222) +#endif + +#ifdef USES_NW223 + ADDNWPLUGIN_H(223) +#endif + +#ifdef USES_NW224 + ADDNWPLUGIN_H(224) +#endif + +#ifdef USES_NW225 + ADDNWPLUGIN_H(225) +#endif + +#ifdef USES_NW226 + ADDNWPLUGIN_H(226) +#endif + +#ifdef USES_NW227 + ADDNWPLUGIN_H(227) +#endif + +#ifdef USES_NW228 + ADDNWPLUGIN_H(228) +#endif + +#ifdef USES_NW229 + ADDNWPLUGIN_H(229) +#endif + +#ifdef USES_NW230 + ADDNWPLUGIN_H(230) +#endif + +#ifdef USES_NW231 + ADDNWPLUGIN_H(231) +#endif + +#ifdef USES_NW232 + ADDNWPLUGIN_H(232) +#endif + +#ifdef USES_NW233 + ADDNWPLUGIN_H(233) +#endif + +#ifdef USES_NW234 + ADDNWPLUGIN_H(234) +#endif + +#ifdef USES_NW235 + ADDNWPLUGIN_H(235) +#endif + +#ifdef USES_NW236 + ADDNWPLUGIN_H(236) +#endif + +#ifdef USES_NW237 + ADDNWPLUGIN_H(237) +#endif + +#ifdef USES_NW238 + ADDNWPLUGIN_H(238) +#endif + +#ifdef USES_NW239 + ADDNWPLUGIN_H(239) +#endif + +#ifdef USES_NW240 + ADDNWPLUGIN_H(240) +#endif + +#ifdef USES_NW241 + ADDNWPLUGIN_H(241) +#endif + +#ifdef USES_NW242 + ADDNWPLUGIN_H(242) +#endif + +#ifdef USES_NW243 + ADDNWPLUGIN_H(243) +#endif + +#ifdef USES_NW244 + ADDNWPLUGIN_H(244) +#endif + +#ifdef USES_NW245 + ADDNWPLUGIN_H(245) +#endif + +#ifdef USES_NW246 + ADDNWPLUGIN_H(246) +#endif + +#ifdef USES_NW247 + ADDNWPLUGIN_H(247) +#endif + +#ifdef USES_NW248 + ADDNWPLUGIN_H(248) +#endif + +#ifdef USES_NW249 + ADDNWPLUGIN_H(249) +#endif + +#ifdef USES_NW250 + ADDNWPLUGIN_H(250) +#endif + +#ifdef USES_NW251 + ADDNWPLUGIN_H(251) +#endif + +#ifdef USES_NW252 + ADDNWPLUGIN_H(252) +#endif + +#ifdef USES_NW253 + ADDNWPLUGIN_H(253) +#endif + +#ifdef USES_NW254 + ADDNWPLUGIN_H(254) +#endif + +#ifdef USES_NW255 + ADDNWPLUGIN_H(255) +#endif + +#undef ADDNWPLUGIN_H + +} // namespace net +} // namespace ESPEasy diff --git a/src/src/DataStructs/tcp_cleanup.cpp b/src/ESPEasy/net/Helpers/tcp_cleanup_ESP8266.cpp similarity index 84% rename from src/src/DataStructs/tcp_cleanup.cpp rename to src/ESPEasy/net/Helpers/tcp_cleanup_ESP8266.cpp index 2f5a85d17..235d1cf2f 100644 --- a/src/src/DataStructs/tcp_cleanup.cpp +++ b/src/ESPEasy/net/Helpers/tcp_cleanup_ESP8266.cpp @@ -1,4 +1,4 @@ -#include "../DataStructs/tcp_cleanup.h" +#include "../Helpers/tcp_cleanup_ESP8266.h" diff --git a/src/src/DataStructs/tcp_cleanup.h b/src/ESPEasy/net/Helpers/tcp_cleanup_ESP8266.h similarity index 78% rename from src/src/DataStructs/tcp_cleanup.h rename to src/ESPEasy/net/Helpers/tcp_cleanup_ESP8266.h index a069e695b..8bfc99842 100644 --- a/src/src/DataStructs/tcp_cleanup.h +++ b/src/ESPEasy/net/Helpers/tcp_cleanup_ESP8266.h @@ -1,8 +1,7 @@ -#ifndef DATASTRUCTS_TCP_CLEANUP_H -#define DATASTRUCTS_TCP_CLEANUP_H +#pragma once -#include "../../ESPEasy_common.h" +#include "../../../ESPEasy_common.h" // clean up tcp connections that are in TIME_WAIT status, to conserve memory // In future versions of WiFiClient it should be possible to call abort(), but @@ -20,7 +19,3 @@ extern "C" void tcp_abort (struct tcp_pcb* pcb); void tcpCleanup(); #endif - - - -#endif // DATASTRUCTS_TCP_CLEANUP_H diff --git a/src/ESPEasy/net/NWPluginStructs/NW001_data_struct_WiFi_STA.cpp b/src/ESPEasy/net/NWPluginStructs/NW001_data_struct_WiFi_STA.cpp new file mode 100644 index 000000000..0ae4cb4fe --- /dev/null +++ b/src/ESPEasy/net/NWPluginStructs/NW001_data_struct_WiFi_STA.cpp @@ -0,0 +1,225 @@ +#include "../NWPluginStructs/NW001_data_struct_WiFi_STA.h" + +#ifdef USES_NW001 + +# include "../../../src/Globals/Settings.h" + +# include "../../../src/Helpers/ESPEasy_time_calc.h" +# include "../../../src/Helpers/LongTermOnOffTimer.h" +# include "../../../src/Helpers/StringConverter.h" +# include "../../../src/WebServer/HTML_wrappers.h" +# include "../../../src/WebServer/Markup.h" + +# include "../wifi/ESPEasyWifi.h" + +# define NW_PLUGIN_ID 1 +# ifdef ESP32 +# define NW_PLUGIN_INTERFACE WiFi.STA +# endif + +# define NW001_RSSI_STATS_INDEX 0 +# define NW001_TX_PWR_STATS_INDEX 1 + + +namespace ESPEasy { +namespace net { +namespace wifi { + + +NW001_data_struct_WiFi_STA::NW001_data_struct_WiFi_STA(networkIndex_t networkIndex) + : NWPluginData_base( + nwpluginID_t(NW_PLUGIN_ID) + , networkIndex +# ifdef ESP32 + , &NW_PLUGIN_INTERFACE +# endif + ), + _WiFiEventHandler(networkIndex) +{} + +NW001_data_struct_WiFi_STA::~NW001_data_struct_WiFi_STA() +{} + +void NW001_data_struct_WiFi_STA::webform_load(EventStruct *event) +{ + + +} + +void NW001_data_struct_WiFi_STA::webform_save(EventStruct *event) {} + +bool NW001_data_struct_WiFi_STA::webform_getPort(KeyValueWriter *writer) { return true; } + +bool NW001_data_struct_WiFi_STA::init(EventStruct *event) +{ + /* + // Taken from ESPEasy_setup() + // Might no longer be needed as we now have a proper WiFi state machine. + + # ifdef ESP32 + + // FIXME TD-er: Disabled for now, as this may not return and thus block the ESP forever. + // See: https://github.com/espressif/esp-idf/issues/15862 + // check_and_update_WiFi_Calibration(); + # endif // ifdef ESP32 + + ESPEasy::net::wifi::WiFi_AP_Candidates.clearCache(); + ESPEasy::net::wifi::WiFi_AP_Candidates.load_knownCredentials(); + ESPEasy::net::wifi::setSTA(true); + + if (!ESPEasy::net::wifi::WiFi_AP_Candidates.hasCandidateCredentials()) { + WiFiEventData.wifiSetup = true; + RTC.clearLastWiFi(); // Must scan all channels + // Wait until scan has finished to make sure as many as possible are found + // We're still in the setup phase, so nothing else is taking resources of the ESP. + ESPEasy::net::wifi::WifiScan(false); + WiFiEventData.lastScanMoment.clear(); + } + + + + // Always perform WiFi scan + // It appears reconnecting from RTC may take just as long to be able to send first packet as performing a scan first and then + #connect. + // Perhaps the WiFi radio needs some time to stabilize first? + if (!ESPEasy::net::wifi::WiFi_AP_Candidates.hasCandidates()) { + ESPEasy::net::wifi::WifiScan(false, RTC.lastWiFiChannel); + } + ESPEasy::net::wifi::WiFi_AP_Candidates.clearCache(); + ESPEasy::net::wifi::processScanDone(); + ESPEasy::net::wifi::WiFi_AP_Candidates.load_knownCredentials(); + + if (!ESPEasy::net::wifi::WiFi_AP_Candidates.hasCandidates()) { + # ifndef BUILD_MINIMAL_OTA + addLog(LOG_LEVEL_INFO, F("Setup: Scan all channels")); + # endif + ESPEasy::net::wifi::WifiScan(false); + } + + // ESPEasy::net::wifi::setWifiMode(WIFI_OFF); + */ + + ESPEasy::net::wifi::initWiFi(); + return true; +} + +bool NW001_data_struct_WiFi_STA::exit(EventStruct *event) { + ESPEasy::net::wifi::exitWiFi(); + return true; +} + +NWPluginData_static_runtime * NW001_data_struct_WiFi_STA::getNWPluginData_static_runtime() { + return _WiFiEventHandler.getNWPluginData_static_runtime(); +} + +const __FlashStringHelper * NW001_data_struct_WiFi_STA::getWiFi_encryptionType() const +{ + return _WiFiEventHandler.getWiFi_encryptionType(); +} + +WiFiDisconnectReason NW001_data_struct_WiFi_STA::getWiFi_disconnectReason() const +{ + return _WiFiEventHandler.getLastDisconnectReason(); +} + +# ifdef ESP32 + +bool NW001_data_struct_WiFi_STA::handle_priority_route_changed() +{ + if (NW_PLUGIN_INTERFACE.isDefault()) { + if (NWPlugin::forceDHCP_request(&NW_PLUGIN_INTERFACE)) { return true; } + return _WiFiEventHandler.restore_dns_from_cache(); + } + return false; +} + +# endif // ifdef ESP32 + +# if FEATURE_NETWORK_STATS + +bool NW001_data_struct_WiFi_STA::initPluginStats() +{ + networkStatsVarIndex_t networkStatsVarIndex{}; + PluginStats_Config_t displayConfig; + + displayConfig.setAxisPosition(PluginStats_Config_t::AxisPosition::Left); + displayConfig.setEnabled(true); + + displayConfig.setAxisIndex(networkStatsVarIndex); + NWPluginData_base::initPluginStats( + networkStatsVarIndex, + F("RSSI"), + 1, + NAN, + displayConfig); +# if FEATURE_SET_WIFI_TX_PWR + ++networkStatsVarIndex; + displayConfig.setAxisIndex(networkStatsVarIndex); + displayConfig.setHidden(true); + NWPluginData_base::initPluginStats( + networkStatsVarIndex, + F("TX Power"), + 1, + NAN, + displayConfig); +# endif // if FEATURE_SET_WIFI_TX_PWR +# if FEATURE_NETWORK_TRAFFIC_COUNT + initPluginStats_trafficCount(++networkStatsVarIndex, true); // TX + initPluginStats_trafficCount(++networkStatsVarIndex, false); // RX +# endif // if FEATURE_NETWORK_TRAFFIC_COUNT + return true; +} + +bool NW001_data_struct_WiFi_STA::record_stats() +{ + if (_plugin_stats_array != nullptr) { + EventStruct tmpEvent; + size_t valueCount{}; + const int rssi = +# ifdef ESP32 + WiFi.STA.RSSI(); +# else + WiFi.RSSI(); +# endif // ifdef ESP32 + + tmpEvent.ParfN[valueCount++] = rssi < 0 ? rssi : NAN; +# if FEATURE_SET_WIFI_TX_PWR + tmpEvent.ParfN[valueCount++] = ESPEasy::net::wifi::GetWiFiTXpower(); +# endif + + bool trackPeaks = true; + bool onlyUpdateTimestampWhenSame = true; + return pushStatsValues(&tmpEvent, valueCount, trackPeaks, onlyUpdateTimestampWhenSame); + } + return false; +} + +bool NW001_data_struct_WiFi_STA::webformLoad_show_stats(struct EventStruct *event) const +{ + if (_plugin_stats_array != nullptr) { +# if FEATURE_SET_WIFI_TX_PWR +# if FEATURE_CHART_JS + addRowColspan(2); + + plot_ChartJS_scatter( + NW001_RSSI_STATS_INDEX, + NW001_TX_PWR_STATS_INDEX, + F("rssitxpwrscatter"), + { F("RSSI/TX pwr Scatter Plot") }, + { F("rssi/tx_pwr"), F("rgb(255, 99, 132)") }); + addHtml(F("")); +# endif // if FEATURE_CHART_JS +# endif // if FEATURE_SET_WIFI_TX_PWR + return _plugin_stats_array->webformLoad_show_stats(event); + } + return false; +} + +# endif // if FEATURE_NETWORK_STATS + + +} // namespace wifi +} // namespace net +} // namespace ESPEasy + +#endif // ifdef USES_NW001 diff --git a/src/ESPEasy/net/NWPluginStructs/NW001_data_struct_WiFi_STA.h b/src/ESPEasy/net/NWPluginStructs/NW001_data_struct_WiFi_STA.h new file mode 100644 index 000000000..2d9a6c3ec --- /dev/null +++ b/src/ESPEasy/net/NWPluginStructs/NW001_data_struct_WiFi_STA.h @@ -0,0 +1,60 @@ +#pragma once + +#include "../_NWPlugin_Helper.h" +#ifdef USES_NW001 + +# ifdef ESP8266 +# include "../wifi/ESPEasyWiFi_STA_Event_ESP8266.h" +# endif +# ifdef ESP32 +# include "../wifi/ESPEasyWiFi_STA_Event_ESP32.h" +# endif + + +namespace ESPEasy { +namespace net { +namespace wifi { + +struct NW001_data_struct_WiFi_STA : public NWPluginData_base { + + NW001_data_struct_WiFi_STA(networkIndex_t networkIndex); + ~NW001_data_struct_WiFi_STA(); + + + void webform_load(EventStruct *event); + void webform_save(EventStruct *event); + + bool webform_getPort(KeyValueWriter *writer); + + bool init(EventStruct *event); + + bool exit(EventStruct *event); + +# ifdef ESP32 + bool handle_priority_route_changed() override; +# endif + +# if FEATURE_NETWORK_STATS + bool initPluginStats() override; + bool record_stats() override; + bool webformLoad_show_stats(struct EventStruct *event) const override; +# endif // if FEATURE_NETWORK_STATS + + NWPluginData_static_runtime* getNWPluginData_static_runtime(); + const __FlashStringHelper* getWiFi_encryptionType() const; + + WiFiDisconnectReason getWiFi_disconnectReason() const; + +private: + + ESPEasyWiFi_STA_EventHandler _WiFiEventHandler; + +}; + + +} // namespace wifi +} // namespace net +} // namespace ESPEasy + + +#endif // ifdef USES_NW001 diff --git a/src/ESPEasy/net/NWPluginStructs/NW002_data_struct_WiFi_AP.cpp b/src/ESPEasy/net/NWPluginStructs/NW002_data_struct_WiFi_AP.cpp new file mode 100644 index 000000000..d37e1e769 --- /dev/null +++ b/src/ESPEasy/net/NWPluginStructs/NW002_data_struct_WiFi_AP.cpp @@ -0,0 +1,258 @@ +#include "../NWPluginStructs/NW002_data_struct_WiFi_AP.h" + +#ifdef USES_NW002 + +# include "../../../src/Globals/Settings.h" + +# include "../../../src/Helpers/ESPEasy_time_calc.h" +# include "../../../src/Helpers/LongTermOnOffTimer.h" +# include "../../../src/Helpers/StringConverter.h" + +# include "../wifi/ESPEasyWifi.h" + +# ifdef ESP32 +# include +# include +# endif // ifdef ESP32 + +# define NW_PLUGIN_ID 2 +# ifdef ESP32 +# define NW_PLUGIN_INTERFACE WiFi.AP +# endif + +# define NW002_STATION_COUNT_STATS_INDEX 0 + +namespace ESPEasy { +namespace net { +namespace wifi { + +# ifdef ESP32 +static NWPluginData_static_runtime stats_and_cache(true, &NW_PLUGIN_INTERFACE); +# else +static NWPluginData_static_runtime stats_and_cache(true, "AP"); // Cannot use flash strings during init of static objects +# endif // ifdef ESP32 +static bool nw002_initialized{}; +# ifdef ESP32 +static bool nw002_enable_NAPT{}; + +static bool NW002_update_NAPT() { + if (nw002_initialized) { + NW_PLUGIN_INTERFACE.enableNAPT(false); + + if (nw002_enable_NAPT && NW_PLUGIN_INTERFACE.stationCount()) { + NW_PLUGIN_INTERFACE.enableNAPT(true); + return true; + } + } + + return false; +} + +# endif // ifdef ESP32 + +NW002_data_struct_WiFi_AP::NW002_data_struct_WiFi_AP(networkIndex_t networkIndex) + : NWPluginData_base( + nwpluginID_t(NW_PLUGIN_ID) + , networkIndex +# ifdef ESP32 + , &NW_PLUGIN_INTERFACE +# endif + ) +{ + stats_and_cache.clear(networkIndex); +# ifdef ESP32 + nw002_enable_NAPT = Settings.WiFi_AP_enable_NAPT(); + nw_event_id = Network.onEvent(NW002_data_struct_WiFi_AP::onEvent); +# endif // ifdef ESP32 + nw002_initialized = true; +} + +NW002_data_struct_WiFi_AP::~NW002_data_struct_WiFi_AP() +{ + nw002_initialized = false; +# ifdef ESP32 + nw002_enable_NAPT = false; + + if (nw_event_id != 0) { + Network.removeEvent(nw_event_id); + } + nw_event_id = 0; +# endif // ifdef ESP32 + stats_and_cache.processEvent_and_clear(); +} + +void NW002_data_struct_WiFi_AP::webform_load(EventStruct *event) {} + +void NW002_data_struct_WiFi_AP::webform_save(EventStruct *event) {} + +bool NW002_data_struct_WiFi_AP::webform_getPort(KeyValueWriter *writer) { return true; } + +bool NW002_data_struct_WiFi_AP::init(EventStruct *event) +{ +# ifdef ESP32 + nw002_enable_NAPT = Settings.WiFi_AP_enable_NAPT(); +# endif + + if (Settings.StartAP_on_NW002_init()) { + ESPEasy::net::wifi::setAPinternal(true); + } +# ifdef ESP32 + NW002_update_NAPT(); +# endif + return true; +} + +bool NW002_data_struct_WiFi_AP::exit(EventStruct *event) +{ + ESPEasy::net::wifi::setAPinternal(false); +# ifdef ESP32 + NW_PLUGIN_INTERFACE.enableNAPT(false); + NW_PLUGIN_INTERFACE.end(); +# endif // ifdef ESP32 +# ifdef ESP8266 + WiFi.softAPdisconnect(); +# endif // ifdef ESP8266 + + stats_and_cache.processEvents(); + + return true; +} + +NWPluginData_static_runtime * NW002_data_struct_WiFi_AP::getNWPluginData_static_runtime() +{ + if (nw002_initialized) { + return &stats_and_cache; + } + return nullptr; +} + +# ifdef ESP32 + +bool NW002_data_struct_WiFi_AP::handle_priority_route_changed() { return NW002_update_NAPT(); } + +# endif // ifdef ESP32 + + +# if FEATURE_NETWORK_STATS + +bool NW002_data_struct_WiFi_AP::initPluginStats() +{ + networkStatsVarIndex_t networkStatsVarIndex{}; + PluginStats_Config_t displayConfig; + + displayConfig.setAxisPosition(PluginStats_Config_t::AxisPosition::Left); + displayConfig.setEnabled(true); + + displayConfig.setAxisIndex(networkStatsVarIndex); + displayConfig.setHidden(true); + NWPluginData_base::initPluginStats( + networkStatsVarIndex, + F("Station Count"), + 1, + NAN, + displayConfig); +# ifdef ESP32 + ++networkStatsVarIndex; + displayConfig.setHidden(false); + displayConfig.setAxisIndex(networkStatsVarIndex); + NWPluginData_base::initPluginStats( + networkStatsVarIndex, + F("RSSI"), + 1, + NAN, + displayConfig); + +# if FEATURE_NETWORK_TRAFFIC_COUNT + initPluginStats_trafficCount(++networkStatsVarIndex, true); // TX + initPluginStats_trafficCount(++networkStatsVarIndex, false); // RX +# endif // if FEATURE_NETWORK_TRAFFIC_COUNT +# endif // ifdef ESP32 + return true; +} + +bool NW002_data_struct_WiFi_AP::record_stats() +{ + if (_plugin_stats_array != nullptr) { + EventStruct tmpEvent; + size_t valueCount{}; + tmpEvent.ParfN[valueCount++] = SOFTAP_STATION_COUNT; + +# ifdef ESP32 + { + wifi_sta_list_t wifi_sta_list = { 0 }; + esp_wifi_ap_get_sta_list(&wifi_sta_list); + + if (wifi_sta_list.num == 0) { + tmpEvent.ParfN[valueCount++] = NAN; + } else { + // FIXME TD-er: Should we list the 1st one, average or best/worst value? + // For now, just use the first one, which is likely the only user scenario actually being used + tmpEvent.ParfN[valueCount++] = wifi_sta_list.sta[0].rssi; + } + } +# endif // ifdef ESP32 + + bool trackPeaks = true; + bool onlyUpdateTimestampWhenSame = true; + return pushStatsValues(&tmpEvent, valueCount, trackPeaks, onlyUpdateTimestampWhenSame); + } + return false; +} + +bool NW002_data_struct_WiFi_AP::webformLoad_show_stats(struct EventStruct *event) const +{ + if (_plugin_stats_array != nullptr) { + return _plugin_stats_array->webformLoad_show_stats(event); + } + return false; +} + +# endif // if FEATURE_NETWORK_STATS + +# ifdef ESP32 + +void NW002_data_struct_WiFi_AP::onEvent(arduino_event_id_t event, + arduino_event_info_t info) +{ + switch (event) + { + case ARDUINO_EVENT_WIFI_AP_START: + stats_and_cache.mark_start(); + break; + case ARDUINO_EVENT_WIFI_AP_STOP: + stats_and_cache.mark_stop(); + break; + case ARDUINO_EVENT_WIFI_AP_STACONNECTED: + stats_and_cache.mark_connected(); + NW002_update_NAPT(); + addLog(LOG_LEVEL_INFO, F("AP_STACONNECTED")); + break; + case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED: + + if (!ESPEasy::net::wifi::wifiAPmodeActivelyUsed()) { + stats_and_cache.mark_disconnected(); + } + NW002_update_NAPT(); + addLog(LOG_LEVEL_INFO, F("AP_STADISCONNECTED")); + break; + case ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED: + addLog(LOG_LEVEL_INFO, F("AP_STAIPASSIGNED")); + break; + case ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED: + addLog(LOG_LEVEL_INFO, F("AP_PROBEREQRECVED")); + break; + case ARDUINO_EVENT_WIFI_AP_GOT_IP6: + addLog(LOG_LEVEL_INFO, F("AP_GOT_IP6")); + break; + + default: break; + } +} + +# endif // ifdef ESP32 + +} // namespace wifi +} // namespace net +} // namespace ESPEasy + +#endif // ifdef USES_NW002 diff --git a/src/ESPEasy/net/NWPluginStructs/NW002_data_struct_WiFi_AP.h b/src/ESPEasy/net/NWPluginStructs/NW002_data_struct_WiFi_AP.h new file mode 100644 index 000000000..7ba19a6e2 --- /dev/null +++ b/src/ESPEasy/net/NWPluginStructs/NW002_data_struct_WiFi_AP.h @@ -0,0 +1,55 @@ +#pragma once + +#include "../_NWPlugin_Helper.h" +#ifdef USES_NW001 + + +namespace ESPEasy { +namespace net { +namespace wifi { + +struct NW002_data_struct_WiFi_AP : public NWPluginData_base { + + NW002_data_struct_WiFi_AP(networkIndex_t networkIndex); + ~NW002_data_struct_WiFi_AP(); + + + void webform_load(EventStruct *event); + void webform_save(EventStruct *event); + + bool webform_getPort(KeyValueWriter *writer); + + bool init(EventStruct *event); + + bool exit(EventStruct *event); + +# ifdef ESP32 + bool handle_priority_route_changed() override; +# endif + +# if FEATURE_NETWORK_STATS + bool initPluginStats() override; + bool record_stats() override; + bool webformLoad_show_stats(struct EventStruct *event) const override; +# endif // if FEATURE_NETWORK_STATS + + NWPluginData_static_runtime* getNWPluginData_static_runtime(); + +private: + +# ifdef ESP32 + static void onEvent(arduino_event_id_t event, + arduino_event_info_t info); + + network_event_handle_t nw_event_id = 0; +# endif // ifdef ESP32 + +}; + + +} // namespace wifi +} // namespace net +} // namespace ESPEasy + + +#endif // ifdef USES_NW001 diff --git a/src/ESPEasy/net/NWPluginStructs/NW003_data_struct_ETH_RMII.cpp b/src/ESPEasy/net/NWPluginStructs/NW003_data_struct_ETH_RMII.cpp new file mode 100644 index 000000000..229bae1d7 --- /dev/null +++ b/src/ESPEasy/net/NWPluginStructs/NW003_data_struct_ETH_RMII.cpp @@ -0,0 +1,579 @@ +#include "../NWPluginStructs/NW003_data_struct_ETH_RMII.h" + +#ifdef USES_NW003 + + +# include "../../../src/ESPEasyCore/ESPEasyGPIO.h" + +# include "../../../src/Globals/Settings.h" + +# include "../../../src/Helpers/ESPEasy_time_calc.h" +# include "../../../src/Helpers/Hardware_GPIO.h" +# include "../../../src/Helpers/LongTermOnOffTimer.h" +# include "../../../src/Helpers/StringConverter.h" + +# include "../../../src/WebServer/Markup.h" +# include "../../../src/WebServer/Markup_Forms.h" +# include "../../../src/WebServer/ESPEasy_key_value_store_webform.h" + +# include "../Globals/NetworkState.h" + +# include "../Helpers/NW_info_writer.h" + +# include "../eth/ESPEasyEth.h" + +# define NW_PLUGIN_ID 3 + +namespace ESPEasy { +namespace net { +namespace eth { + + +// Keys as used in the Key-value-store +# define NW003_KEY_ETH_INDEX 1 +# define NW003_KEY_ETH_PHY_TYPE 2 +# define NW003_KEY_ETH_PHY_ADDR 3 +# define NW003_KEY_ETH_PIN_MDC 4 +# define NW003_KEY_ETH_PIN_MDIO 5 +# define NW003_KEY_ETH_PIN_POWER 6 +# define NW003_KEY_CLOCK_MODE 7 +# define NW003_KEY_IP 8 +# define NW003_KEY_GW 9 +# define NW003_KEY_SN 10 +# define NW003_KEY_DNS 11 + +# define NW003_MAX_KEY 12 + +const __FlashStringHelper * NW003_data_struct_ETH_RMII::getLabelString(uint32_t key, bool displayString, KVS_StorageType::Enum& storageType) +{ + storageType = KVS_StorageType::Enum::int8_type; + + switch (key) + { + case NW003_KEY_ETH_INDEX: return F("Index"); + case NW003_KEY_ETH_PHY_TYPE: return displayString ? F("Ethernet PHY type") : F("phytype"); + case NW003_KEY_ETH_PHY_ADDR: return displayString ? F("Ethernet PHY Address") : F("phyaddr"); + case NW003_KEY_ETH_PIN_MDC: return displayString ? F("Ethernet MDC pin") : F("MDC"); + case NW003_KEY_ETH_PIN_MDIO: return displayString ? F("Ethernet MDIO pin") : F("MDIO"); + case NW003_KEY_ETH_PIN_POWER: return displayString ? F("Ethernet Power pin") : F("pwr"); + case NW003_KEY_CLOCK_MODE: return displayString ? F("Ethernet Clock") : F("clock"); + case NW003_KEY_IP: + storageType = KVS_StorageType::Enum::string_type; + return F("IP"); + case NW003_KEY_GW: + storageType = KVS_StorageType::Enum::string_type; + return displayString ? F("Gateway") : F("gw"); + case NW003_KEY_SN: + storageType = KVS_StorageType::Enum::string_type; + return displayString ? F("Subnetmask") : F("sn"); + case NW003_KEY_DNS: + storageType = KVS_StorageType::Enum::string_type; + return F("DNS"); + } + return F(""); +} + +int32_t NW003_data_struct_ETH_RMII::getNextKey(int32_t key) +{ + if (key == -1) { return NW003_KEY_ETH_INDEX; } + + if (key >= NW003_MAX_KEY) { return -2; } + return key + 1; +} + +String NW003_data_struct_ETH_RMII::formatGpioLabel(uint32_t key, + PinSelectPurpose& purpose, + bool shortNotation) +{ + String label; + + if ((key >= NW003_KEY_ETH_PIN_MDC) && (key <= NW003_KEY_ETH_PIN_POWER)) + { + KVS_StorageType::Enum storageType; + purpose = PinSelectPurpose::Generic; + label = getLabelString(key, true, storageType); + + const bool optional = !shortNotation; + + switch (key) + { + case NW003_KEY_ETH_PIN_MDIO: + purpose = PinSelectPurpose::Generic_input; + label = formatGpioName( + label, gpio_direction::gpio_input, optional); + break; + case NW003_KEY_ETH_PIN_MDC: + case NW003_KEY_ETH_PIN_POWER: + purpose = PinSelectPurpose::Generic_output; + label = formatGpioName( + label, gpio_direction::gpio_output, optional); + break; + } + } + return label; +} + +WebFormItemParams NW003_makeWebFormItemParams(uint32_t key) { + KVS_StorageType::Enum storageType; + const __FlashStringHelper*label = NW003_data_struct_ETH_RMII::getLabelString(key, true, storageType); + const __FlashStringHelper*id = NW003_data_struct_ETH_RMII::getLabelString(key, false, storageType); + + return WebFormItemParams(label, id, storageType, key); +} + +NW003_data_struct_ETH_RMII::NW003_data_struct_ETH_RMII(networkIndex_t networkIndex, NetworkInterface *netif) + : NWPluginData_base(nwpluginID_t(NW_PLUGIN_ID), networkIndex, netif) +{} + +NW003_data_struct_ETH_RMII::~NW003_data_struct_ETH_RMII() +{ + ESPEasy::net::eth::ETH_NWPluginData_static_runtime::exit(_networkIndex); +} + +void NW003_data_struct_ETH_RMII::loadDefaults(ESPEasy_key_value_store *kvs, + ESPEasy::net::networkIndex_t networkIndex, + ESPEasy::net::nwpluginID_t nwPluginID) +{ + // Only load the defaults when the settings are empty + if (kvs && kvs->isEmpty()) { + if (isValid(Settings.ETH_Phy_Type) && + !ESPEasy::net::isSPI_EthernetType(Settings.ETH_Phy_Type)) + { + kvs->setValue(NW003_KEY_ETH_INDEX, static_cast(-1)); + kvs->setValue(NW003_KEY_ETH_PHY_TYPE, static_cast(Settings.ETH_Phy_Type)); + kvs->setValue(NW003_KEY_ETH_PHY_ADDR, static_cast(Settings.ETH_Phy_Addr)); + kvs->setValue(NW003_KEY_ETH_PIN_MDC, static_cast(Settings.ETH_Pin_mdc_cs)); + kvs->setValue(NW003_KEY_ETH_PIN_MDIO, static_cast(Settings.ETH_Pin_mdio_irq)); + kvs->setValue(NW003_KEY_ETH_PIN_POWER, static_cast(Settings.ETH_Pin_power_rst)); + kvs->setValue(NW003_KEY_CLOCK_MODE, static_cast(Settings.ETH_Clock_Mode)); + + // TODO TD-er: Copy IP info + + store_nwpluginTaskData_KVS(kvs, networkIndex, nwPluginID); + } + # if defined(DEFAULT_ETH_PHY_TYPE) && defined(DEFAULT_ETH_CLOCK_MODE) && defined(DEFAULT_ETH_PHY_ADDR) && \ + defined(DEFAULT_ETH_PIN_MDC) && defined(DEFAULT_ETH_PIN_MDIO) + else if (isValid(DEFAULT_ETH_PHY_TYPE) && + !ESPEasy::net::isSPI_EthernetType(DEFAULT_ETH_PHY_TYPE)) + { + kvs->setValue(NW003_KEY_ETH_INDEX, static_cast(-1)); + kvs->setValue(NW003_KEY_ETH_PHY_TYPE, static_cast(DEFAULT_ETH_PHY_TYPE)); + kvs->setValue(NW003_KEY_ETH_PHY_ADDR, static_cast(DEFAULT_ETH_PHY_ADDR)); + kvs->setValue(NW003_KEY_ETH_PIN_MDC, static_cast(DEFAULT_ETH_PIN_MDC)); + kvs->setValue(NW003_KEY_ETH_PIN_MDIO, static_cast(DEFAULT_ETH_PIN_MDIO)); + kvs->setValue(NW003_KEY_ETH_PIN_POWER, static_cast(DEFAULT_ETH_PIN_POWER)); + kvs->setValue(NW003_KEY_CLOCK_MODE, static_cast(DEFAULT_ETH_CLOCK_MODE)); + + // TODO TD-er: Copy IP info + + store_nwpluginTaskData_KVS(kvs, networkIndex, nwPluginID); + } + # endif // if defined(DEFAULT_ETH_PHY_TYPE) && defined(DEFAULT_ETH_CLOCK_MODE) && defined(DEFAULT_ETH_PHY_ADDR) && + // defined(DEFAULT_ETH_PIN_MDC) && defined(DEFAULT_ETH_PIN_MDIO) + } +} + +void NW003_data_struct_ETH_RMII::webform_load(EventStruct *event) +{ + _load(); + NW003_data_struct_ETH_RMII::loadDefaults(_kvs, event->NetworkIndex, nwpluginID_t(3)); + + addFormSubHeader(F("Ethernet IP Settings")); + + addFormIPBox(F("ESP Ethernet IP"), F("espethip"), Settings.ETH_IP); + addFormIPBox(F("ESP Ethernet Gateway"), F("espethgateway"), Settings.ETH_Gateway); + addFormIPBox(F("ESP Ethernet Subnetmask"), F("espethsubnet"), Settings.ETH_Subnet); + addFormIPBox(F("ESP Ethernet DNS"), F("espethdns"), Settings.ETH_DNS); + addFormNote(F("Leave empty for DHCP")); + + addFormSubHeader(F("Ethernet")); + + { + const __FlashStringHelper *options[] = { + toString(EthPhyType_t::notSet), + + toString(EthPhyType_t::LAN8720), +# if ETH_PHY_LAN867X_SUPPORTED + toString(ESPEasy::net::EthPhyType_t::LAN867X), +# endif + toString(EthPhyType_t::TLK110), + toString(EthPhyType_t::RTL8201), +# if ETH_TYPE_JL1101_SUPPORTED + toString(EthPhyType_t::JL1101), +# endif + toString(EthPhyType_t::DP83848), + toString(EthPhyType_t::KSZ8041), + toString(EthPhyType_t::KSZ8081), + }; + const int ids[] = { + static_cast(EthPhyType_t::notSet), + + static_cast(EthPhyType_t::LAN8720), +# if ETH_PHY_LAN867X_SUPPORTED + static_cast(ESPEasy::net::EthPhyType_t::LAN867X), +# endif + static_cast(EthPhyType_t::TLK110), + static_cast(EthPhyType_t::RTL8201), +# if ETH_TYPE_JL1101_SUPPORTED + static_cast(EthPhyType_t::JL1101), +# endif + static_cast(EthPhyType_t::DP83848), + static_cast(EthPhyType_t::KSZ8041), + static_cast(EthPhyType_t::KSZ8081), + + }; + const FormSelectorOptions selector(NR_ELEMENTS(ids), options, ids); + auto params = NW003_makeWebFormItemParams(NW003_KEY_ETH_PHY_TYPE); + params._defaultIntValue = static_cast(EthPhyType_t::notSet); + showFormSelector(*_kvs, selector, params); + } + { + auto params = NW003_makeWebFormItemParams(NW003_KEY_ETH_PHY_ADDR); + params._min = -1; + params._max = 127; + params._defaultIntValue = 1; + showWebformItem(*_kvs, params); + addFormNote(F("I²C-address of Ethernet PHY" + " (0 or 1 for LAN8720, 31 for TLK110, -1 autodetect)" + )); + + } + { + const int gpio_keys[] = { + NW003_KEY_ETH_PIN_MDC, + NW003_KEY_ETH_PIN_MDIO, + NW003_KEY_ETH_PIN_POWER + }; + + for (int i = 0; i < NR_ELEMENTS(gpio_keys); ++i) + { + const int key = gpio_keys[i]; + KVS_StorageType::Enum storageType; + const __FlashStringHelper *id = getLabelString(key, false, storageType); + PinSelectPurpose purpose = PinSelectPurpose::Ethernet; + String label = formatGpioLabel(key, purpose); + + int8_t pin = -1; + _kvs->getValue(key, pin); + + addFormPinSelect(purpose, label, id, pin); + } + } + { +# if CONFIG_IDF_TARGET_ESP32 + const __FlashStringHelper *ethClockOptions[] = { + toString(EthClockMode_t::Ext_crystal_osc), + toString(EthClockMode_t::Int_50MHz_GPIO_0), + toString(EthClockMode_t::Int_50MHz_GPIO_16), + toString(EthClockMode_t::Int_50MHz_GPIO_17_inv) + }; + const int indices[] = { + static_cast(EthClockMode_t::Ext_crystal_osc), + static_cast(EthClockMode_t::Int_50MHz_GPIO_0), + static_cast(EthClockMode_t::Int_50MHz_GPIO_16), + static_cast(EthClockMode_t::Int_50MHz_GPIO_17_inv) + }; +# endif // if CONFIG_IDF_TARGET_ESP32 +# if CONFIG_IDF_TARGET_ESP32P4 + const __FlashStringHelper *ethClockOptions[] = { + // toString(EthClockMode_t::Default), + toString(EthClockMode_t::Ext_crystal), + toString(EthClockMode_t::Int_50MHz) + }; + const int indices[] = { + // static_cast(EthClockMode_t::Default), + static_cast(EthClockMode_t::Ext_crystal), + static_cast(EthClockMode_t::Int_50MHz) + }; +# endif // if CONFIG_IDF_TARGET_ESP32P4 + const FormSelectorOptions selector( + NR_ELEMENTS(ethClockOptions), + ethClockOptions, + indices); + auto params = NW003_makeWebFormItemParams(NW003_KEY_CLOCK_MODE); +# if CONFIG_IDF_TARGET_ESP32 + params._defaultIntValue = static_cast(EthClockMode_t::Ext_crystal_osc); +#endif +# if CONFIG_IDF_TARGET_ESP32P4 + params._defaultIntValue = static_cast(EthClockMode_t::Ext_crystal); +#endif + + showFormSelector(*_kvs, selector, params); + + } +} + +void NW003_data_struct_ETH_RMII::webform_save(EventStruct *event) +{ + // TODO TD-er: Move this to a central function, like done with import/export + int32_t key = getNextKey(-1); + + while (key >= 0) { + KVS_StorageType::Enum storageType; + const __FlashStringHelper *id = getLabelString(key, false, storageType); + storeWebformItem(*_kvs, key, storageType, id); + key = getNextKey(key); + } + _store(); +} + +bool NW003_data_struct_ETH_RMII::webform_getPort(KeyValueWriter *writer) { return true; } + +bool NW003_data_struct_ETH_RMII::init(EventStruct *event) +{ + _load(); + ETHConnectRelaxed(); + + return true; +} + +bool NW003_data_struct_ETH_RMII::exit(EventStruct *event) { return true; } + +NWPluginData_static_runtime * NW003_data_struct_ETH_RMII::getNWPluginData_static_runtime() { + return ESPEasy::net::eth::ETH_NWPluginData_static_runtime::getNWPluginData_static_runtime(_networkIndex); +} + +bool NW003_data_struct_ETH_RMII::write_Eth_HW_Address(KeyValueWriter *writer) +{ + if (writer == nullptr) { return false; } + auto data = getNWPluginData_static_runtime(); + auto iface = ESPEasy::net::eth::ETH_NWPluginData_static_runtime::getInterface(_networkIndex); + + if (!(data && iface)) { return false; } + const int phyType_int = _kvs->getValueAsInt_or_default(NW003_KEY_ETH_PHY_TYPE, -1); + + if (phyType_int == -1) { return false; } + return ESPEasy::net::write_Eth_HW_Address( + static_cast(phyType_int), + iface, + writer); +} + +bool NW003_data_struct_ETH_RMII::write_Eth_port(KeyValueWriter *writer) +{ + if (writer == nullptr) { return false; } + const ESPEasy::net::EthPhyType_t phyType = static_cast(_kvs->getValueAsInt(NW003_KEY_ETH_PHY_TYPE)); + + if (!isValid(phyType)) { return false; } + + const __FlashStringHelper*labels[] = { + F("MDC"), F("MDIO"), F("Power") }; + const int pins[] = { + (int)_kvs->getValueAsInt(NW003_KEY_ETH_PIN_MDC), + (int)_kvs->getValueAsInt(NW003_KEY_ETH_PIN_MDIO), + (int)_kvs->getValueAsInt(NW003_KEY_ETH_PIN_POWER) + }; + return write_NetworkPort(labels, pins, NR_ELEMENTS(labels), writer); +} + +void NW003_data_struct_ETH_RMII::ethResetGPIOpins() { + // fix an disconnection issue after rebooting Olimex POE - this forces a clean state for all GPIO involved in RMII + // Thanks to @s-hadinger and @Jason2866 + // Resetting state of power pin is done in ethPower() + addLog(LOG_LEVEL_INFO, F("ethResetGPIOpins()")); + gpio_reset_pin((gpio_num_t)_kvs->getValueAsInt(NW003_KEY_ETH_PIN_MDC)); + gpio_reset_pin((gpio_num_t)_kvs->getValueAsInt(NW003_KEY_ETH_PIN_MDIO)); +# ifdef CONFIG_IDF_TARGET_ESP32 + + gpio_reset_pin(GPIO_NUM_19); // EMAC_TXD0 - hardcoded + gpio_reset_pin(GPIO_NUM_21); // EMAC_TX_EN - hardcoded + gpio_reset_pin(GPIO_NUM_22); // EMAC_TXD1 - hardcoded + gpio_reset_pin(GPIO_NUM_25); // EMAC_RXD0 - hardcoded + gpio_reset_pin(GPIO_NUM_26); // EMAC_RXD1 - hardcoded + gpio_reset_pin(GPIO_NUM_27); // EMAC_RX_CRS_DV - hardcoded +# endif // ifdef CONFIG_IDF_TARGET_ESP32 + + /* + switch (Settings.ETH_Clock_Mode) { + case EthClockMode_t::Ext_crystal_osc: // ETH_CLOCK_GPIO0_IN + case EthClockMode_t::Int_50MHz_GPIO_0: // ETH_CLOCK_GPIO0_OUT + gpio_reset_pin(GPIO_NUM_0); + break; + case EthClockMode_t::Int_50MHz_GPIO_16: // ETH_CLOCK_GPIO16_OUT + gpio_reset_pin(GPIO_NUM_16); + break; + case EthClockMode_t::Int_50MHz_GPIO_17_inv: // ETH_CLOCK_GPIO17_OUT + gpio_reset_pin(GPIO_NUM_17); + break; + } + */ + delay(1); + +} + +void NW003_data_struct_ETH_RMII::ethPower(bool enable) +{ + const int powerpin = _kvs->getValueAsInt(NW003_KEY_ETH_PIN_POWER); + + if (powerpin == -1) { return; } + + if (GPIO_Internal_Read(powerpin) == enable) { + // Already the desired state + return; + } + addLog(LOG_LEVEL_INFO, enable ? F("ETH power ON") : F("ETH power OFF")); + + /* + if (!enable) { + EthEventData.ethInitSuccess = false; + EthEventData.clearAll(); + # ifdef ESP_IDF_VERSION_MAJOR + + // FIXME TD-er: See: https://github.com/espressif/arduino-esp32/issues/6105 + // Need to store the last link state, as it will be cleared after destructing the object. + EthEventData.setEthDisconnected(); + + if (ETH.linkUp()) { + EthEventData.setEthConnected(); + } + # endif // ifdef ESP_IDF_VERSION_MAJOR + + // ETH = ETHClass(); + } + */ + + if (enable) { + // ethResetGPIOpins(); + } + + // gpio_reset_pin((gpio_num_t)powerpin); + + GPIO_Write(PLUGIN_GPIO, powerpin, enable ? 1 : 0); + + if (!enable) { + const EthClockMode_t ETH_ClockMode = static_cast(_kvs->getValueAsInt(NW003_KEY_CLOCK_MODE)); + # if CONFIG_IDF_TARGET_ESP32P4 + const bool isExternalCrystal = ETH_ClockMode == EthClockMode_t::Ext_crystal; + # else + const bool isExternalCrystal = ETH_ClockMode == EthClockMode_t::Ext_crystal_osc; + # endif // if CONFIG_IDF_TARGET_ESP32P4 + + if (isExternalCrystal) { + delay(600); // Give some time to discharge any capacitors + // Delay is needed to make sure no clock signal remains present which may cause the ESP to boot into flash mode. + } + } else { + delay(400); // LAN chip needs to initialize before calling Eth.begin() + } +} + +bool NW003_data_struct_ETH_RMII::ethCheckSettings() { + const EthClockMode_t ETH_ClockMode = static_cast(_kvs->getValueAsInt(NW003_KEY_CLOCK_MODE)); + const ESPEasy::net::EthPhyType_t phyType = static_cast(_kvs->getValueAsInt(NW003_KEY_ETH_PHY_TYPE)); + + const int powerPin = _kvs->getValueAsInt(NW003_KEY_ETH_PIN_POWER); + + return isValid(phyType) + && (isValid(ETH_ClockMode) /* || isSPI_EthernetType(Settings.ETH_Phy_Type)*/) + && validGpio(_kvs->getValueAsInt(NW003_KEY_ETH_PIN_MDC)) + && (validGpio(_kvs->getValueAsInt(NW003_KEY_ETH_PIN_MDIO)) && + (validGpio(powerPin) || (powerPin == -1)) + ); // Some boards have fixed power +} + +void NW003_data_struct_ETH_RMII::ethPrintSettings() { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log; + + if (log.reserve(115)) { + + const EthClockMode_t ETH_ClockMode = static_cast(_kvs->getValueAsInt(NW003_KEY_CLOCK_MODE)); + const ESPEasy::net::EthPhyType_t phyType = static_cast(_kvs->getValueAsInt(NW003_KEY_ETH_PHY_TYPE)); + + log += F("ETH PHY Type: "); + log += toString(phyType); + log += F(" PHY Addr: "); + log += _kvs->getValueAsInt(NW003_KEY_ETH_PHY_ADDR); + + log += F(" Eth Clock mode: "); + log += ESPEasy::net::toString(ETH_ClockMode); + log += strformat(F(" MDC: %d MIO: %d PWR: %d"), + _kvs->getValueAsInt(NW003_KEY_ETH_PIN_MDC), + _kvs->getValueAsInt(NW003_KEY_ETH_PIN_MDIO), + _kvs->getValueAsInt(NW003_KEY_ETH_PIN_POWER)); + addLogMove(LOG_LEVEL_INFO, log); + } + } +} + +bool NW003_data_struct_ETH_RMII::ETHConnectRelaxed() { + auto data = getNWPluginData_static_runtime(); + + auto iface = ESPEasy::net::eth::ETH_NWPluginData_static_runtime::getInterface(_networkIndex); + + if (!(data && iface)) { return false; } + + if (data->started() && data->connected()) { + return EthLinkUp(); + } + ethPrintSettings(); + + if (!ethCheckSettings()) + { + addLog(LOG_LEVEL_ERROR, F("ETH: Settings not correct!!!")); + data->mark_stop(); + return false; + } + + ethPower(true); + data->mark_begin_establish_connection(); + + bool success = data->started(); + + if (!success) { +# if FEATURE_USE_IPV6 + + if (Settings.EnableIPv6()) { + iface->enableIPv6(true); + } +# endif // if FEATURE_USE_IPV6 + + const EthClockMode_t ETH_ClockMode = static_cast(_kvs->getValueAsInt(NW003_KEY_CLOCK_MODE)); + const ESPEasy::net::EthPhyType_t phyType = static_cast(_kvs->getValueAsInt(NW003_KEY_ETH_PHY_TYPE)); + + const int phy_addr = _kvs->getValueAsInt(NW003_KEY_ETH_PHY_ADDR); + const int powerPin = _kvs->getValueAsInt(NW003_KEY_ETH_PIN_POWER); + const int mdcPin = _kvs->getValueAsInt(NW003_KEY_ETH_PIN_MDC); + const int mdioPin = _kvs->getValueAsInt(NW003_KEY_ETH_PIN_MDIO); + +# ifndef ESP32P4 + ethResetGPIOpins(); +# endif + success = iface->begin( + to_ESP_phy_type(phyType), + phy_addr, + mdcPin, + mdioPin, + powerPin, + (eth_clock_mode_t)Settings.ETH_Clock_Mode); + } + + if (success) { + // FIXME TD-er: Not sure if this is correctly set to false + // EthEventData.ethConnectAttemptNeeded = false; + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + PrintToString p2s; + KeyValueWriter_WebForm writer(false, &p2s); + writer.setSummaryValueOnly(); + + if (write_Eth_Show_Connected(*iface, &writer)) { + addLog(LOG_LEVEL_INFO, concat(F("ETH : "), p2s.get())); + } + } + + if (EthLinkUp()) { + // We might miss the connected event, since we are already connected. + data->mark_connected(); + } + } else { + addLog(LOG_LEVEL_ERROR, F("ETH : Failed to initialize ETH")); + } + return success; +} + +} // namespace eth +} // namespace net +} // namespace ESPEasy + +#endif // ifdef USES_NW003 diff --git a/src/ESPEasy/net/NWPluginStructs/NW003_data_struct_ETH_RMII.h b/src/ESPEasy/net/NWPluginStructs/NW003_data_struct_ETH_RMII.h new file mode 100644 index 000000000..480ee1dd8 --- /dev/null +++ b/src/ESPEasy/net/NWPluginStructs/NW003_data_struct_ETH_RMII.h @@ -0,0 +1,73 @@ +#pragma once + +#include "../_NWPlugin_Helper.h" +#ifdef USES_NW003 + +#include "../eth/ETH_NWPluginData_static_runtime.h" + +namespace ESPEasy { +namespace net { +namespace eth { + +struct NW003_data_struct_ETH_RMII : public NWPluginData_base { + + // See: ESPEasy_key_value_store_import_export::LabelStringFunction + static const __FlashStringHelper* getLabelString(uint32_t key, + bool displayString, + KVS_StorageType::Enum& storageType); + + // When queried with a key of -1, it will return the first key index + // Return next key, or -2 when no next key exists. + // See: ESPEasy_key_value_store_import_export::NextKeyFunction + static int32_t getNextKey(int32_t key); + + static void loadDefaults(ESPEasy_key_value_store *kvs, + ESPEasy::net::networkIndex_t networkIndex, + ESPEasy::net::nwpluginID_t nwPluginID); + + + NW003_data_struct_ETH_RMII(networkIndex_t networkIndex, NetworkInterface *netif); + ~NW003_data_struct_ETH_RMII(); + + void webform_load(EventStruct *event); + void webform_save(EventStruct *event); + + bool webform_getPort(KeyValueWriter *writer); + + bool init(EventStruct *event); + + bool exit(EventStruct *event); + + NWPluginData_static_runtime* getNWPluginData_static_runtime(); + + bool write_Eth_HW_Address(KeyValueWriter *writer); + + bool write_Eth_port(KeyValueWriter *writer); + + +private: + + + static String formatGpioLabel(uint32_t key, + PinSelectPurpose& purpose, + bool shortNotation = false); + +void ethResetGPIOpins(); + +void ethPower(bool enable); + +bool ethCheckSettings(); + +void ethPrintSettings(); + +bool ETHConnectRelaxed(); + +}; + + +} // namespace eth +} // namespace net +} // namespace ESPEasy + + +#endif // ifdef USES_NW003 diff --git a/src/ESPEasy/net/NWPluginStructs/NW004_data_struct_ETH_SPI.cpp b/src/ESPEasy/net/NWPluginStructs/NW004_data_struct_ETH_SPI.cpp new file mode 100644 index 000000000..f3ae64fe3 --- /dev/null +++ b/src/ESPEasy/net/NWPluginStructs/NW004_data_struct_ETH_SPI.cpp @@ -0,0 +1,494 @@ +#include "../NWPluginStructs/NW004_data_struct_ETH_SPI.h" + +#ifdef USES_NW004 + +# include "../../../src/Globals/Settings.h" + +# include "../../../src/Helpers/ESPEasy_time_calc.h" +# include "../../../src/Helpers/Hardware_GPIO.h" +# include "../../../src/Helpers/LongTermOnOffTimer.h" +# include "../../../src/Helpers/StringConverter.h" + +# include "../../../src/WebServer/Markup.h" +# include "../../../src/WebServer/Markup_Forms.h" +# include "../../../src/WebServer/ESPEasy_key_value_store_webform.h" + +# include "../Globals/NetworkState.h" + +# include "../Helpers/NW_info_writer.h" + +# include "../eth/ESPEasyEth.h" + +# define NW_PLUGIN_ID 4 + +namespace ESPEasy { +namespace net { +namespace eth { + + +// Keys as used in the Key-value-store +# define NW004_KEY_ETH_INDEX 1 +# define NW004_KEY_ETH_PHY_TYPE 2 +# define NW004_KEY_ETH_PHY_ADDR 3 +# define NW004_KEY_ETH_PIN_CS 4 +# define NW004_KEY_ETH_PIN_IRQ 5 +# define NW004_KEY_ETH_PIN_RST 6 +# define NW004_KEY_IP 7 +# define NW004_KEY_GW 8 +# define NW004_KEY_SN 9 +# define NW004_KEY_DNS 10 + +# define NW004_MAX_KEY 11 + +const __FlashStringHelper * NW004_data_struct_ETH_SPI::getLabelString(uint32_t key, bool displayString, KVS_StorageType::Enum& storageType) +{ + storageType = KVS_StorageType::Enum::int8_type; + + switch (key) + { + case NW004_KEY_ETH_INDEX: return F("Index"); + case NW004_KEY_ETH_PHY_TYPE: return displayString ? F("Ethernet PHY type") : F("phytype"); + case NW004_KEY_ETH_PHY_ADDR: return displayString ? F("Ethernet PHY Address") : F("phyaddr"); + case NW004_KEY_ETH_PIN_CS: return displayString ? F("Ethernet CS pin") : F("CS"); + case NW004_KEY_ETH_PIN_IRQ: return displayString ? F("Ethernet IRQ pin") : F("IRQ"); + case NW004_KEY_ETH_PIN_RST: return displayString ? F("Ethernet RST pin") : F("RST"); + case NW004_KEY_IP: + storageType = KVS_StorageType::Enum::string_type; + return F("IP"); + case NW004_KEY_GW: + storageType = KVS_StorageType::Enum::string_type; + return displayString ? F("Gateway") : F("gw"); + case NW004_KEY_SN: + storageType = KVS_StorageType::Enum::string_type; + return displayString ? F("Subnetmask") : F("sn"); + case NW004_KEY_DNS: + storageType = KVS_StorageType::Enum::string_type; + return F("DNS"); + } + return F(""); +} + +int32_t NW004_data_struct_ETH_SPI::getNextKey(int32_t key) +{ + if (key == -1) { return NW004_KEY_ETH_INDEX; } + + if (key >= NW004_MAX_KEY) { return -2; } + return key + 1; +} + +String NW004_data_struct_ETH_SPI::formatGpioLabel(uint32_t key, + PinSelectPurpose& purpose, + bool shortNotation) +{ + String label; + + if ((key >= NW004_KEY_ETH_PIN_CS) && (key <= NW004_KEY_ETH_PIN_RST)) + { + KVS_StorageType::Enum storageType; + purpose = PinSelectPurpose::Generic; + label = getLabelString(key, true, storageType); + + const bool optional = !shortNotation; + + switch (key) + { + case NW004_KEY_ETH_PIN_IRQ: + purpose = PinSelectPurpose::Generic_input; + label = formatGpioName( + label, gpio_direction::gpio_input, optional); + break; + case NW004_KEY_ETH_PIN_CS: + case NW004_KEY_ETH_PIN_RST: + purpose = PinSelectPurpose::Generic_output; + label = formatGpioName( + label, gpio_direction::gpio_output, optional); + break; + } + } + return label; +} + +WebFormItemParams NW004_makeWebFormItemParams(uint32_t key) { + KVS_StorageType::Enum storageType; + const __FlashStringHelper*label = NW004_data_struct_ETH_SPI::getLabelString(key, true, storageType); + const __FlashStringHelper*id = NW004_data_struct_ETH_SPI::getLabelString(key, false, storageType); + + return WebFormItemParams(label, id, storageType, key); +} + +NW004_data_struct_ETH_SPI::NW004_data_struct_ETH_SPI(networkIndex_t networkIndex, NetworkInterface *netif) + : NWPluginData_base(nwpluginID_t(NW_PLUGIN_ID), networkIndex, netif) +{} + +NW004_data_struct_ETH_SPI::~NW004_data_struct_ETH_SPI() +{ + ESPEasy::net::eth::ETH_NWPluginData_static_runtime::exit(_networkIndex); +} + +void NW004_data_struct_ETH_SPI::loadDefaults(ESPEasy_key_value_store *kvs, + ESPEasy::net::networkIndex_t networkIndex, + ESPEasy::net::nwpluginID_t nwPluginID) +{ + // Only load the defaults when the settings are empty + if (kvs && kvs->isEmpty()) { + if (isValid(Settings.ETH_Phy_Type) && + ESPEasy::net::isSPI_EthernetType(Settings.ETH_Phy_Type)) + { + kvs->setValue(NW004_KEY_ETH_INDEX, static_cast(-1)); + kvs->setValue(NW004_KEY_ETH_PHY_TYPE, static_cast(Settings.ETH_Phy_Type)); + kvs->setValue(NW004_KEY_ETH_PHY_ADDR, static_cast(Settings.ETH_Phy_Addr)); + kvs->setValue(NW004_KEY_ETH_PIN_CS, static_cast(Settings.ETH_Pin_mdc_cs)); + kvs->setValue(NW004_KEY_ETH_PIN_IRQ, static_cast(Settings.ETH_Pin_mdio_irq)); + kvs->setValue(NW004_KEY_ETH_PIN_RST, static_cast(Settings.ETH_Pin_power_rst)); + + // TODO TD-er: Copy IP info + + store_nwpluginTaskData_KVS(kvs, networkIndex, nwPluginID); + } + # if defined(DEFAULT_ETH_PHY_TYPE) && defined(DEFAULT_ETH_CLOCK_MODE) && defined(DEFAULT_ETH_PHY_ADDR) && \ + defined(DEFAULT_ETH_PIN_MDC) && defined(DEFAULT_ETH_PIN_MDIO) + else if (isValid(DEFAULT_ETH_PHY_TYPE) && + ESPEasy::net::isSPI_EthernetType(DEFAULT_ETH_PHY_TYPE)) + { + kvs->setValue(NW004_KEY_ETH_INDEX, static_cast(-1)); + kvs->setValue(NW004_KEY_ETH_PHY_TYPE, static_cast(DEFAULT_ETH_PHY_TYPE)); + kvs->setValue(NW004_KEY_ETH_PHY_ADDR, static_cast(DEFAULT_ETH_PHY_ADDR)); + kvs->setValue(NW004_KEY_ETH_PIN_CS, static_cast(DEFAULT_ETH_PIN_MDC)); + kvs->setValue(NW004_KEY_ETH_PIN_IRQ, static_cast(DEFAULT_ETH_PIN_MDIO)); + kvs->setValue(NW004_KEY_ETH_PIN_RST, static_cast(DEFAULT_ETH_PIN_POWER)); + + // TODO TD-er: Copy IP info + + store_nwpluginTaskData_KVS(kvs, networkIndex, nwPluginID); + } + # endif // if defined(DEFAULT_ETH_PHY_TYPE) && defined(DEFAULT_ETH_CLOCK_MODE) && defined(DEFAULT_ETH_PHY_ADDR) && + // defined(DEFAULT_ETH_PIN_MDC) && defined(DEFAULT_ETH_PIN_MDIO) + } +} + +void NW004_data_struct_ETH_SPI::webform_load(EventStruct *event) +{ + _load(); + NW004_data_struct_ETH_SPI::loadDefaults(_kvs, event->NetworkIndex, nwpluginID_t(4)); + addFormSubHeader(F("Ethernet IP Settings")); + + addFormIPBox(F("ESP Ethernet IP"), F("espethip"), Settings.ETH_IP); + addFormIPBox(F("ESP Ethernet Gateway"), F("espethgateway"), Settings.ETH_Gateway); + addFormIPBox(F("ESP Ethernet Subnetmask"), F("espethsubnet"), Settings.ETH_Subnet); + addFormIPBox(F("ESP Ethernet DNS"), F("espethdns"), Settings.ETH_DNS); + addFormNote(F("Leave empty for DHCP")); + + addFormSubHeader(F("Ethernet")); + + { + const __FlashStringHelper *options[] = { + toString(EthPhyType_t::notSet), +# if ESP_IDF_VERSION_MAJOR >= 5 +# if CONFIG_ETH_SPI_ETHERNET_DM9051 + toString(EthPhyType_t::DM9051), +# endif // if CONFIG_ETH_SPI_ETHERNET_DM9051 +# if CONFIG_ETH_SPI_ETHERNET_W5500 + toString(EthPhyType_t::W5500), +# endif // if CONFIG_ETH_SPI_ETHERNET_W5500 +# if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL + toString(EthPhyType_t::KSZ8851), +# endif // if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL +# endif // if ESP_IDF_VERSION_MAJOR >= 5 + }; + const int ids[] = { + static_cast(EthPhyType_t::notSet), + +# if ESP_IDF_VERSION_MAJOR >= 5 +# if CONFIG_ETH_SPI_ETHERNET_DM9051 + static_cast(EthPhyType_t::DM9051), +# endif // if CONFIG_ETH_SPI_ETHERNET_DM9051 +# if CONFIG_ETH_SPI_ETHERNET_W5500 + static_cast(EthPhyType_t::W5500), +# endif // if CONFIG_ETH_SPI_ETHERNET_W5500 +# if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL + static_cast(EthPhyType_t::KSZ8851), +# endif // if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL +# endif // if ESP_IDF_VERSION_MAJOR >= 5 + }; + + FormSelectorOptions selector(NR_ELEMENTS(ids), options, ids); + auto params = NW004_makeWebFormItemParams(NW004_KEY_ETH_PHY_TYPE); + params._defaultIntValue = static_cast(EthPhyType_t::notSet); + showFormSelector(*_kvs, selector, params); + } + { + auto params = NW004_makeWebFormItemParams(NW004_KEY_ETH_PHY_ADDR); + params._min = -1; + params._max = 127; + params._defaultIntValue = 1; + showWebformItem(*_kvs, params); + addFormNote(F("I²C-address of Ethernet PHY")); + } + { + const int gpio_keys[] = { + NW004_KEY_ETH_PIN_CS, + NW004_KEY_ETH_PIN_IRQ, + NW004_KEY_ETH_PIN_RST + }; + + for (int i = 0; i < NR_ELEMENTS(gpio_keys); ++i) + { + const int key = gpio_keys[i]; + KVS_StorageType::Enum storageType; + const __FlashStringHelper *id = getLabelString(key, false, storageType); + PinSelectPurpose purpose = PinSelectPurpose::Ethernet; + String label = formatGpioLabel(key, purpose); + + int8_t pin = -1; + _kvs->getValue(key, pin); + + addFormPinSelect(purpose, label, id, pin); + } + } +} + +void NW004_data_struct_ETH_SPI::webform_save(EventStruct *event) +{ + // TODO TD-er: Move this to a central function, like done with import/export + int32_t key = getNextKey(-1); + + while (key >= 0) { + KVS_StorageType::Enum storageType; + const __FlashStringHelper *id = getLabelString(key, false, storageType); + storeWebformItem(*_kvs, key, storageType, id); + key = getNextKey(key); + } + _store(); +} + +bool NW004_data_struct_ETH_SPI::webform_getPort(KeyValueWriter *writer) { return true; } + +bool NW004_data_struct_ETH_SPI::init(EventStruct *event) +{ + _load(); + ETHConnectRelaxed(); + + return true; +} + +bool NW004_data_struct_ETH_SPI::exit(EventStruct *event) { return true; } + +NWPluginData_static_runtime * NW004_data_struct_ETH_SPI::getNWPluginData_static_runtime() { + return ESPEasy::net::eth::ETH_NWPluginData_static_runtime::getNWPluginData_static_runtime(_networkIndex); +} + +bool NW004_data_struct_ETH_SPI::write_Eth_HW_Address(KeyValueWriter *writer) +{ + if (writer == nullptr) { return false; } + auto data = getNWPluginData_static_runtime(); + auto iface = ESPEasy::net::eth::ETH_NWPluginData_static_runtime::getInterface(_networkIndex); + + if (!(data && iface)) { return false; } + const int phyType_int = _kvs->getValueAsInt_or_default(NW004_KEY_ETH_PHY_TYPE, -1); + + if (phyType_int == -1) { return false; } + return ESPEasy::net::write_Eth_HW_Address( + static_cast(phyType_int), + iface, + writer); +} + +bool NW004_data_struct_ETH_SPI::write_Eth_port(KeyValueWriter *writer) +{ + if (writer == nullptr) { return false; } + const ESPEasy::net::EthPhyType_t phyType = static_cast(_kvs->getValueAsInt(NW004_KEY_ETH_PHY_TYPE)); + + if (!isValid(phyType)) { return false; } + + int8_t spi_gpios[3]{}; + + if (!Settings.getSPI_pins(spi_gpios)) { return false; } + const __FlashStringHelper*labels[] = { + F("CLK"), F("MISO"), F("MOSI"), F("CS"), F("IRQ"), F("RST") }; + const int pins[] = { + spi_gpios[0], + spi_gpios[1], + spi_gpios[2], + (int)_kvs->getValueAsInt(NW004_KEY_ETH_PIN_CS), + (int)_kvs->getValueAsInt(NW004_KEY_ETH_PIN_IRQ), + (int)_kvs->getValueAsInt(NW004_KEY_ETH_PIN_RST) }; + return write_NetworkPort(labels, pins, NR_ELEMENTS(labels), writer); +} + +void NW004_data_struct_ETH_SPI::ethResetGPIOpins() { + // fix an disconnection issue after rebooting Olimex POE - this forces a clean state for all GPIO involved in RMII + // Thanks to @s-hadinger and @Jason2866 + // Resetting state of power pin is done in ethPower() + addLog(LOG_LEVEL_INFO, F("ethResetGPIOpins()")); + gpio_reset_pin((gpio_num_t)_kvs->getValueAsInt(NW004_KEY_ETH_PIN_CS)); + gpio_reset_pin((gpio_num_t)_kvs->getValueAsInt(NW004_KEY_ETH_PIN_IRQ)); +# if CONFIG_ETH_USE_ESP32_EMAC && FEATURE_ETHERNET + + gpio_reset_pin(GPIO_NUM_19); // EMAC_TXD0 - hardcoded + gpio_reset_pin(GPIO_NUM_21); // EMAC_TX_EN - hardcoded + gpio_reset_pin(GPIO_NUM_22); // EMAC_TXD1 - hardcoded + gpio_reset_pin(GPIO_NUM_25); // EMAC_RXD0 - hardcoded + gpio_reset_pin(GPIO_NUM_26); // EMAC_RXD1 - hardcoded + gpio_reset_pin(GPIO_NUM_27); // EMAC_RX_CRS_DV - hardcoded +# endif // if CONFIG_ETH_USE_ESP32_EMAC && FEATURE_ETHERNET + + /* + switch (Settings.ETH_Clock_Mode) { + case EthClockMode_t::Ext_crystal_osc: // ETH_CLOCK_GPIO0_IN + case EthClockMode_t::Int_50MHz_GPIO_0: // ETH_CLOCK_GPIO0_OUT + gpio_reset_pin(GPIO_NUM_0); + break; + case EthClockMode_t::Int_50MHz_GPIO_16: // ETH_CLOCK_GPIO16_OUT + gpio_reset_pin(GPIO_NUM_16); + break; + case EthClockMode_t::Int_50MHz_GPIO_17_inv: // ETH_CLOCK_GPIO17_OUT + gpio_reset_pin(GPIO_NUM_17); + break; + } + */ + delay(1); + +} + +bool NW004_data_struct_ETH_SPI::ethCheckSettings() { + const ESPEasy::net::EthPhyType_t phyType = static_cast(_kvs->getValueAsInt(NW004_KEY_ETH_PHY_TYPE)); + + const int rstPin = _kvs->getValueAsInt_or_default(NW004_KEY_ETH_PIN_RST, -1); + const int irqPin = _kvs->getValueAsInt_or_default(NW004_KEY_ETH_PIN_IRQ, -1); + + return isValid(phyType) + && validGpio(_kvs->getValueAsInt(NW004_KEY_ETH_PIN_CS)) + && ((validGpio(irqPin) || (irqPin == -1)) && + (validGpio(rstPin) || (rstPin == -1)) + ); // IRQ and RST are optional +} + +void NW004_data_struct_ETH_SPI::ethPrintSettings() { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log; + + if (log.reserve(115)) { + + const ESPEasy::net::EthPhyType_t phyType = static_cast(_kvs->getValueAsInt(NW004_KEY_ETH_PHY_TYPE)); + + log += F("ETH PHY Type: "); + log += toString(phyType); + log += F(" PHY Addr: "); + log += _kvs->getValueAsInt(NW004_KEY_ETH_PHY_ADDR); + log += strformat(F(" CS: %d IRQ: %d RST: %d"), + _kvs->getValueAsInt(NW004_KEY_ETH_PIN_CS), + _kvs->getValueAsInt(NW004_KEY_ETH_PIN_IRQ), + _kvs->getValueAsInt(NW004_KEY_ETH_PIN_RST)); + addLogMove(LOG_LEVEL_INFO, log); + } + } +} + +bool NW004_data_struct_ETH_SPI::ETHConnectRelaxed() { + auto data = getNWPluginData_static_runtime(); + + auto iface = ESPEasy::net::eth::ETH_NWPluginData_static_runtime::getInterface(_networkIndex); + + if (!(data && iface)) { return false; } + + if (data->started() && data->connected()) { + return EthLinkUp(); + } + ethPrintSettings(); + + if (!ethCheckSettings()) + { + addLog(LOG_LEVEL_ERROR, F("ETH: Settings not correct!!!")); + data->mark_stop(); + return false; + } + + data->mark_begin_establish_connection(); + + bool success = data->started(); + + if (!success) { +# if FEATURE_USE_IPV6 + + if (Settings.EnableIPv6()) { + iface->enableIPv6(true); + } +# endif // if FEATURE_USE_IPV6 + + const ESPEasy::net::EthPhyType_t phyType = static_cast(_kvs->getValueAsInt(NW004_KEY_ETH_PHY_TYPE)); + + const int phy_addr = _kvs->getValueAsInt(NW004_KEY_ETH_PHY_ADDR); + const int rstPin = _kvs->getValueAsInt(NW004_KEY_ETH_PIN_RST); + const int csPin = _kvs->getValueAsInt(NW004_KEY_ETH_PIN_CS); + const int irqPin = _kvs->getValueAsInt(NW004_KEY_ETH_PIN_IRQ); + + + spi_host_device_t SPI_host = Settings.getSPI_host(); + + if (SPI_host == spi_host_device_t::SPI_HOST_MAX) { + addLog(LOG_LEVEL_ERROR, F("SPI not enabled")); + # ifdef ESP32C3 + + // FIXME TD-er: Fallback for ETH01-EVO board + SPI_host = spi_host_device_t::SPI2_HOST; + Settings.InitSPI = static_cast(SPI_Options_e::UserDefined); + Settings.SPI_SCLK_pin = 7; + Settings.SPI_MISO_pin = 3; + Settings.SPI_MOSI_pin = 10; + # endif // ifdef ESP32C3 + } + + // else + { +# if ETH_SPI_SUPPORTS_CUSTOM + success = iface->begin( + to_ESP_phy_type(phyType), + phy_addr, + csPin, + irqPin, + rstPin, + SPI); +# else // if ETH_SPI_SUPPORTS_CUSTOM + success = iface->begin( + to_ESP_phy_type(phyType), + phy_addr, + csPin, + irqPin, + rstPin, + SPI_host, + static_cast(Settings.SPI_SCLK_pin), + static_cast(Settings.SPI_MISO_pin), + static_cast(Settings.SPI_MOSI_pin)); +# endif // if ETH_SPI_SUPPORTS_CUSTOM + } + } + + if (success) { + // FIXME TD-er: Not sure if this is correctly set to false + // EthEventData.ethConnectAttemptNeeded = false; + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + PrintToString p2s; + KeyValueWriter_WebForm writer(false, &p2s); + writer.setSummaryValueOnly(); + + if (write_Eth_Show_Connected(*iface, &writer)) { + addLog(LOG_LEVEL_INFO, concat(F("ETH : "), p2s.get())); + } + } + + if (EthLinkUp()) { + // We might miss the connected event, since we are already connected. + data->mark_connected(); + } + } else { + addLog(LOG_LEVEL_ERROR, F("ETH : Failed to initialize ETH")); + } + return success; +} + +} // namespace eth +} // namespace net +} // namespace ESPEasy + +#endif // ifdef USES_NW004 diff --git a/src/ESPEasy/net/NWPluginStructs/NW004_data_struct_ETH_SPI.h b/src/ESPEasy/net/NWPluginStructs/NW004_data_struct_ETH_SPI.h new file mode 100644 index 000000000..86a007771 --- /dev/null +++ b/src/ESPEasy/net/NWPluginStructs/NW004_data_struct_ETH_SPI.h @@ -0,0 +1,68 @@ +#pragma once + +#include "../_NWPlugin_Helper.h" +#ifdef USES_NW004 + +#include "../eth/ETH_NWPluginData_static_runtime.h" + +namespace ESPEasy { +namespace net { +namespace eth { + +struct NW004_data_struct_ETH_SPI : public NWPluginData_base { + + // See: ESPEasy_key_value_store_import_export::LabelStringFunction + static const __FlashStringHelper* getLabelString(uint32_t key, + bool displayString, + KVS_StorageType::Enum& storageType); + + // When queried with a key of -1, it will return the first key index + // Return next key, or -2 when no next key exists. + // See: ESPEasy_key_value_store_import_export::NextKeyFunction + static int32_t getNextKey(int32_t key); + + static void loadDefaults(ESPEasy_key_value_store *kvs, + ESPEasy::net::networkIndex_t networkIndex, + ESPEasy::net::nwpluginID_t nwPluginID); + + + NW004_data_struct_ETH_SPI(networkIndex_t networkIndex, NetworkInterface *netif); + ~NW004_data_struct_ETH_SPI(); + + void webform_load(EventStruct *event); + void webform_save(EventStruct *event); + + bool webform_getPort(KeyValueWriter *writer); + + bool init(EventStruct *event); + + bool exit(EventStruct *event); + + NWPluginData_static_runtime* getNWPluginData_static_runtime(); + + bool write_Eth_HW_Address(KeyValueWriter *writer); + bool write_Eth_port(KeyValueWriter *writer); + +private: + + static String formatGpioLabel(uint32_t key, + PinSelectPurpose& purpose, + bool shortNotation = false); + +void ethResetGPIOpins(); + +bool ethCheckSettings(); + +void ethPrintSettings(); + +bool ETHConnectRelaxed(); + +}; + + +} // namespace eth +} // namespace net +} // namespace ESPEasy + + +#endif // ifdef USES_NW004 diff --git a/src/ESPEasy/net/NWPluginStructs/NW005_data_struct_PPP_modem.cpp b/src/ESPEasy/net/NWPluginStructs/NW005_data_struct_PPP_modem.cpp new file mode 100644 index 000000000..779789473 --- /dev/null +++ b/src/ESPEasy/net/NWPluginStructs/NW005_data_struct_PPP_modem.cpp @@ -0,0 +1,1183 @@ +#include "../NWPluginStructs/NW005_data_struct_PPP_modem.h" + +#ifdef USES_NW005 + +# include "../../../src/ESPEasyCore/Controller.h" // For SendStatus (should we move that function?) + +# include "../../../src/Globals/Settings.h" + +# include "../../../src/Helpers/ESPEasy_time_calc.h" +# include "../../../src/Helpers/StringConverter.h" +# include "../../../src/Helpers/_Plugin_Helper_serial.h" + +# include "../../../src/WebServer/HTML_wrappers.h" +# include "../../../src/WebServer/Markup.h" +# include "../../../src/WebServer/Markup_Forms.h" +# include "../../../src/WebServer/ESPEasy_key_value_store_webform.h" + +# include "../Globals/NetworkState.h" +# include "../Helpers/_NWPlugin_Helper_webform.h" +# include +# include + +// # include +// #include + +# define NW_PLUGIN_ID 5 +# define NW_PLUGIN_INTERFACE PPP + +namespace ESPEasy { +namespace net { +namespace ppp { + +static NWPluginData_static_runtime stats_and_cache(&NW_PLUGIN_INTERFACE, F("PPP")); + + +// Keys as used in the Key-value-store +# define NW005_KEY_SERIAL_PORT 1 +# define NW005_KEY_PIN_RX 2 +# define NW005_KEY_PIN_TX 3 +# define NW005_KEY_PIN_RTS 4 +# define NW005_KEY_PIN_CTS 5 +# define NW005_KEY_PIN_RESET 6 +# define NW005_KEY_PIN_RESET_ACTIVE_LOW 7 +# define NW005_KEY_PIN_RESET_DELAY 8 +# define NW005_KEY_BAUDRATE 9 +# define NW005_KEY_FLOWCTRL 10 +# define NW005_KEY_MODEM_MODEL 11 +# define NW005_KEY_APN 12 +# define NW005_KEY_SIM_PIN 13 +# define NW005_KEY_PIN_DTR 14 + +const __FlashStringHelper * NW005_data_struct_PPP_modem::getLabelString(uint32_t key, bool displayString, KVS_StorageType::Enum& storageType) +{ + storageType = KVS_StorageType::Enum::int8_type; + + switch (key) + { + case NW005_KEY_SERIAL_PORT: + return displayString ? F("Serial Port") : F("serPort"); + case NW005_KEY_PIN_RX: return F("RX"); + case NW005_KEY_PIN_TX: return F("TX"); + case NW005_KEY_PIN_RTS: return F("RTS"); + case NW005_KEY_PIN_CTS: return F("CTS"); + case NW005_KEY_PIN_DTR: return F("DTR"); + case NW005_KEY_PIN_RESET: return displayString ? F("Reset") : F("rst"); + case NW005_KEY_PIN_RESET_ACTIVE_LOW: + storageType = KVS_StorageType::Enum::bool_type; + return displayString ? F("Reset Active Low") : F("rst_act_low"); + case NW005_KEY_PIN_RESET_DELAY: + storageType = KVS_StorageType::Enum::uint16_type; + return displayString ? F("Reset Delay") : F("rst_delay"); + case NW005_KEY_BAUDRATE: + storageType = KVS_StorageType::Enum::uint32_type; + return displayString ? F("Baud rate") : F("baudrate"); + case NW005_KEY_FLOWCTRL: + return displayString ? F("Flow Control") : F("flowctrl"); + case NW005_KEY_MODEM_MODEL: return displayString ? F("Modem Model") : F("mmodel"); + case NW005_KEY_APN: + storageType = KVS_StorageType::Enum::string_type; + return displayString ? F("Access Point Name (APN)") : F("apn"); + case NW005_KEY_SIM_PIN: + storageType = KVS_StorageType::Enum::string_type; + return displayString ? F("SIM Card Pin") : F("pin"); + + } + return F(""); +} + +int32_t NW005_data_struct_PPP_modem::getNextKey(int32_t key) +{ + if (key == -1) { return NW005_KEY_SERIAL_PORT; } + + if (key < NW005_KEY_PIN_DTR) { return key + 1; } + return -2; +} + +int32_t NW005_data_struct_PPP_modem::getNextKey_noCredentials(int32_t key) +{ + if (key == -1) { return NW005_KEY_SERIAL_PORT; } + + if (key < NW005_KEY_MODEM_MODEL) { return key + 1; } + + if (key < NW005_KEY_PIN_DTR) { return NW005_KEY_PIN_DTR; } + return -2; +} + +NW005_data_struct_PPP_modem::NW005_data_struct_PPP_modem(networkIndex_t networkIndex) + : NWPluginData_base(nwpluginID_t(NW_PLUGIN_ID), networkIndex, &NW_PLUGIN_INTERFACE) +{ + stats_and_cache.clear(networkIndex); + nw_event_id = Network.onEvent(NW005_data_struct_PPP_modem::onEvent); +} + +WebFormItemParams NW005_makeWebFormItemParams(uint32_t key) { + KVS_StorageType::Enum storageType; + const __FlashStringHelper*label = NW005_data_struct_PPP_modem::getLabelString(key, true, storageType); + const __FlashStringHelper*id = NW005_data_struct_PPP_modem::getLabelString(key, false, storageType); + + return WebFormItemParams(label, id, storageType, key); +} + +NW005_data_struct_PPP_modem::~NW005_data_struct_PPP_modem() { + if (_modem_task_data.modem_taskHandle) { + vTaskDelete(_modem_task_data.modem_taskHandle); + _modem_task_data.modem_taskHandle = NULL; + } + NW_PLUGIN_INTERFACE.mode(ESP_MODEM_MODE_COMMAND); + + const int dtrPin = _kvs->getValueAsInt_or_default(NW005_KEY_PIN_DTR, -1); + + if (dtrPin != -1) { + digitalWrite(dtrPin, LOW); + } + + NW_PLUGIN_INTERFACE.end(); + _modem_task_data.modem_initialized = false; + + if (nw_event_id != 0) { + Network.removeEvent(nw_event_id); + } + nw_event_id = 0; + stats_and_cache.processEvent_and_clear(); +} + +enum class NW005_modem_model { + generic = 1, + SIM7600 = 2, + SIM7070 = 3, + SIM7000 = 4, + BG96 = 5, + SIM800 = 6 + +}; + +const __FlashStringHelper* toString(NW005_modem_model NW005_modemmodel) +{ + switch (NW005_modemmodel) + { + case NW005_modem_model::generic: return F("Generic"); + case NW005_modem_model::SIM7600: return F("SIM7600"); + case NW005_modem_model::SIM7070: return F("SIM7070"); + case NW005_modem_model::SIM7000: return F("SIM7000"); + case NW005_modem_model::BG96: return F("BG96"); + case NW005_modem_model::SIM800: return F("SIM800"); + } + return F("unknown"); +} + +ppp_modem_model_t to_ppp_modem_model_t(NW005_modem_model NW005_modemmodel) +{ + switch (NW005_modemmodel) + { + case NW005_modem_model::generic: return PPP_MODEM_GENERIC; + case NW005_modem_model::SIM7600: return PPP_MODEM_SIM7600; + case NW005_modem_model::SIM7070: return PPP_MODEM_SIM7070; + case NW005_modem_model::SIM7000: return PPP_MODEM_SIM7000; + case NW005_modem_model::BG96: return PPP_MODEM_BG96; + case NW005_modem_model::SIM800: return PPP_MODEM_SIM800; + } + return PPP_MODEM_GENERIC; +} + +int doGetRSSI() +{ + const int rssi_raw = NW_PLUGIN_INTERFACE.RSSI(); + + if (rssi_raw == 0) { return -113; } + + if (rssi_raw == 99) { return 0; } + return map(rssi_raw, 2, 30, -109, -53); +} + +String NW005_data_struct_PPP_modem::getRSSI() const +{ + if (!_modem_task_data.modem_initialized) { return F("-"); } + const int rssi_raw = NW_PLUGIN_INTERFACE.RSSI(); + + if (rssi_raw == 99) { return F("-"); } // Not known or not detectable + + if (rssi_raw == 0) { return F("<= -113"); } + + if (rssi_raw >= 31) { return F(">= -51"); } + return String(map(rssi_raw, 2, 30, -109, -53)); +} + +String NW005_data_struct_PPP_modem::getBER() const +{ + if (_modem_task_data.modem_initialized) { + switch (NW_PLUGIN_INTERFACE.BER()) + { + case 0: return F("<0.01 %"); + case 1: return F("0.01 % ... 0.1 %"); + case 2: return F("0.1 % ... 0.5 %"); + case 3: return F("0.5 % ... 1 %"); + case 4: return F("1 % ... 2 %"); + case 5: return F("2 % ... 4 %"); + case 6: return F("4 % ... 8 %"); + case 7: return F(">= 8 %"); + case 99: break; // Not known or not detectable + } + } + return F("-"); +} + +float NW005_data_struct_PPP_modem::getBER_float() const +{ + if (_modem_task_data.modem_initialized) { + switch (NW_PLUGIN_INTERFACE.BER()) + { + case 0: return 0.01f; // F("<0.01 %"); + case 1: return 0.1f; // F("0.01 % ... 0.1 %"); + case 2: return 0.5f; // F("0.1 % ... 0.5 %"); + case 3: return 1.0f; // F("0.5 % ... 1 %"); + case 4: return 2.0f; // F("1 % ... 2 %"); + case 5: return 4.0f; // F("2 % ... 4 %"); + case 6: return 8.0f; // F("4 % ... 8 %"); + case 7: return 16.0f; // F(">= 8 %"); + case 99: break; // Not known or not detectable + } + } + return NAN; +} + +bool NW005_data_struct_PPP_modem::attached() const +{ + if (!_modem_task_data.modem_initialized) { return false; } + return NW_PLUGIN_INTERFACE.attached(); +} + +String NW005_data_struct_PPP_modem::IMEI() const +{ + if (!_modem_task_data.modem_initialized) { return EMPTY_STRING; } + return NW_PLUGIN_INTERFACE.IMEI(); +} + +String NW005_data_struct_PPP_modem::operatorName() const +{ + if (!_modem_task_data.modem_initialized) { return EMPTY_STRING; } + return NW_PLUGIN_INTERFACE.operatorName(); +} + +const __FlashStringHelper* NW005_decode_label(int sysmode_index, uint8_t i, String& value_str, bool& has_dBm_unit) +{ + const __FlashStringHelper*res = F(""); + + switch (sysmode_index) + { + case 0: + { + // No service system mode + const __FlashStringHelper*labels[] + { + F("System Mode"), F("Operation Mode") + }; + + if (i < NR_ELEMENTS(labels)) { res = labels[i]; } + break; + } + case 1: + { + // GSM system mode + const __FlashStringHelper*labels[] + { + F("System Mode"), F("Operation Mode"), F("MCC-MNC"), F("LAC"), F("Cell ID"), F("Absolute RF Ch Num"), F("RxLev"), + F("Track LO Adjust"), F("C1-C2") + }; + + if (i < NR_ELEMENTS(labels)) { res = labels[i]; } + break; + } + case 2: + { + // WCDMA system mode + const __FlashStringHelper*labels[] + { + F("System Mode"), F("Operation Mode"), F("MCC-MNC"), F("LAC"), F("Cell ID"), F("Frequency Band"), F("PSC"), F("Freq"), F("SSC"), + F("EC/IO"), F("RSCP"), F("Qual"), F("RxLev"), F("TXPWR") + }; + + if (i < NR_ELEMENTS(labels)) { res = labels[i]; } + break; + } + case 3: + { + // LTE system mode + const __FlashStringHelper*labels[] + { + F("System Mode"), F("Operation Mode"), F("MCC-MNC"), F("TAC"), F("SCellID"), F("PCellID"), F("Frequency Band"), F("earfcn"), + F("dlbw"), F("ulbw"), F("RSRQ"), F("RSRP"), F("RSSI"), F("RSSNR") + }; + + if (i < NR_ELEMENTS(labels)) { + res = labels[i]; + + switch (i) + { + case 10: + // RSRQ + { + float val = value_str.toInt(); + val -= 40; + val /= 2.0f; + value_str = String(val, 1); + has_dBm_unit = true; + break; + } + case 11: + // RSRP + value_str = value_str.toInt() - 140; + has_dBm_unit = true; + break; + case 12: + // RSSI + value_str = value_str.toInt() - 110; + has_dBm_unit = true; + break; + } + + } + break; + } + } + return res; +} + +void NW005_data_struct_PPP_modem::webform_load_UE_system_information(KeyValueWriter*writer) +{ + if (!NW_PLUGIN_INTERFACE.attached()) { + // clear cached string + _modem_task_data.AT_CPSI.clear(); + return; + } + + if (NW_PLUGIN_INTERFACE.mode() != ESP_MODEM_MODE_CMUX) { + NW_PLUGIN_INTERFACE.mode(ESP_MODEM_MODE_CMUX); + } + + + String res = write_AT_cmd(F("AT+CPSI?"), 1000); + + if (res.isEmpty()) { res = _modem_task_data.AT_CPSI; } + + if (!res.isEmpty() /* && res.startsWith(F("+CPSI"))*/) { + int start_index = 0; + int end_index = res.indexOf(','); + const String systemMode = res.substring(start_index, end_index); + addLog(LOG_LEVEL_INFO, concat(F("PPP: UE sysinfo: "), systemMode)); + + const __FlashStringHelper*sysmode_str[] = { + F("NO SERVICE"), F("GSM"), F("WCDMA"), F("LTE") + }; + { + String provider_name = write_AT_cmd(F("AT+CSPN?"), 1000); + + if (!provider_name.isEmpty()) { + int pos1 = provider_name.indexOf('"'); + int pos2 = provider_name.lastIndexOf('"'); + + if ((pos1 != -1) && (pos2 != pos1)) { + writer->write({ F("Provider"), provider_name.substring(pos1 + 1, pos2) }); + } + } + } + int sysmode_index = -1; + + for (int i = 0; i < NR_ELEMENTS(sysmode_str) && sysmode_index == -1; ++i) { + if (systemMode.endsWith(sysmode_str[i])) { sysmode_index = i; } + + } + + if (sysmode_index == -1) { return; } + + // Update cached string + _modem_task_data.AT_CPSI = res; + + // addFormSubHeader(F("UE System Information")); + + res += ','; // Add trailing comma so we're not missing the last element + + for (int i = 0; start_index < res.length() && end_index != -1 && i < 15; ++i) + { + String value_str = res.substring(start_index, end_index); + bool has_dBm_unit = false; + const String label = NW005_decode_label(sysmode_index, i, value_str, has_dBm_unit); + + if (!label.isEmpty()) { + if (i == 0) { + // We have some leading characters left here, so use the 'clean' strings + KeyValueStruct kv(label, sysmode_str[sysmode_index]); + + if (has_dBm_unit) { +# if FEATURE_TASKVALUE_UNIT_OF_MEASURE + kv.setUnit(UOM_dBm); +# endif + } + + writer->write(kv); + } else { + KeyValueStruct kv(label, value_str, KeyValueStruct::Format::PreFormatted); + + if (has_dBm_unit) { +# if FEATURE_TASKVALUE_UNIT_OF_MEASURE + kv.setUnit(UOM_dBm); +# endif + } + + writer->write(kv); + } + } + start_index = end_index + 1; + end_index = res.indexOf(',', start_index); + } + + } + + if (NW_PLUGIN_INTERFACE.attached()) { + NW_PLUGIN_INTERFACE.mode(ESP_MODEM_MODE_DATA); + } +} + +void NW005_data_struct_PPP_modem::webform_load(EventStruct *event) +{ + _load(); + + addFormSubHeader(F("Device Settings")); + { + const int ids[] = { + static_cast(NW005_modem_model::generic), + static_cast(NW005_modem_model::SIM7600), + static_cast(NW005_modem_model::SIM7070), + static_cast(NW005_modem_model::SIM7000), + static_cast(NW005_modem_model::BG96), + static_cast(NW005_modem_model::SIM800) + }; + + const __FlashStringHelper*options[] = { + toString(NW005_modem_model::generic), + toString(NW005_modem_model::SIM7600), + toString(NW005_modem_model::SIM7070), + toString(NW005_modem_model::SIM7000), + toString(NW005_modem_model::BG96), + toString(NW005_modem_model::SIM800) + }; + + FormSelectorOptions selector(NR_ELEMENTS(ids), options, ids); + showFormSelector(*_kvs, selector, NW005_makeWebFormItemParams(NW005_KEY_MODEM_MODEL)); + } + + { + // TODO TD-er: We cannot use ESPEasySerialPort here as PPPClass needs to handle the pins using periman + const int ids[] = { + static_cast(ESPEasySerialPort::serial0) +# if USABLE_SOC_UART_NUM > 1 + , static_cast(ESPEasySerialPort::serial1) +# endif +# if USABLE_SOC_UART_NUM > 2 + , static_cast(ESPEasySerialPort::serial2) +# endif +# if USABLE_SOC_UART_NUM > 3 + , static_cast(ESPEasySerialPort::serial3) +# endif +# if USABLE_SOC_UART_NUM > 4 + , static_cast(ESPEasySerialPort::serial4) +# endif +# if USABLE_SOC_UART_NUM > 5 + , static_cast(ESPEasySerialPort::serial5) +# endif + }; + + constexpr int NR_ESPEASY_SERIAL_TYPES = NR_ELEMENTS(ids); + const __FlashStringHelper*options[] = { + serialHelper_getSerialTypeLabel(ESPEasySerialPort::serial0) +# if USABLE_SOC_UART_NUM > 1 + , serialHelper_getSerialTypeLabel(ESPEasySerialPort::serial1) +# endif +# if USABLE_SOC_UART_NUM > 2 + , serialHelper_getSerialTypeLabel(ESPEasySerialPort::serial2) +# endif +# if USABLE_SOC_UART_NUM > 3 + , serialHelper_getSerialTypeLabel(ESPEasySerialPort::serial3) +# endif +# if USABLE_SOC_UART_NUM > 4 + , serialHelper_getSerialTypeLabel(ESPEasySerialPort::serial4) +# endif +# if USABLE_SOC_UART_NUM > 5 + , serialHelper_getSerialTypeLabel(ESPEasySerialPort::serial5) +# endif + }; + + FormSelectorOptions selector(NR_ELEMENTS(ids), options, ids); + showFormSelector(*_kvs, selector, NW005_makeWebFormItemParams(NW005_KEY_SERIAL_PORT)); + } + + const int gpio_keys[] = { + NW005_KEY_PIN_RX, + NW005_KEY_PIN_TX, + NW005_KEY_PIN_RTS, + NW005_KEY_PIN_CTS, + NW005_KEY_PIN_DTR, + NW005_KEY_PIN_RESET + }; + + for (int i = 0; i < NR_ELEMENTS(gpio_keys); ++i) + { + const int key = gpio_keys[i]; + KVS_StorageType::Enum storageType; + const __FlashStringHelper *id = getLabelString(key, false, storageType); + PinSelectPurpose purpose = PinSelectPurpose::Generic; + String label = NW005_formatGpioLabel(key, purpose); + + int8_t pin = -1; + _kvs->getValue(key, pin); + + addFormPinSelect(purpose, label, id, pin); + } + showWebformItem( + *_kvs, + NW005_makeWebFormItemParams(NW005_KEY_PIN_RESET_ACTIVE_LOW)); + { + auto params = NW005_makeWebFormItemParams(NW005_KEY_PIN_RESET_DELAY); + params._max = 2000; + params._defaultIntValue = 200; + showWebformItem(*_kvs, params); + addUnit(F("ms")); + + } + { + const int ids[] = { + ESP_MODEM_FLOW_CONTROL_NONE, + ESP_MODEM_FLOW_CONTROL_SW, + ESP_MODEM_FLOW_CONTROL_HW + }; + const __FlashStringHelper*options[] = { + F("None"), + F("Software"), + F("Hardware") + }; + + FormSelectorOptions selector(NR_ELEMENTS(ids), options, ids); + showFormSelector(*_kvs, selector, NW005_makeWebFormItemParams(NW005_KEY_FLOWCTRL)); + addFormNote(F("Only set flow control if RTS and CTS are used")); + } + { + auto params = NW005_makeWebFormItemParams(NW005_KEY_BAUDRATE); + params._max = 10000000; + params._defaultIntValue = 115200; + showWebformItem(*_kvs, params); + } + + addFormSubHeader(F("Connection Settings")); + { + auto params = NW005_makeWebFormItemParams(NW005_KEY_APN); + params._maxLength = 64; + showWebformItem(*_kvs, params); + addFormNote(F("Optional, can be left empty")); + } + { + auto params = NW005_makeWebFormItemParams(NW005_KEY_SIM_PIN); + params._maxLength = 4; + showWebformItem(*_kvs, params); + addFormNote(F("Only numerical digits")); + } + + if (!Settings.getNetworkEnabled(event->NetworkIndex)) { return; } + + + if (!_modem_task_data.modem_initialized) { + addFormSubHeader(F("Modem State")); + addRowLabel(F("Modem Model")); + + if (_modem_task_data.initializing) { + addHtml(F("Initializing ...")); + } else { + addHtml(F("Not found")); + } + return; + } +} + +void NW005_data_struct_PPP_modem::webform_save(EventStruct *event) +{ + // TODO TD-er: Move this to a central function, like done with import/export + int32_t key = getNextKey(-1); + + while (key >= 0) { + KVS_StorageType::Enum storageType; + const __FlashStringHelper *id = getLabelString(key, false, storageType); + storeWebformItem(*_kvs, key, storageType, id); + key = getNextKey(key); + } + _store(); +} + +bool NW005_data_struct_PPP_modem::webform_getPort(KeyValueWriter *writer) +{ + if (_KVS_initialized() && _load() && writer) { + int serialPort = _kvs->getValueAsInt_or_default(NW005_KEY_SERIAL_PORT, -1); + auto serialTypeLabel = serialHelper_getSerialTypeLabel(static_cast(serialPort)); + + if (serialPort >= 0) { + const uint32_t keys[] { + NW005_KEY_PIN_RX, + NW005_KEY_PIN_TX, + NW005_KEY_PIN_RTS, + NW005_KEY_PIN_CTS, + NW005_KEY_PIN_DTR, + NW005_KEY_PIN_RESET + }; + + KeyValueStruct kv(EMPTY_STRING, serialTypeLabel); + + if (!writer->summaryValueOnly()) { + writer->write({ F("Serial Type"), serialTypeLabel }); + } + + for (int i = 0; i < NR_ELEMENTS(keys); ++i) { + int pin = _kvs->getValueAsInt_or_default(keys[i], -1); + + if (pin >= 0) { + PinSelectPurpose purpose = PinSelectPurpose::Generic; + const bool shortNotation = true; + const String label = NW005_formatGpioLabel(keys[i], purpose, shortNotation); + + if (writer->summaryValueOnly()) { + kv.appendValue(strformat(F("%s: %d"), + label.c_str(), + pin)); + } else { + if (writer->plainText() || !writer->allowHTML()) { + KVS_StorageType::Enum storageType; + writer->write({ getLabelString(keys[i], false, storageType), pin }); + } else { + writer->write({ label, pin }); + } + } + } + } + + if (writer->summaryValueOnly()) { + writer->write(kv); + } + } + return true; + } + return false; +} + +bool NW005_data_struct_PPP_modem::write_ModemState(KeyValueWriter*modemState) +{ + // if (!writer) { return false; } + // auto modemState = writer->createChild(F("Modem State")); + + if (!modemState) { return false; } + + if (!_modem_task_data.modem_initialized) { + modemState->write({ + F("Modem Model"), + _modem_task_data.initializing + ? F("Initializing ...") + : F("Not found") + }); + + return false; + } + # define STATE_WRITE(X, Y) modemState->write({ X, Y \ + }) + # define STATE_WRITE_PRE(X, Y) modemState->write({ X, Y, KeyValueStruct::Format::PreFormatted \ + }) + STATE_WRITE_PRE(F("Modem Model"), NW_PLUGIN_INTERFACE.moduleName()); + STATE_WRITE_PRE(F("IMEI"), NW_PLUGIN_INTERFACE.IMEI()); + STATE_WRITE_PRE(F("IMSI"), NW_PLUGIN_INTERFACE.IMSI()); + + if (NW_PLUGIN_INTERFACE.attached()) { + const String operatorName = NW_PLUGIN_INTERFACE.operatorName(); + STATE_WRITE_PRE(F("Mobile Country Code (MCC)"), operatorName.substring(0, 3)); + STATE_WRITE_PRE(F("Mobile Network Code (MNC)"), operatorName.substring(3)); + modemState->writeNote( + F("See Wikipedia - Mobile Country Code") + ); + + if (NW_PLUGIN_INTERFACE.mode() != ESP_MODEM_MODE_CMUX) { + NW_PLUGIN_INTERFACE.mode(ESP_MODEM_MODE_CMUX); + } + } + { + KeyValueStruct kv(F("RSSI"), getRSSI()); +# if FEATURE_TASKVALUE_UNIT_OF_MEASURE + kv.setUnit(UOM_dBm); +# endif + modemState->write(kv); + } + STATE_WRITE(F("BER"), getBER()); + + STATE_WRITE( + F("Radio State"), + (NW_PLUGIN_INTERFACE.radioState() == 0) ? F("Minimal") : F("Full")); + + { + int networkMode = NW_PLUGIN_INTERFACE.networkMode(); + + if (networkMode >= 0) { + const __FlashStringHelper*mode; + + // Result from AT+CNSMOD Show network system mode + if (networkMode <= 8) { + switch (networkMode) + { + case 0: mode = F("no service"); + break; + case 1: mode = F("GSM"); + break; + case 2: mode = F("GPRS"); + break; + case 3: mode = F("EGPRS (EDGE)"); + break; + case 4: mode = F("WCDMA"); + break; + case 5: mode = F("HSDPA only(WCDMA)"); + break; + case 6: mode = F("HSUPA only(WCDMA)"); + break; + case 7: mode = F("HSPA (HSDPA and HSUPA, WCDMA)"); + break; + case 8: mode = F("LTE"); + break; + } + STATE_WRITE(F("Network Mode"), mode); + } else { + STATE_WRITE(F("Network Mode"), networkMode); + } + } + } + { + int batStatus = NW_PLUGIN_INTERFACE.batteryStatus(); + const __FlashStringHelper*str = F("Not Available"); + + switch (batStatus) + { + case 0: str = F("Not Charging"); + break; + case 1: str = F("Charging"); + break; + case 2: str = F("Charging Done"); + break; + } + STATE_WRITE(F("Battery Status"), str); + + if (batStatus >= 0) { + int batVolt = NW_PLUGIN_INTERFACE.batteryVoltage(); + + if (batVolt >= 0) { + KeyValueStruct kv(F("Battery Voltage"), batVolt); +# if FEATURE_TASKVALUE_UNIT_OF_MEASURE + kv.setUnit(UOM_milliVolt); +# endif + modemState->write(kv); + } + + int batLevel = NW_PLUGIN_INTERFACE.batteryLevel(); + + if (batLevel >= 0) { + STATE_WRITE(F("Battery Level"), batLevel); + } + } + } + # undef STATE_WRITE + # undef STATE_WRITE_PRE + + + // TODO TD-er: Disabled for now, missing PdpContext + + /* + esp_modem_dce_t *handle = NW_PLUGIN_INTERFACE.handle(); + + if (handle) { + int state{}; + + if (esp_modem_get_network_registration_state(handle, state) == ESP_OK) { + addRowLabel(F("Network Registration State")); + const __FlashStringHelper*str = F("Unknown"); + + switch (state) + { + case 0: str = F("Not registered, MT is not currently searching an operator to register to"); + break; + case 1: str = F("Registered, home network"); + break; + case 2: str = F("Not registered, but MT is currently trying to attach or searching an operator to register to"); + break; + case 3: str = F("Registration denied"); + break; + case 4: str = F("Unknown"); + break; + case 5: str = F("Registered, Roaming"); + break; + case 6: str = F("Registered, for SMS only, home network (NB-IoT only)"); + break; + case 7: str = F("Registered, for SMS only, roaming (NB-IoT only)"); + break; + case 8: str = F("Attached for emergency bearer services only"); + break; + case 9: str = F("Registered for CSFB not preferred, home network"); + break; + case 10: str = F("Registered for CSFB not preferred, roaming"); + break; + } + addHtml(str); + } + } + */ + if (NW_PLUGIN_INTERFACE.attached()) { + NW_PLUGIN_INTERFACE.mode(ESP_MODEM_MODE_DATA); + } + + return true; +} + +void NW005_begin_modem_task(void *parameter) +{ + NW005_modem_task_data*modem_task_data = static_cast(parameter); + + if (!modem_task_data->modem_initialized) { + modem_task_data->initializing = true; + + if (modem_task_data->dtrPin != -1) { + digitalWrite(modem_task_data->dtrPin, LOW); + } + + const bool res = + NW_PLUGIN_INTERFACE.begin( + modem_task_data->model, + modem_task_data->uart_num, + modem_task_data->baud_rate); + modem_task_data->initializing = false; + modem_task_data->logString = strformat( + F("PPP: Module Name: %s, IMEI: %s"), + NW_PLUGIN_INTERFACE.moduleName().c_str(), + NW_PLUGIN_INTERFACE.IMEI().c_str()); + + if (res) { + + uint32_t start = millis(); + + do + { + delay(100); + } + while (timePassedSince(start) < 5000 && !NW_PLUGIN_INTERFACE.attached()); + + NW_PLUGIN_INTERFACE.mode(ESP_MODEM_MODE_CMUX); + modem_task_data->AT_CPSI = NW_PLUGIN_INTERFACE.cmd(F("AT+CPSI?"), 3000); + + if (modem_task_data->dtrPin != -1) { + NW_PLUGIN_INTERFACE.cmd(F("AT&D1"), 9000); + digitalWrite(modem_task_data->dtrPin, HIGH); + } + } + modem_task_data->modem_initialized = res; + } + modem_task_data->modem_taskHandle = NULL; + vTaskDelete(modem_task_data->modem_taskHandle); +} + +bool NW005_data_struct_PPP_modem::init(EventStruct *event) +{ + if (!_KVS_initialized()) { + addLog(LOG_LEVEL_ERROR, F("PPP: Could not initialize storage")); + return false; + } + + if (!_load()) { + addLog(LOG_LEVEL_ERROR, F("PPP: Could not load settings")); + return false; + } + + NW_PLUGIN_INTERFACE.setResetPin( + _kvs->getValueAsInt_or_default(NW005_KEY_PIN_RESET, -1), + _kvs->getValueAsInt_or_default(NW005_KEY_PIN_RESET_ACTIVE_LOW, 0), + _kvs->getValueAsInt_or_default(NW005_KEY_PIN_RESET_DELAY, 200)); + + const int rtsPin = _kvs->getValueAsInt_or_default(NW005_KEY_PIN_RTS, -1); + const int ctsPin = _kvs->getValueAsInt_or_default(NW005_KEY_PIN_CTS, -1); + esp_modem_flow_ctrl_t flow_ctl = static_cast(_kvs->getValueAsInt_or_default(NW005_KEY_FLOWCTRL, + ESP_MODEM_FLOW_CONTROL_NONE)); + + if ((rtsPin < 0) || (ctsPin < 0)) { + if (flow_ctl != ESP_MODEM_FLOW_CONTROL_NONE) { + addLog(LOG_LEVEL_INFO, F("PPP: Disable flow control as RTS/CTS are not set")); + flow_ctl = ESP_MODEM_FLOW_CONTROL_NONE; + } + } + + if (!NW_PLUGIN_INTERFACE.setPins( + _kvs->getValueAsInt_or_default(NW005_KEY_PIN_TX, -1), + _kvs->getValueAsInt_or_default(NW005_KEY_PIN_RX, -1), + rtsPin, + ctsPin, + flow_ctl)) + { + addLog(LOG_LEVEL_ERROR, F("PPP: Could not set pins")); + return false; + } + { + String apn; + _kvs->getValue(NW005_KEY_APN, apn); + NW_PLUGIN_INTERFACE.setApn(apn.c_str()); + } + { + String pin; + _kvs->getValue(NW005_KEY_SIM_PIN, pin); + + if (pin.length() >= 4) { + NW_PLUGIN_INTERFACE.setPin(pin.c_str()); + } + } + + _modem_task_data.model = to_ppp_modem_model_t(static_cast( + _kvs->getValueAsInt_or_default(NW005_KEY_MODEM_MODEL, PPP_MODEM_GENERIC))); + + _modem_task_data.uart_num = 1; + { + int8_t serial = 1; + + if (_kvs->getValue(NW005_KEY_SERIAL_PORT, serial)) { + switch (static_cast(serial)) + { + case ESPEasySerialPort::serial0: _modem_task_data.uart_num = 0; + break; +# if USABLE_SOC_UART_NUM > 1 + case ESPEasySerialPort::serial1: _modem_task_data.uart_num = 1; + break; +# endif // if USABLE_SOC_UART_NUM > 1 +# if USABLE_SOC_UART_NUM > 2 + case ESPEasySerialPort::serial2: _modem_task_data.uart_num = 2; + break; +# endif // if USABLE_SOC_UART_NUM > 2 +# if USABLE_SOC_UART_NUM > 3 + case ESPEasySerialPort::serial3: _modem_task_data.uart_num = 3; + break; +# endif // if USABLE_SOC_UART_NUM > 3 +# if USABLE_SOC_UART_NUM > 4 + case ESPEasySerialPort::serial4: _modem_task_data.uart_num = 4; + break; +# endif // if USABLE_SOC_UART_NUM > 4 +# if USABLE_SOC_UART_NUM > 5 + case ESPEasySerialPort::serial5: _modem_task_data.uart_num = 5; + break; +# endif // if USABLE_SOC_UART_NUM > 5 + default: break; + } + } + } + _modem_task_data.baud_rate = _kvs->getValueAsInt_or_default(NW005_KEY_BAUDRATE, 115200); + _modem_task_data.modem_initialized = false; + _modem_task_data.initializing = false; + _modem_task_data.dtrPin = _kvs->getValueAsInt_or_default(NW005_KEY_PIN_DTR, -1); + + stats_and_cache.mark_begin_establish_connection(); + + xTaskCreatePinnedToCore( + NW005_begin_modem_task, // Function that should be called + "PPP.begin()", // Name of the task (for debugging) + 4000, // Stack size (bytes) + &_modem_task_data, // Parameter to pass + 1, // Task priority + &_modem_task_data.modem_taskHandle, // Task handle + xPortGetCoreID() // Core you want to run the task on (0 or 1) + ); + + return true; +} + +bool NW005_data_struct_PPP_modem::exit(EventStruct *event) +{ + if (_modem_task_data.modem_taskHandle) { + vTaskDelete(_modem_task_data.modem_taskHandle); + _modem_task_data.modem_taskHandle = NULL; + } + NW_PLUGIN_INTERFACE.mode(ESP_MODEM_MODE_COMMAND); + NW_PLUGIN_INTERFACE.end(); + _modem_task_data.modem_initialized = false; + return true; +} + +String NW005_data_struct_PPP_modem::write_AT_cmd(const String& cmd, int timeout) +{ + String res; + auto cur_mode = NW_PLUGIN_INTERFACE.mode(); + + if (NW_PLUGIN_INTERFACE.mode() != ESP_MODEM_MODE_CMUX) { + NW_PLUGIN_INTERFACE.mode(ESP_MODEM_MODE_CMUX); + } + res = NW_PLUGIN_INTERFACE.cmd(cmd.c_str(), timeout); + NW_PLUGIN_INTERFACE.mode(cur_mode); + return res; +} + +bool NW005_data_struct_PPP_modem::handle_nwplugin_write(EventStruct *event, String& str) +{ + bool success = false; + const String command = parseString(str, 1); + + if (equals(command, F("ppp"))) { + const String subcommand = parseString(str, 2); + + if (equals(subcommand, F("write"))) { + const String writeCommand = parseStringToEnd(str, 3); + const String res = write_AT_cmd(writeCommand); + addLog(LOG_LEVEL_INFO, strformat( + F("PPP cmd: %s -> %s\n"), + writeCommand.c_str(), + res.c_str())); + success = true; + SendStatus(event, res); + } + } + return success; +} + +# if FEATURE_NETWORK_STATS + +bool NW005_data_struct_PPP_modem::initPluginStats() +{ + networkStatsVarIndex_t networkStatsVarIndex{}; + PluginStats_Config_t displayConfig; + + displayConfig.setAxisPosition(PluginStats_Config_t::AxisPosition::Left); + displayConfig.setEnabled(true); + + displayConfig.setAxisIndex(networkStatsVarIndex); + NWPluginData_base::initPluginStats( + networkStatsVarIndex, + F("RSSI"), + 1, + NAN, + displayConfig); + + ++networkStatsVarIndex; + displayConfig.setAxisIndex(networkStatsVarIndex); + displayConfig.setHidden(true); + NWPluginData_base::initPluginStats( + networkStatsVarIndex, + F("Bit Error Rate (BER)"), + 1, + NAN, + displayConfig); +# if FEATURE_NETWORK_TRAFFIC_COUNT + initPluginStats_trafficCount(++networkStatsVarIndex, true); // TX + initPluginStats_trafficCount(++networkStatsVarIndex, false); // RX +# endif // if FEATURE_NETWORK_TRAFFIC_COUNT + return true; +} + +bool NW005_data_struct_PPP_modem::record_stats() +{ + if ((_plugin_stats_array != nullptr) && _modem_task_data.modem_initialized) { + EventStruct tmpEvent; + size_t valueCount{}; + const int rssi = doGetRSSI(); + tmpEvent.ParfN[valueCount++] = rssi == 0 ? NAN : rssi; + tmpEvent.ParfN[valueCount++] = getBER_float(); + bool trackPeaks = true; + bool onlyUpdateTimestampWhenSame = true; + return pushStatsValues(&tmpEvent, valueCount, trackPeaks, onlyUpdateTimestampWhenSame); + } + return false; +} + +# endif // if FEATURE_NETWORK_STATS + +NWPluginData_static_runtime * NW005_data_struct_PPP_modem::getNWPluginData_static_runtime() { return &stats_and_cache; } + +void NW005_data_struct_PPP_modem::onEvent(arduino_event_id_t event, arduino_event_info_t info) { + // TODO TD-er: Must store flags from events in static (or global) object to act on it later. + switch (event) + { + case ARDUINO_EVENT_PPP_START: + stats_and_cache.mark_start(); + break; + case ARDUINO_EVENT_PPP_STOP: + stats_and_cache.mark_stop(); + break; + case ARDUINO_EVENT_PPP_CONNECTED: + stats_and_cache.mark_connected(); + break; + case ARDUINO_EVENT_PPP_DISCONNECTED: + stats_and_cache.mark_disconnected(); + WiFi.AP.enableNAPT(false); + break; + case ARDUINO_EVENT_PPP_GOT_IP: + stats_and_cache.mark_got_IP(); + + // NW_PLUGIN_INTERFACE.setDefault(); + + if (WiFi.AP.enableNAPT(true)) { + addLog(LOG_LEVEL_INFO, F("WiFi.AP.enableNAPT")); + } + break; +# if FEATURE_USE_IPV6 + case ARDUINO_EVENT_PPP_GOT_IP6: + stats_and_cache.mark_got_IPv6(&info.got_ip6); + break; +# endif // if FEATURE_USE_IPV6 + case ARDUINO_EVENT_PPP_LOST_IP: + stats_and_cache.mark_lost_IP(); + WiFi.AP.enableNAPT(false); + break; + + default: break; + } +} + +String NW005_data_struct_PPP_modem::NW005_formatGpioLabel(uint32_t key, PinSelectPurpose& purpose, bool shortNotation) const +{ + String label; + + if ((key == NW005_KEY_PIN_DTR) || + ((key >= NW005_KEY_PIN_RX) && (key <= NW005_KEY_PIN_RESET))) + { + KVS_StorageType::Enum storageType; + purpose = PinSelectPurpose::Generic; + label = getLabelString(key, true, storageType); + + const bool optional = !shortNotation; + + switch (key) + { + case NW005_KEY_PIN_RX: + purpose = PinSelectPurpose::Serial_input; + label = shortNotation + ? formatGpioName_RX(false) + : formatGpioName_serialRX(false); + break; + case NW005_KEY_PIN_TX: + purpose = PinSelectPurpose::Serial_output; + label = shortNotation + ? formatGpioName_TX(false) + : formatGpioName_serialTX(false); + break; + case NW005_KEY_PIN_CTS: + purpose = PinSelectPurpose::Generic_input; + label = formatGpioName( + label, gpio_direction::gpio_input, optional); + break; + case NW005_KEY_PIN_RTS: + case NW005_KEY_PIN_DTR: + case NW005_KEY_PIN_RESET: + purpose = PinSelectPurpose::Generic_output; + label = formatGpioName( + label, gpio_direction::gpio_output, optional); + break; + } + + } + return label; +} + +} // namespace ppp + +} // namespace net +} // namespace ESPEasy + +#endif // ifdef USES_NW005 diff --git a/src/ESPEasy/net/NWPluginStructs/NW005_data_struct_PPP_modem.h b/src/ESPEasy/net/NWPluginStructs/NW005_data_struct_PPP_modem.h new file mode 100644 index 000000000..b0ab4e8fd --- /dev/null +++ b/src/ESPEasy/net/NWPluginStructs/NW005_data_struct_PPP_modem.h @@ -0,0 +1,100 @@ +#pragma once + +#include "../_NWPlugin_Helper.h" +#ifdef USES_NW005 + +# include "../../../src/Helpers/StringGenerator_GPIO.h" + +# include + + +namespace ESPEasy { +namespace net { +namespace ppp { + +struct NW005_modem_task_data { + ppp_modem_model_t model = PPP_MODEM_GENERIC; + uint8_t uart_num = 1; + int baud_rate = 115200; + bool initializing{}; + bool modem_initialized{}; + int dtrPin = -1; + String logString; + String AT_CPSI; // Result from "AT+CPSI?" + + // This is C-code, so not set to nullptr, but to NULL + TaskHandle_t modem_taskHandle = NULL; + +}; + + +struct NW005_data_struct_PPP_modem : public NWPluginData_base { + +// See: ESPEasy_key_value_store_import_export::LabelStringFunction +static const __FlashStringHelper* getLabelString(uint32_t key, bool displayString, KVS_StorageType::Enum& storageType); + +// When queried with a key of -1, it will return the first key index +// Return next key, or -2 when no next key exists. +// See: ESPEasy_key_value_store_import_export::NextKeyFunction +static int32_t getNextKey(int32_t key); +static int32_t getNextKey_noCredentials(int32_t key); + + + NW005_data_struct_PPP_modem(networkIndex_t networkIndex); + ~NW005_data_struct_PPP_modem(); + + String getRSSI() const; + String getBER() const; + float getBER_float() const; + bool attached() const; + String IMEI() const; + String operatorName() const; + + void webform_load_UE_system_information(KeyValueWriter* writer); + + void webform_load(EventStruct *event); + void webform_save(EventStruct *event); + + bool webform_getPort(KeyValueWriter *writer); + + bool write_ModemState(KeyValueWriter*writer); + + bool init(EventStruct *event); + + bool exit(EventStruct *event); + + bool handle_nwplugin_write(EventStruct *event, + String & str) override; + + String write_AT_cmd(const String& cmd, + int timeout = 1000); + +# if FEATURE_NETWORK_STATS + bool initPluginStats() override; + bool record_stats() override; +# endif // if FEATURE_NETWORK_STATS + + + NWPluginData_static_runtime* getNWPluginData_static_runtime(); + +private: + + static void onEvent(arduino_event_id_t event, + arduino_event_info_t info); + + String NW005_formatGpioLabel(uint32_t key, + PinSelectPurpose& purpose, + bool shortNotation = false) const; + + NW005_modem_task_data _modem_task_data; + + network_event_handle_t nw_event_id = 0; + + +}; + +} // namespace ppp +} // namespace net +} // namespace ESPEasy + +#endif // ifdef USES_NW005 diff --git a/src/ESPEasy/net/_NW001_WiFi_STA.cpp b/src/ESPEasy/net/_NW001_WiFi_STA.cpp new file mode 100644 index 000000000..98b6b7bb5 --- /dev/null +++ b/src/ESPEasy/net/_NW001_WiFi_STA.cpp @@ -0,0 +1,545 @@ +#include "../../ESPEasy_common.h" + +#ifdef USES_NW001 + +// ####################################################################################################### +// ########################### Network Plugin 001: WiFi Station ########################################## +// ####################################################################################################### + +# define NWPLUGIN_001 +# define NWPLUGIN_ID_001 1 +# define NWPLUGIN_NAME_001 "WiFi Station" + +# include "../../src/DataStructs/ESPEasy_EventStruct.h" +# include "../../src/Globals/RTC.h" +# include "../../src/Globals/SecuritySettings.h" +# include "../../src/Globals/Settings.h" +# include "../../src/Helpers/ESPEasy_Storage.h" +# include "../../src/Helpers/PrintToString.h" +# include "../../src/Helpers/StringConverter.h" +# include "../../src/Helpers/StringGenerator_WiFi.h" +# include "../../src/WebServer/ESPEasy_WebServer.h" +# include "../../src/WebServer/HTML_Print.h" +# include "../../src/WebServer/HTML_wrappers.h" +# include "../../src/WebServer/Markup.h" +# include "../../src/WebServer/Markup_Forms.h" +# include "../../src/WebServer/common.h" +# include "../net/ESPEasyNetwork.h" +# include "../net/Globals/ESPEasyWiFiEvent.h" +# include "../net/Globals/NWPlugins.h" +# include "../net/Globals/WiFi_AP_Candidates.h" +# include "../net/Helpers/_NWPlugin_Helper_webform.h" +# include "../net/Helpers/_NWPlugin_init.h" +# include "../net/NWPluginStructs/NW001_data_struct_WiFi_STA.h" +# include "../net/wifi/ESPEasyWifi.h" + +# if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE +# include "../../src/WebServer/SecurityStruct_deviceSpecific_webform.h" +# endif + + +# ifdef ESP8266 +# include "../net/Helpers/NWAccessControl.h" +# endif + +# ifdef ESP32P4 +# include +# endif + + +namespace ESPEasy { +namespace net { + +bool NWPlugin_001(NWPlugin::Function function, EventStruct *event, String& string) +{ + bool success = false; + + switch (function) + { + case NWPlugin::Function::NWPLUGIN_DRIVER_ADD: + { + NetworkDriverStruct& nw = getNetworkDriverStruct(networkDriverIndex_t::toNetworkDriverIndex(event->idx)); + nw.onlySingleInstance = true; + nw.alwaysPresent = true; +# ifdef ESP32P4 + nw.enabledOnFactoryReset = false; + + // ESPEasy::net::wifi::GetHostedMCUFwVersion() > 0x00020600; +# else // ifdef ESP32P4 + nw.enabledOnFactoryReset = true; +# endif // ifdef ESP32P4 + nw.fixedNetworkIndex = NWPLUGIN_ID_001 - 1; // Start counting at 0 + break; + } + + case NWPlugin::Function::NWPLUGIN_LOAD_DEFAULTS: + { +# ifdef ESP32 + Settings.setRoutePrio_for_network(event->NetworkIndex, 100); +# endif // ifdef ESP32 + Settings.setNetworkInterfaceSubnetBlockClientIP(event->NetworkIndex, false); + Settings.setNetworkInterfaceStartupDelayAtBoot(event->NetworkIndex, 1000); + + Settings.ConnectFailRetryCount = 1; + break; + } + + case NWPlugin::Function::NWPLUGIN_GET_DEVICENAME: + { + string = F(NWPLUGIN_NAME_001); + break; + } +# ifdef ESP32 + case NWPlugin::Function::NWPLUGIN_GET_INTERFACE: + { + event->networkInterface = &WiFi.STA; + success = event->networkInterface != nullptr; + break; + } +# endif // ifdef ESP32 + + case NWPlugin::Function::NWPLUGIN_WEBSERVER_SHOULD_RUN: + { +# ifdef ESP32 + success = WiFi.STA.connected(); +# else // ifdef ESP32 + success = WiFi.isConnected(); +# endif // ifdef ESP32 + break; + } + + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_ACTIVE: + { + success = ESPEasy::net::wifi::WifiIsSTA(WiFi.getMode()); + break; + } + + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_CONNECTED: + { +# ifdef ESP32 + success = WiFi.STA.connected(); +# else + success = WiFi.isConnected(); +# endif // ifdef ESP32 + + if (event->kvWriter == nullptr) { + break; + } + + if (success) { +# ifdef ESP32 +# define STA_SSID_STR WiFi.STA.SSID() +# define STA_BSSID_STR WiFi.STA.BSSIDstr() +# else // ifdef ESP32 +# define STA_SSID_STR WiFi.SSID() +# define STA_BSSID_STR WiFi.BSSIDstr() +# endif // ifdef ESP32 + + + if (event->kvWriter->summaryValueOnly()) { + event->kvWriter->write({ + EMPTY_STRING, + strformat(F("%s (ch: %d)"), + STA_SSID_STR.c_str(), + WiFi.channel()) }); + } else { + event->kvWriter->write({ + F("SSID"), + STA_SSID_STR, + KeyValueStruct::Format::PreFormatted }); + event->kvWriter->write({ + F("Channel"), + WiFi.channel() }); +# if CONFIG_SOC_WIFI_SUPPORT_5G + { + KeyValueStruct kv( + F("Band"), + (WiFi.channel() < 36) ? F("2.4") : F("5")); +# if FEATURE_TASKVALUE_UNIT_OF_MEASURE + kv.setUnit(UOM_GHz); +# endif + event->kvWriter->write(kv); + } +# endif // if CONFIG_SOC_WIFI_SUPPORT_5G + + } + { + KeyValueStruct kv( + F("RSSI"), + strformat( + event->kvWriter->summaryValueOnly() ? F("RSSI: %d dBm") : F("%d"), + WiFi.RSSI())); +# if FEATURE_TASKVALUE_UNIT_OF_MEASURE + kv.setUnit(UOM_dBm); +# endif + event->kvWriter->write(kv); + } + + if (!event->kvWriter->summaryValueOnly()) { +# if FEATURE_SET_WIFI_TX_PWR + { + KeyValueStruct kv( + F("WiFi TX Power"), + ESPEasy::net::wifi::GetWiFiTXpower(), 2); +# if FEATURE_TASKVALUE_UNIT_OF_MEASURE + kv.setUnit(UOM_dBm); +# endif + + event->kvWriter->write(kv); + } +# endif // if FEATURE_SET_WIFI_TX_PWR + event->kvWriter->write({ + F("Last Disconnect Reason"), + getWiFi_disconnectReason_str() + }); + +# ifdef ESP32 + { + const int64_t tsf_time = ESPEasy::net::wifi::WiFi_get_TSF_time(); + + if (tsf_time > 0) { + + // Split it while printing, so we're not loosing a lot of decimals in the float conversion + uint32_t tsf_usec{}; + + KeyValueStruct kv( + F("WiFi TSF time"), + concat( + secondsToDayHourMinuteSecond(micros_to_sec_usec(tsf_time, tsf_usec)), + strformat(F(".%06u"), tsf_usec)) + ); +# if FEATURE_TASKVALUE_UNIT_OF_MEASURE + kv.setUnit(UOM_usec); +# endif + event->kvWriter->write(kv); + } + } +# endif // ifdef ESP32 + } +# ifndef LIMIT_BUILD_SIZE + event->kvWriter->write({ + F("BSSID"), + STA_BSSID_STR, + KeyValueStruct::Format::PreFormatted }); + + event->kvWriter->write({ + F("Protocol"), + ESPEasy::net::wifi::toString(ESPEasy::net::wifi::getConnectionProtocol()) + }); + + ESPEasy::net::wifi::NW001_data_struct_WiFi_STA *NW_data = + static_cast(getNWPluginData(event->NetworkIndex)); + + if (NW_data) { + event->kvWriter->write({ + F("Encryption Type"), + NW_data->getWiFi_encryptionType() }); + } +# endif // ifndef LIMIT_BUILD_SIZE + } + + break; + } + +# ifdef ESP8266 + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_NAME: + { + if (event->kvWriter) { + event->kvWriter->write({ F("Name"), F("sta") }); + success = true; + } + break; + } + + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_HOSTNAME: + { + if (event->kvWriter) { + event->kvWriter->write({ F("Hostname"), WiFi.hostname() }); + success = true; + } + break; + } + + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_HW_ADDRESS: + { + if (event->kvWriter) { + event->kvWriter->write({ F("MAC"), WiFi.macAddress(), KeyValueStruct::Format::PreFormatted }); + success = true; + } + break; + } + + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_IP: + { + if (event->kvWriter) { + event->kvWriter->write({ F("IP"), WiFi.localIP().toString(), KeyValueStruct::Format::PreFormatted }); + success = true; + } + break; + } + + case NWPlugin::Function::NWPLUGIN_CLIENT_IP_WEB_ACCESS_ALLOWED: + { + IPAddress client_ip; + client_ip.fromString(string); + + if ((SecuritySettings.IPblockLevel == LOCAL_SUBNET_ALLOWED) && + !Settings.getNetworkInterfaceSubnetBlockClientIP(event->NetworkIndex)) { + success = NWPlugin::ipInRange(client_ip, NetworkID(), NetworkBroadcast()); + } else if (SecuritySettings.IPblockLevel == ONLY_IP_RANGE_ALLOWED) { + const IPAddress low(SecuritySettings.AllowedIPrangeLow); + const IPAddress high(SecuritySettings.AllowedIPrangeHigh); + success = NWPlugin::ipInRange(client_ip, low, high); + } else { + success = true; + } + break; + } +# endif // ifdef ESP8266 + +# ifdef BOARD_HAS_SDIO_ESP_HOSTED + + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_HW_ADDRESS: + { + if (event->kvWriter) { + success = ESPEasy::net::wifi::write_WiFi_Hosted_MCU_info(event->kvWriter); + } + break; + } +# ifndef LIMIT_BUILD_SIZE + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_PORT: + { + if (event->kvWriter) { + success = ESPEasy::net::wifi::write_WiFi_Hosted_MCU_pins(event->kvWriter); + } + break; + } +# endif // ifndef LIMIT_BUILD_SIZE +# endif // ifdef BOARD_HAS_SDIO_ESP_HOSTED + case NWPlugin::Function::NWPLUGIN_WEBFORM_SAVE: + { + // SSID 1 + safe_strncpy(SecuritySettings.WifiSSID, webArg(F("ssid")).c_str(), sizeof(SecuritySettings.WifiSSID)); + copyFormPassword(F("key"), SecuritySettings.WifiKey, sizeof(SecuritySettings.WifiKey)); + + // SSID 2 + strncpy_webserver_arg(SecuritySettings.WifiSSID2, F("ssid2")); + copyFormPassword(F("key2"), SecuritySettings.WifiKey2, sizeof(SecuritySettings.WifiKey2)); + +# if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + + for (uint16_t i = 0; i < MAX_EXTRA_WIFI_CREDENTIALS_SEPARATE_FILE; ++i) { + store_SecurityStruct_deviceSpecific_WebFormItem( + SecurityStruct_deviceSpecific::KeyType::WiFi_SSID, i); + store_SecurityStruct_deviceSpecific_WebFormItem( + SecurityStruct_deviceSpecific::KeyType::WiFi_Password, i); + } +# endif // if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + + // Hidden SSID + Settings.IncludeHiddenSSID(isFormItemChecked(LabelType::CONNECT_HIDDEN_SSID)); + Settings.HiddenSSID_SlowConnectPerBSSID(isFormItemChecked(LabelType::HIDDEN_SSID_SLOW_CONNECT)); + +# ifdef ESP32 + Settings.PassiveWiFiScan(isFormItemChecked(LabelType::WIFI_PASSIVE_SCAN)); +# endif + + webArg2ip(F("espip"), Settings.IP); + webArg2ip(F("espgateway"), Settings.Gateway); + webArg2ip(F("espsubnet"), Settings.Subnet); + webArg2ip(F("espdns"), Settings.DNS); + + + Settings.ForceWiFi_bg_mode(isFormItemChecked(LabelType::FORCE_WIFI_BG)); + Settings.WiFiRestart_connection_lost(isFormItemChecked(LabelType::RESTART_WIFI_LOST_CONN)); + + Settings.WifiNoneSleep(isFormItemChecked(LabelType::FORCE_WIFI_NOSLEEP)); +# ifdef SUPPORT_ARP + Settings.gratuitousARP(isFormItemChecked(LabelType::PERIODICAL_GRAT_ARP)); +# endif // ifdef SUPPORT_ARP +# if FEATURE_SET_WIFI_TX_PWR + Settings.setWiFi_TX_power(getFormItemFloat(LabelType::WIFI_TX_MAX_PWR)); + Settings.WiFi_sensitivity_margin = getFormItemInt(LabelType::WIFI_SENS_MARGIN); + Settings.UseMaxTXpowerForSending(isFormItemChecked(LabelType::WIFI_SEND_AT_MAX_TX_PWR)); +# endif // if FEATURE_SET_WIFI_TX_PWR + Settings.ConnectFailRetryCount = getFormItemInt(LabelType::WIFI_NR_RECONNECT_ATTEMPTS); + Settings.UseLastWiFiFromRTC(isFormItemChecked(LabelType::WIFI_USE_LAST_CONN_FROM_RTC)); + +# ifndef ESP32 + Settings.WaitWiFiConnect(isFormItemChecked(LabelType::WAIT_WIFI_CONNECT)); +# endif + Settings.SDK_WiFi_autoreconnect(isFormItemChecked(LabelType::SDK_WIFI_AUTORECONNECT)); +# if CONFIG_SOC_WIFI_SUPPORT_5G + Settings.WiFi_band_mode(static_cast(getFormItemInt(getInternalLabel(LabelType::WIFI_BAND_MODE)))); +# endif + + /* + # if FEATURE_USE_IPV6 + Settings.EnableIPv6(isFormItemChecked(LabelType::ENABLE_IPV6)); + # endif + */ + + break; + } + + case NWPlugin::Function::NWPLUGIN_WEBFORM_LOAD: + { + KeyValueWriter_WebForm writer(true); + # ifdef BOARD_HAS_SDIO_ESP_HOSTED + ESPEasy::net::wifi::write_WiFi_Hosted_MCU_info(writer.createChild(F("ESP-Hosted-MCU")).get()); + # endif // ifdef BOARD_HAS_SDIO_ESP_HOSTED + + addFormSubHeader(F("Wifi Credentials")); + + // TODO Add pin configuration for ESP32P4. + // ESP32-C5 may use different SDIO pins. + // See: https://github.com/espressif/esp-hosted-mcu/blob/main/docs/sdio.md#esp32-p4-function-ev-board-host-pin-mapping + + addFormTextBox(getLabel(LabelType::SSID), F("ssid"), SecuritySettings.getSSID(SecurityStruct::WiFiCredentialsSlot::first), 31); + addFormPasswordBox(F("WPA Key"), F("key"), SecuritySettings.WifiKey, 63); + addFormTextBox(F("Fallback SSID"), F("ssid2"), SecuritySettings.getSSID(SecurityStruct::WiFiCredentialsSlot::second), 31); + addFormPasswordBox(F("Fallback WPA Key"), F("key2"), SecuritySettings.WifiKey2, 63); + addFormNote(F("WPA Key must be at least 8 characters long")); + +# if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + addFormSubHeader(F("Wifi Credentials Extra")); + + for (uint16_t i = 0; i < MAX_EXTRA_WIFI_CREDENTIALS_SEPARATE_FILE; ++i) { + show_SecurityStruct_deviceSpecific_WebFormItem( + SecurityStruct_deviceSpecific::KeyType::WiFi_SSID, i); + show_SecurityStruct_deviceSpecific_WebFormItem( + SecurityStruct_deviceSpecific::KeyType::WiFi_Password, i); + } + addFormNote(F("These credentials will be stored in a separate file: devsecurity.dat")); +# endif // if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + addFormSubHeader(F("Wifi Settings")); + { + LabelType::Enum labels[]{ + LabelType::CONNECT_HIDDEN_SSID + , LabelType::HIDDEN_SSID_SLOW_CONNECT +# ifdef ESP32 + , LabelType::WIFI_PASSIVE_SCAN +# endif + }; + addFormCheckBoxes(labels, NR_ELEMENTS(labels)); + } + + addFormSubHeader(F("WiFi IP Settings")); + + addFormIPBox(F("ESP WiFi IP"), F("espip"), Settings.IP); + addFormIPBox(F("ESP WiFi Gateway"), F("espgateway"), Settings.Gateway); + addFormIPBox(F("ESP WiFi Subnetmask"), F("espsubnet"), Settings.Subnet); + addFormIPBox(F("ESP WiFi DNS"), F("espdns"), Settings.DNS); + addFormNote(F("Leave empty for DHCP")); + + /* + # if FEATURE_USE_IPV6 + addFormCheckBox(LabelType::ENABLE_IPV6); + # endif + */ + + addFormSubHeader(F("WiFi Mode")); + addFormCheckBox(LabelType::FORCE_WIFI_BG); +# if CONFIG_SOC_WIFI_SUPPORT_5G + { + const __FlashStringHelper *wifiModeNames[] = { + ESPEasy::net::wifi::getWifiBandModeString(WIFI_BAND_MODE_2G_ONLY), + ESPEasy::net::wifi::getWifiBandModeString(WIFI_BAND_MODE_5G_ONLY), + ESPEasy::net::wifi::getWifiBandModeString(WIFI_BAND_MODE_AUTO), + }; + const int wifiModeOptions[] = { WIFI_BAND_MODE_2G_ONLY, WIFI_BAND_MODE_5G_ONLY, WIFI_BAND_MODE_AUTO }; + constexpr int nrWifiModeOptions = NR_ELEMENTS(wifiModeNames); + const FormSelectorOptions selector( + nrWifiModeOptions, + wifiModeNames, + wifiModeOptions); + selector.addFormSelector( + LabelType::WIFI_BAND_MODE, + Settings.WiFi_band_mode()); + } +# endif // if CONFIG_SOC_WIFI_SUPPORT_5G + + + addFormSubHeader(F("WiFi Power")); + addFormCheckBox(LabelType::FORCE_WIFI_NOSLEEP); +# if FEATURE_SET_WIFI_TX_PWR + addFormFloatNumberBox(LabelType::WIFI_TX_MAX_PWR, 0.0f, MAX_TX_PWR_DBM_11b, 2, 0.25f); + addFormNumericBox(LabelType::WIFI_SENS_MARGIN, -20, 30); + addFormCheckBox(LabelType::WIFI_SEND_AT_MAX_TX_PWR); +# endif // if FEATURE_SET_WIFI_TX_PWR + + addFormSubHeader(F("WiFi Tweaks")); + + addFormNumericBox(LabelType::WIFI_NR_RECONNECT_ATTEMPTS, 0, 255); + { + LabelType::Enum labels[]{ + LabelType::RESTART_WIFI_LOST_CONN + , LabelType::WIFI_USE_LAST_CONN_FROM_RTC +# ifndef ESP32 + , LabelType::WAIT_WIFI_CONNECT +# endif + , LabelType::SDK_WIFI_AUTORECONNECT +# ifdef SUPPORT_ARP + , LabelType::PERIODICAL_GRAT_ARP +# endif // ifdef SUPPORT_ARP + }; + + addFormCheckBoxes(labels, NR_ELEMENTS(labels)); + } + + break; + } + + case NWPlugin::Function::NWPLUGIN_INIT: + { + initNWPluginData(event->NetworkIndex, new (std::nothrow) ESPEasy::net::wifi::NW001_data_struct_WiFi_STA(event->NetworkIndex)); + auto *NW_data = getNWPluginData(event->NetworkIndex); + + if (NW_data) { + success = NW_data->init(event); + } + break; + } + + case NWPlugin::Function::NWPLUGIN_EXIT: + { + auto *NW_data = getNWPluginData(event->NetworkIndex); + + if (NW_data) { + NW_data->exit(event); + } + success = true; + break; + } + + case NWPlugin::Function::NWPLUGIN_FIFTY_PER_SECOND: + { + // FIXME TD-er: Also called in ESPEasy_loop() + // Is it still needed there? + ESPEasy::net::wifi::loopWiFi(); + break; + } +# ifdef ESP32 + case NWPlugin::Function::NWPLUGIN_PRIORITY_ROUTE_CHANGED: + { + ESPEasy::net::wifi::NW001_data_struct_WiFi_STA *NW_data = + static_cast(getNWPluginData(event->NetworkIndex)); + + if (NW_data) { + success = NW_data->handle_priority_route_changed(); + } + break; + } +# endif // ifdef ESP32 + + default: + break; + + } + return success; +} + +} // namespace net +} // namespace ESPEasy + +#endif // ifdef USES_NW001 diff --git a/src/ESPEasy/net/_NW002_WiFi_AP.cpp b/src/ESPEasy/net/_NW002_WiFi_AP.cpp new file mode 100644 index 000000000..ce88fb3cd --- /dev/null +++ b/src/ESPEasy/net/_NW002_WiFi_AP.cpp @@ -0,0 +1,365 @@ +#include "../../ESPEasy_common.h" + +#ifdef USES_NW002 + +// ####################################################################################################### +// ########################### Network Plugin 002: WiFi Access Point ##################################### +// ####################################################################################################### + +# define NWPLUGIN_002 +# define NWPLUGIN_ID_002 2 +# define NWPLUGIN_NAME_002 "WiFi AP" + +# include "../../src/DataStructs/ESPEasy_EventStruct.h" +# include "../../src/Globals/SecuritySettings.h" +# include "../../src/Globals/Settings.h" +# include "../../src/Helpers/ESPEasy_Storage.h" +# include "../../src/Helpers/PrintToString.h" +# include "../../src/Helpers/StringConverter.h" +# include "../../src/WebServer/ESPEasy_WebServer.h" +# include "../../src/WebServer/HTML_Print.h" +# include "../../src/WebServer/HTML_wrappers.h" +# include "../../src/WebServer/Markup.h" +# include "../../src/WebServer/Markup_Forms.h" +# include "../../src/WebServer/common.h" +# include "../net/ESPEasyNetwork.h" +# include "../net/Globals/NWPlugins.h" +# include "../net/Helpers/_NWPlugin_Helper_webform.h" +# include "../net/Helpers/_NWPlugin_init.h" +# include "../net/NWPluginStructs/NW002_data_struct_WiFi_AP.h" + + +// TODO TD-er: This code should be moved to this NW002 plugin +# include "../net/wifi/ESPEasyWifi.h" + +# ifdef ESP32 +# include +# include +# endif // ifdef ESP32 + +namespace ESPEasy { +namespace net { + +bool NWPlugin_002(NWPlugin::Function function, EventStruct *event, String& string) +{ + bool success = false; + + switch (function) + { + case NWPlugin::Function::NWPLUGIN_DRIVER_ADD: + { + NetworkDriverStruct& nw = getNetworkDriverStruct(networkDriverIndex_t::toNetworkDriverIndex(event->idx)); + nw.onlySingleInstance = true; + nw.alwaysPresent = true; + nw.enabledOnFactoryReset = true; + nw.fixedNetworkIndex = NWPLUGIN_ID_002 - 1; // Start counting at 0 + break; + } + + case NWPlugin::Function::NWPLUGIN_LOAD_DEFAULTS: + { +# ifdef ESP32 + Settings.setRoutePrio_for_network(event->NetworkIndex, 10); +# endif + Settings.setNetworkInterfaceSubnetBlockClientIP(event->NetworkIndex, false); + Settings.setNetworkInterfaceStartupDelayAtBoot(event->NetworkIndex, 10000); + Settings.StartAP_on_NW002_init(false); + Settings.StartAPfallback_NoCredentials(true); + Settings.DoNotStartAPfallback_ConnectFail(false); + Settings.APfallback_autostart_max_uptime_m(0); + Settings.APfallback_minimal_on_time_sec(DEFAULT_AP_FALLBACK_MINIMAL_ON_TIME_SEC); + break; + } + + case NWPlugin::Function::NWPLUGIN_GET_DEVICENAME: + { + string = F(NWPLUGIN_NAME_002); + break; + } + +# ifdef ESP32 + case NWPlugin::Function::NWPLUGIN_GET_INTERFACE: + { + event->networkInterface = &WiFi.AP; + success = event->networkInterface != nullptr; + break; + } +# endif // ifdef ESP32 + + case NWPlugin::Function::NWPLUGIN_WEBSERVER_SHOULD_RUN: + { + success = ESPEasy::net::wifi::wifiAPmodeActivelyUsed(); + + break; + } + + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_ACTIVE: + { + success = ESPEasy::net::wifi::WifiIsAP(WiFi.getMode()); + break; + } + + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_CONNECTED: + { + if (event->kvWriter == nullptr) { + break; + } + + KeyValueStruct kv(F("Clients")); + +# ifdef ESP32 + wifi_sta_list_t wifi_sta_list = { 0 }; + wifi_sta_mac_ip_list_t wifi_sta_mac_ip_list = { 0 }; + + esp_wifi_ap_get_sta_list(&wifi_sta_list); + esp_wifi_ap_get_sta_list_with_ip(&wifi_sta_list, &wifi_sta_mac_ip_list); + + for (int i = 0; i < wifi_sta_mac_ip_list.num; i++) + { + const MAC_address mac(wifi_sta_mac_ip_list.sta[i].mac); + const IPAddress ip(wifi_sta_mac_ip_list.sta[i].ip.addr); + + kv.appendValue(strformat( + F("%s IP:%s (%d dBm)"), + mac.toString().c_str(), + ip.toString().c_str(), + wifi_sta_list.sta[i].rssi + )); + } +# endif // ifdef ESP32 + +# ifdef ESP8266 +# ifndef LIMIT_BUILD_SIZE + struct station_info *station = wifi_softap_get_station_info(); + + while (station) + { + const MAC_address mac(station->bssid); + const IPAddress ip(station->ip.addr); + + kv.appendValue(strformat( + F("%s IP:%s"), + mac.toString().c_str(), + ip.toString().c_str() + )); + + station = STAILQ_NEXT(station, next); + } + wifi_softap_free_station_info(); + +# else // ifndef LIMIT_BUILD_SIZE + const uint8_t num = SOFTAP_STATION_COUNT; + + if (num > 0) { + success = true; + string = num; + string += F(" client"); + + if (num > 1) { string += 's'; } + } +# endif // ifndef LIMIT_BUILD_SIZE +# endif // ifdef ESP8266 + + if (kv._values.size() > 0) { + success = true; + event->kvWriter->write(kv); + } + break; + } + + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_NAME: + { + if (event->kvWriter) { + event->kvWriter->write({ F("Name"), F("ap") }); + success = true; + } + break; + } + + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_HOSTNAME: + { + if (event->kvWriter) { + event->kvWriter->write({ F("Hostname"), +# ifdef ESP32 + WiFi.AP.SSID() +# else + WiFi.softAPSSID() +# endif // ifdef ESP32 + }); + success = true; + } + break; + } + +# ifdef ESP8266 + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_HW_ADDRESS: + { + if (event->kvWriter) { + event->kvWriter->write({ F("MAC"), WiFi.softAPmacAddress(), KeyValueStruct::Format::PreFormatted }); + success = true; + } + break; + } + + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_IP: + { + if (event->kvWriter) { + event->kvWriter->write({ F("IP"), WiFi.softAPIP().toString(), KeyValueStruct::Format::PreFormatted }); + success = true; + } + break; + } + + case NWPlugin::Function::NWPLUGIN_CLIENT_IP_WEB_ACCESS_ALLOWED: + { + // FIXME TD-er: Shouldn't we just always allow access from AP? + if (!Settings.getNetworkInterfaceSubnetBlockClientIP(event->NetworkIndex)) { + IPAddress client_ip; + client_ip.fromString(string); + + // FIXME TD-er: Do we allow to set the subnetmask for AP to anything else? + const IPAddress subnet(255, 255, 255, 0); + const IPAddress localIP = WiFi.softAPIP(); + bool success = true; + + for (uint8_t i = 0; success && i < 4; ++i) { + if ((localIP[i] & subnet[i]) != (client_ip[i] & subnet[i])) { + success = false; + } + } + } + break; + } +# endif // ifdef ESP8266 + + case NWPlugin::Function::NWPLUGIN_WEBFORM_SAVE: + { + + // Access point password. + copyFormPassword(F("apkey"), SecuritySettings.WifiAPKey, sizeof(SecuritySettings.WifiAPKey)); + + Settings.WiFiAP_channel = getFormItemInt(LabelType::WIFI_AP_CHANNEL); + + // When set, user will be redirected to /setup or root page when connecting to this AP + Settings.ApCaptivePortal(isFormItemChecked(LabelType::WIFI_ENABLE_CAPTIVE_PORTAL)); + + // Usually the AP will be started when no WiFi is defined, or the defined one cannot be found. This flag may prevent it. + Settings.StartAPfallback_NoCredentials(isFormItemChecked(LabelType::WIFI_START_AP_NO_CREDENTIALS)); + Settings.DoNotStartAPfallback_ConnectFail(!isFormItemChecked(LabelType::WIFI_START_AP_ON_CONNECT_FAIL)); + Settings.StartAP_on_NW002_init(isFormItemChecked(LabelType::WIFI_START_AP_ON_NW002_INIT)); + Settings.APfallback_autostart_max_uptime_m(getFormItemInt(LabelType::WIFI_MAX_UPTIME_AUTO_START_AP)); + Settings.APfallback_minimal_on_time_sec(getFormItemInt(LabelType::WIFI_AP_MINIMAL_ON_TIME)); + + +# ifdef ESP32 + Settings.WiFi_AP_enable_NAPT(isFormItemChecked(LabelType::WIFI_AP_ENABLE_NAPT)); +# endif + + break; + } + + case NWPlugin::Function::NWPLUGIN_WEBFORM_LOAD: + { + addFormCheckBox(LabelType::WIFI_START_AP_ON_NW002_INIT); + + addFormSubHeader(F("Wifi AP Settings")); + addFormPasswordBox(F("WPA AP Mode Key"), F("apkey"), SecuritySettings.WifiAPKey, 63); + addFormNote(F("WPA Key must be at least 8 characters long")); + + + { +# if CONFIG_SOC_WIFI_SUPPORT_5G + + // See wifi_5g_channel_bit_t for all supported channels + const int wifiChannels[] = + { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 // 2.4 GHz + ,36, 40, 44, 48 // 5 GHz U-NII-1 + ,52, 56, 60, 64, 68 // 5 GHz U-NII-2A + // ,72, 76, 80, 84, 88 /* , 92 */ // 5 GHz U-NII-2B + ,/* 96,*/ 100,104, 108, 112, 116, 120, 124, 128, 132, 136, 140 // 5 GHz U-NII-2C + ,144 // 5 GHz U-NII-2C/3 + ,149, 153, 157, 161, 165 // 5 GHz U-NII-3 + ,169 // 5 GHz U-NII-3/4 + ,173, 177 // 5 GHz U-NII-4 + }; + constexpr int nrwifiChannels = NR_ELEMENTS(wifiChannels); + const FormSelectorOptions selector( + nrwifiChannels, + wifiChannels); + selector.addFormSelector( + LabelType::WIFI_AP_CHANNEL, + Settings.WiFiAP_channel); +# else // if CONFIG_SOC_WIFI_SUPPORT_5G + addFormNumericBox(LabelType::WIFI_AP_CHANNEL, 1, 14); +# endif // if CONFIG_SOC_WIFI_SUPPORT_5G + + } +# ifdef ESP32 + addFormCheckBox(LabelType::WIFI_AP_ENABLE_NAPT); +# endif // ifdef ESP32 + + addFormSubHeader(F("Wifi AP Fallback")); + { + LabelType::Enum labels[]{ + LabelType::WIFI_ENABLE_CAPTIVE_PORTAL + , LabelType::WIFI_START_AP_NO_CREDENTIALS + , LabelType::WIFI_START_AP_ON_CONNECT_FAIL + }; + + addFormCheckBoxes(labels, NR_ELEMENTS(labels)); + } + + addFormNumericBox(LabelType::WIFI_MAX_UPTIME_AUTO_START_AP, 0, 255); + addFormNumericBox(LabelType::WIFI_AP_MINIMAL_ON_TIME, 0, 255); + break; + } + + case NWPlugin::Function::NWPLUGIN_INIT: + { + initNWPluginData(event->NetworkIndex, new (std::nothrow) ESPEasy::net::wifi::NW002_data_struct_WiFi_AP(event->NetworkIndex)); + ESPEasy::net::wifi::NW002_data_struct_WiFi_AP *NW_data = + static_cast(getNWPluginData(event->NetworkIndex)); + + if (NW_data) { + success = NW_data->init(event); + } + break; + } + + case NWPlugin::Function::NWPLUGIN_EXIT: + { + ESPEasy::net::wifi::NW002_data_struct_WiFi_AP *NW_data = + static_cast(getNWPluginData(event->NetworkIndex)); + + if (NW_data) { + NW_data->exit(event); + } + success = true; + break; + } + +# ifdef ESP32 + case NWPlugin::Function::NWPLUGIN_PRIORITY_ROUTE_CHANGED: + { + ESPEasy::net::wifi::NW002_data_struct_WiFi_AP *NW_data = + static_cast(getNWPluginData(event->NetworkIndex)); + + if (NW_data) { + success = NW_data->handle_priority_route_changed(); + } + break; + } +# endif // ifdef ESP32 + + + default: + break; + + } + return success; +} + +} // namespace net +} // namespace ESPEasy + +#endif // ifdef USES_NW002 diff --git a/src/ESPEasy/net/_NW003_ETH_RMII.cpp b/src/ESPEasy/net/_NW003_ETH_RMII.cpp new file mode 100644 index 000000000..73aa70457 --- /dev/null +++ b/src/ESPEasy/net/_NW003_ETH_RMII.cpp @@ -0,0 +1,237 @@ +#include "../../ESPEasy_common.h" + +#ifdef USES_NW003 + +// ####################################################################################################### +// ########################### Network Plugin 003: Ethernet RMII ######################################### +// ####################################################################################################### + +# define NWPLUGIN_003 +# define NWPLUGIN_ID_003 3 +# define NWPLUGIN_NAME_003 "Ethernet (RMII)" + +# include "../../src/DataStructs/ESPEasy_EventStruct.h" +# include "../../src/Globals/SecuritySettings.h" +# include "../../src/Globals/Settings.h" +# include "../../src/Helpers/ESPEasy_Storage.h" +# include "../../src/Helpers/PrintToString.h" +# include "../../src/Helpers/StringConverter.h" +# include "../../src/WebServer/ESPEasy_WebServer.h" +# include "../../src/WebServer/HTML_Print.h" +# include "../../src/WebServer/HTML_wrappers.h" +# include "../../src/WebServer/Markup.h" +# include "../../src/WebServer/Markup_Forms.h" +# include "../../src/WebServer/common.h" +# include "../net/eth/ESPEasyEth.h" +# include "../net/Globals/NWPlugins.h" +# include "../net/Globals/NetworkState.h" +# include "../net/Helpers/_NWPlugin_Helper_webform.h" +# include "../net/Helpers/_NWPlugin_init.h" +# include "../net/Helpers/NW_info_writer.h" + +# include "../net/NWPluginStructs/NW003_data_struct_ETH_RMII.h" + +# include + +namespace ESPEasy { +namespace net { + +bool NWPlugin_003(NWPlugin::Function function, EventStruct *event, String& string) +{ + bool success = false; + + switch (function) + { + case NWPlugin::Function::NWPLUGIN_DRIVER_ADD: + { + NetworkDriverStruct& nw = getNetworkDriverStruct(networkDriverIndex_t::toNetworkDriverIndex(event->idx)); + nw.onlySingleInstance = true; +# ifdef ESP32P4 + nw.alwaysPresent = true; + nw.enabledOnFactoryReset = true; + nw.fixedNetworkIndex = NWPLUGIN_ID_003 - 1; // Start counting at 0 +# else // ifdef ESP32P4 + nw.alwaysPresent = false; +# endif // ifdef ESP32P4 + break; + } + + case NWPlugin::Function::NWPLUGIN_LOAD_DEFAULTS: + { + Settings.setRoutePrio_for_network(event->NetworkIndex, 50); + Settings.setNetworkInterfaceSubnetBlockClientIP(event->NetworkIndex, false); + Settings.setNetworkInterfaceStartupDelayAtBoot(event->NetworkIndex, 500 * event->NetworkIndex); + + ESPEasy_key_value_store kvs; + ESPEasy::net::eth::NW003_data_struct_ETH_RMII::loadDefaults( + &kvs, + event->NetworkIndex, + ESPEasy::net::nwpluginID_t(NWPLUGIN_ID_003)); + + break; + } + + case NWPlugin::Function::NWPLUGIN_GET_DEVICENAME: + { + string = F(NWPLUGIN_NAME_003); + break; + } + + # ifdef ESP32 + case NWPlugin::Function::NWPLUGIN_GET_INTERFACE: + { + event->networkInterface = ESPEasy::net::eth::ETH_NWPluginData_static_runtime::getInterface(event->NetworkIndex); + success = event->networkInterface != nullptr; + break; + } +# endif // ifdef ESP32 + + case NWPlugin::Function::NWPLUGIN_WEBSERVER_SHOULD_RUN: + { + auto iface = ESPEasy::net::eth::ETH_NWPluginData_static_runtime::getInterface(event->NetworkIndex); + + if (iface) { + success = iface->connected(); + } + break; + } + + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_CONNECTED: + { + auto iface = ESPEasy::net::eth::ETH_NWPluginData_static_runtime::getInterface(event->NetworkIndex); + + if (iface) { + success = write_Eth_Show_Connected(*iface, event->kvWriter); + } + break; + } + + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_HW_ADDRESS: + { + ESPEasy::net::eth::NW003_data_struct_ETH_RMII *NW_data = + static_cast(getNWPluginData(event->NetworkIndex)); + + if (NW_data) { + success = NW_data->write_Eth_HW_Address(event->kvWriter); + } + break; + } +# ifndef LIMIT_BUILD_SIZE + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_PORT: + { + ESPEasy::net::eth::NW003_data_struct_ETH_RMII *NW_data = + static_cast(getNWPluginData(event->NetworkIndex)); + + if (NW_data) { + success = NW_data->write_Eth_port(event->kvWriter); + } + break; + } +# endif // ifndef LIMIT_BUILD_SIZE + + case NWPlugin::Function::NWPLUGIN_WEBFORM_SAVE: + { + ESPEasy::net::eth::NW003_data_struct_ETH_RMII *NW_data = + static_cast(getNWPluginData(event->NetworkIndex)); + bool mustCleanup = NW_data == nullptr; + + if (mustCleanup) { + NW_data = new (std::nothrow) ESPEasy::net::eth::NW003_data_struct_ETH_RMII(event->NetworkIndex, nullptr); +# if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + + if (NW_data) { + NW_data->init_KVS(); + } +# endif // if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + } + + if (NW_data) { + NW_data->webform_save(event); + + if (mustCleanup) { delete NW_data; } + success = true; + } + + break; + } + + case NWPlugin::Function::NWPLUGIN_WEBFORM_LOAD: + { + ESPEasy::net::eth::NW003_data_struct_ETH_RMII *NW_data = + static_cast(getNWPluginData(event->NetworkIndex)); + bool mustCleanup = NW_data == nullptr; + + if (mustCleanup) { + NW_data = new (std::nothrow) ESPEasy::net::eth::NW003_data_struct_ETH_RMII(event->NetworkIndex, nullptr); +# if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + + if (NW_data) { + NW_data->init_KVS(); + } +# endif // if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + } + + if (NW_data) { + NW_data->webform_load(event); + success = true; + + if (mustCleanup) { delete NW_data; } + + } + + break; + } + + case NWPlugin::Function::NWPLUGIN_INIT: + { + auto iface = ESPEasy::net::eth::ETH_NWPluginData_static_runtime::init(event->NetworkIndex); + + if (iface) { + initNWPluginData(event->NetworkIndex, new (std::nothrow) ESPEasy::net::eth::NW003_data_struct_ETH_RMII(event->NetworkIndex, iface)); + auto *NW_data = getNWPluginData(event->NetworkIndex); + + if (NW_data) { + success = NW_data->init(event); + } + } + break; + } + + case NWPlugin::Function::NWPLUGIN_EXIT: + { + auto *NW_data = getNWPluginData(event->NetworkIndex); + + if (NW_data) { + NW_data->exit(event); + } + success = true; + break; + } + + case NWPlugin::Function::NWPLUGIN_TEN_PER_SECOND: + // FIXME TD-er: Must make this act on DNS updates from other interfaces + // Fall through + case NWPlugin::Function::NWPLUGIN_PRIORITY_ROUTE_CHANGED: + { + ESPEasy::net::eth::NW003_data_struct_ETH_RMII *NW_data = + static_cast(getNWPluginData(event->NetworkIndex)); + + if (NW_data) { + success = NW_data->handle_priority_route_changed(); + } + break; + } + + default: + break; + + } + return success; +} + +} // namespace net + +} // namespace ESPEasy + + +#endif // ifdef USES_NW003 diff --git a/src/ESPEasy/net/_NW004_ETH_SPI.cpp b/src/ESPEasy/net/_NW004_ETH_SPI.cpp new file mode 100644 index 000000000..7b6d6dfb0 --- /dev/null +++ b/src/ESPEasy/net/_NW004_ETH_SPI.cpp @@ -0,0 +1,226 @@ +#include "../../ESPEasy_common.h" + +#ifdef USES_NW004 + +// ####################################################################################################### +// ########################### Network Plugin 004: Ethernet SPI ########################################## +// ####################################################################################################### + +# define NWPLUGIN_004 +# define NWPLUGIN_ID_004 4 +# define NWPLUGIN_NAME_004 "Ethernet (SPI)" + +# include "../../src/DataStructs/ESPEasy_EventStruct.h" +# include "../net/eth/ESPEasyEth.h" +# include "../../src/Globals/SecuritySettings.h" +# include "../../src/Globals/Settings.h" +# include "../../src/Helpers/ESPEasy_Storage.h" +# include "../../src/Helpers/PrintToString.h" +# include "../../src/Helpers/StringConverter.h" +# include "../../src/WebServer/ESPEasy_WebServer.h" +# include "../../src/WebServer/HTML_Print.h" +# include "../../src/WebServer/HTML_wrappers.h" +# include "../../src/WebServer/Markup.h" +# include "../../src/WebServer/Markup_Forms.h" +# include "../../src/WebServer/common.h" +# include "../net/Globals/NWPlugins.h" +# include "../net/Globals/NetworkState.h" +# include "../net/Helpers/_NWPlugin_Helper_webform.h" +# include "../net/Helpers/_NWPlugin_init.h" +# include "../net/Helpers/NW_info_writer.h" + +# include "../net/NWPluginStructs/NW004_data_struct_ETH_SPI.h" + + +# include + +namespace ESPEasy { +namespace net { + +bool NWPlugin_004(NWPlugin::Function function, EventStruct *event, String& string) +{ + bool success = false; + + switch (function) + { + case NWPlugin::Function::NWPLUGIN_DRIVER_ADD: + { + NetworkDriverStruct& nw = getNetworkDriverStruct(networkDriverIndex_t::toNetworkDriverIndex(event->idx)); + nw.onlySingleInstance = true; + nw.alwaysPresent = false; + break; + } + + case NWPlugin::Function::NWPLUGIN_LOAD_DEFAULTS: + { + Settings.setRoutePrio_for_network(event->NetworkIndex, 50); + Settings.setNetworkInterfaceSubnetBlockClientIP(event->NetworkIndex, false); + Settings.setNetworkInterfaceStartupDelayAtBoot(event->NetworkIndex, 500 * event->NetworkIndex); + + ESPEasy_key_value_store kvs; + ESPEasy::net::eth::NW004_data_struct_ETH_SPI::loadDefaults( + &kvs, + event->NetworkIndex, + ESPEasy::net::nwpluginID_t(NWPLUGIN_ID_004)); + + break; + } + + case NWPlugin::Function::NWPLUGIN_GET_DEVICENAME: + { + string = F(NWPLUGIN_NAME_004); + break; + } + + # ifdef ESP32 + case NWPlugin::Function::NWPLUGIN_GET_INTERFACE: + { + event->networkInterface = ESPEasy::net::eth::ETH_NWPluginData_static_runtime::getInterface(event->NetworkIndex); + success = event->networkInterface != nullptr; + break; + } +# endif // ifdef ESP32 + + case NWPlugin::Function::NWPLUGIN_WEBSERVER_SHOULD_RUN: + { + auto iface = ESPEasy::net::eth::ETH_NWPluginData_static_runtime::getInterface(event->NetworkIndex); + + if (iface) { + success = iface->connected(); + } + break; + } + + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_CONNECTED: + { + auto iface = ESPEasy::net::eth::ETH_NWPluginData_static_runtime::getInterface(event->NetworkIndex); + + if (iface) { + success = write_Eth_Show_Connected(*iface, event->kvWriter); + } + break; + } + + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_HW_ADDRESS: + { + ESPEasy::net::eth::NW004_data_struct_ETH_SPI *NW_data = + static_cast(getNWPluginData(event->NetworkIndex)); + + if (NW_data) { + success = NW_data->write_Eth_HW_Address(event->kvWriter); + } + break; + } +# ifndef LIMIT_BUILD_SIZE + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_PORT: + { + ESPEasy::net::eth::NW004_data_struct_ETH_SPI *NW_data = + static_cast(getNWPluginData(event->NetworkIndex)); + + if (NW_data) { + success = NW_data->write_Eth_port(event->kvWriter); + } + break; + } +# endif // ifndef LIMIT_BUILD_SIZE + case NWPlugin::Function::NWPLUGIN_WEBFORM_SAVE: + { + ESPEasy::net::eth::NW004_data_struct_ETH_SPI *NW_data = + static_cast(getNWPluginData(event->NetworkIndex)); + bool mustCleanup = NW_data == nullptr; + + if (mustCleanup) { + NW_data = new (std::nothrow) ESPEasy::net::eth::NW004_data_struct_ETH_SPI(event->NetworkIndex, nullptr); +# if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + + if (NW_data) { + NW_data->init_KVS(); + } +# endif // if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + } + + if (NW_data) { + NW_data->webform_save(event); + + if (mustCleanup) { delete NW_data; } + success = true; + } + break; + } + + case NWPlugin::Function::NWPLUGIN_WEBFORM_LOAD: + { + ESPEasy::net::eth::NW004_data_struct_ETH_SPI *NW_data = + static_cast(getNWPluginData(event->NetworkIndex)); + bool mustCleanup = NW_data == nullptr; + + if (mustCleanup) { + NW_data = new (std::nothrow) ESPEasy::net::eth::NW004_data_struct_ETH_SPI(event->NetworkIndex, nullptr); +# if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + + if (NW_data) { + NW_data->init_KVS(); + } +# endif // if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + } + + if (NW_data) { + NW_data->webform_load(event); + success = true; + + if (mustCleanup) { delete NW_data; } + + } + break; + } + + + case NWPlugin::Function::NWPLUGIN_INIT: + { + auto iface = ESPEasy::net::eth::ETH_NWPluginData_static_runtime::init(event->NetworkIndex); + + if (iface) { + initNWPluginData(event->NetworkIndex, new (std::nothrow) ESPEasy::net::eth::NW004_data_struct_ETH_SPI(event->NetworkIndex, iface)); + auto *NW_data = getNWPluginData(event->NetworkIndex); + + if (NW_data) { + success = NW_data->init(event); + } + } + break; + } + + case NWPlugin::Function::NWPLUGIN_EXIT: + { + auto *NW_data = getNWPluginData(event->NetworkIndex); + + if (NW_data) { + NW_data->exit(event); + } + success = true; + break; + } + + case NWPlugin::Function::NWPLUGIN_PRIORITY_ROUTE_CHANGED: + { + ESPEasy::net::eth::NW004_data_struct_ETH_SPI *NW_data = + static_cast(getNWPluginData(event->NetworkIndex)); + + if (NW_data) { + success = NW_data->handle_priority_route_changed(); + } + break; + } + + default: + break; + + } + + return success; +} + +} // namespace net +} // namespace ESPEasy + +#endif // ifdef USES_NW004 diff --git a/src/ESPEasy/net/_NW005_PPP_modem.cpp b/src/ESPEasy/net/_NW005_PPP_modem.cpp new file mode 100644 index 000000000..5265a76d7 --- /dev/null +++ b/src/ESPEasy/net/_NW005_PPP_modem.cpp @@ -0,0 +1,270 @@ +#include "../../ESPEasy_common.h" + +#ifdef USES_NW005 + +// ####################################################################################################### +// ########################### Network Plugin 005: PPP modem ############################################# +// ####################################################################################################### + +# define NWPLUGIN_005 +# define NWPLUGIN_ID_005 5 +# define NWPLUGIN_NAME_005 "PPP modem" + +# include "../../src/DataStructs/ESPEasy_EventStruct.h" +# include "../net/ESPEasyNetwork.h" +# include "../../src/Globals/SecuritySettings.h" +# include "../../src/Globals/Settings.h" +# include "../../src/Helpers/ESPEasy_Storage.h" +# include "../../src/Helpers/PrintToString.h" +# include "../../src/Helpers/StringConverter.h" +# include "../../src/Helpers/_Plugin_Helper_serial.h" +# include "../../src/WebServer/ESPEasy_WebServer.h" +# include "../../src/WebServer/HTML_Print.h" +# include "../../src/WebServer/HTML_wrappers.h" +# include "../../src/WebServer/Markup.h" +# include "../../src/WebServer/Markup_Forms.h" +# include "../../src/WebServer/common.h" +# include "../net/Globals/NWPlugins.h" +# include "../net/Helpers/_NWPlugin_Helper_webform.h" +# include "../net/Helpers/_NWPlugin_init.h" +# include "../net/NWPluginStructs/NW005_data_struct_PPP_modem.h" + + +# include + +# include + +namespace ESPEasy { +namespace net { + +bool NWPlugin_005(NWPlugin::Function function, EventStruct *event, String& string) +{ + bool success = false; + + switch (function) + { + case NWPlugin::Function::NWPLUGIN_DRIVER_ADD: + { + NetworkDriverStruct& nw = getNetworkDriverStruct(networkDriverIndex_t::toNetworkDriverIndex(event->idx)); + nw.onlySingleInstance = true; + nw.alwaysPresent = false; + break; + } + + case NWPlugin::Function::NWPLUGIN_LOAD_DEFAULTS: + { + Settings.setRoutePrio_for_network(event->NetworkIndex, 20); + Settings.setNetworkInterfaceSubnetBlockClientIP(event->NetworkIndex, true); + Settings.setNetworkInterfaceStartupDelayAtBoot(event->NetworkIndex, 500 * event->NetworkIndex); + break; + } + + case NWPlugin::Function::NWPLUGIN_GET_DEVICENAME: + { + string = F(NWPLUGIN_NAME_005); + break; + } + + case NWPlugin::Function::NWPLUGIN_GET_INTERFACE: + { + ESPEasy::net::ppp::NW005_data_struct_PPP_modem *NW_data = + static_cast(getNWPluginData(event->NetworkIndex)); + + if (NW_data && NW_data->attached()) { + event->networkInterface = &PPP; + success = event->networkInterface != nullptr; + } + break; + } + + case NWPlugin::Function::NWPLUGIN_WEBSERVER_SHOULD_RUN: + { + // Webserver should not be accessed from PPP modem + success = false; + break; + } + + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_CONNECTED: + { + if (event->kvWriter) { + ESPEasy::net::ppp::NW005_data_struct_PPP_modem *NW_data = + static_cast(getNWPluginData(event->NetworkIndex)); + + if (NW_data) { + success = NW_data->attached(); + + if (success) { + if (event->kvWriter->summaryValueOnly()) { + event->kvWriter->write({ + EMPTY_STRING, + strformat( + F("%s (%s dBm)"), + NW_data->operatorName().c_str(), + NW_data->getRSSI().c_str()) + }); + } else { + NW_data->webform_load_UE_system_information(event->kvWriter); + event->kvWriter->write({ F("BER"), NW_data->getBER() }); + } + } + } + } + + break; + } + + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_HW_ADDRESS: + { + if (event->kvWriter) { + ESPEasy::net::ppp::NW005_data_struct_PPP_modem *NW_data = + static_cast(getNWPluginData(event->NetworkIndex)); + + if (NW_data) { + const String imei(NW_data->IMEI()); + + if (imei.length()) { + if (event->kvWriter->summaryValueOnly()) { + event->kvWriter->write({ EMPTY_STRING, concat(F("IMEI: "), imei) }); + } else { + NW_data->write_ModemState(event->kvWriter); + } + } + } + } + + // Still mark success = true to prevent a call to get the MAC address + success = true; + break; + } + +# ifndef LIMIT_BUILD_SIZE + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_PORT: + { + if (event->kvWriter) { + ESPEasy::net::ppp::NW005_data_struct_PPP_modem *NW_data = + static_cast(getNWPluginData(event->NetworkIndex)); + + if (NW_data) { + success = NW_data->webform_getPort(event->kvWriter); + } + } + break; + } +# endif // ifndef LIMIT_BUILD_SIZE + + case NWPlugin::Function::NWPLUGIN_WEBFORM_SAVE: + { + ESPEasy::net::ppp::NW005_data_struct_PPP_modem *NW_data = + static_cast(getNWPluginData(event->NetworkIndex)); + bool mustCleanup = NW_data == nullptr; + + if (mustCleanup) { + NW_data = new (std::nothrow) ESPEasy::net::ppp::NW005_data_struct_PPP_modem(event->NetworkIndex); +# if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + + if (NW_data) { + NW_data->init_KVS(); + } +# endif // if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + } + + if (NW_data) { + NW_data->webform_save(event); + + if (mustCleanup) { delete NW_data; } + success = true; + } + + break; + } + + case NWPlugin::Function::NWPLUGIN_WEBFORM_LOAD: + { + ESPEasy::net::ppp::NW005_data_struct_PPP_modem *NW_data = + static_cast(getNWPluginData(event->NetworkIndex)); + bool mustCleanup = NW_data == nullptr; + + if (mustCleanup) { + NW_data = new (std::nothrow) ESPEasy::net::ppp::NW005_data_struct_PPP_modem(event->NetworkIndex); +# if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + + if (NW_data) { + NW_data->init_KVS(); + } +# endif // if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + } + + if (NW_data) { + NW_data->webform_load(event); + success = true; + + if (mustCleanup) { delete NW_data; } + + } + + break; + } + + case NWPlugin::Function::NWPLUGIN_INIT: + { + initNWPluginData(event->NetworkIndex, new (std::nothrow) ESPEasy::net::ppp::NW005_data_struct_PPP_modem(event->NetworkIndex)); + ESPEasy::net::ppp::NW005_data_struct_PPP_modem *NW_data = + static_cast(getNWPluginData(event->NetworkIndex)); + + if (NW_data) { + success = NW_data->init(event); + } + break; + } + + case NWPlugin::Function::NWPLUGIN_EXIT: + { + ESPEasy::net::ppp::NW005_data_struct_PPP_modem *NW_data = + static_cast(getNWPluginData(event->NetworkIndex)); + + if (NW_data) { + NW_data->exit(event); + } + success = true; + break; + } + + + case NWPlugin::Function::NWPLUGIN_FIFTY_PER_SECOND: + { + break; + } + + case NWPlugin::Function::NWPLUGIN_PRIORITY_ROUTE_CHANGED: + { + ESPEasy::net::ppp::NW005_data_struct_PPP_modem *NW_data = + static_cast(getNWPluginData(event->NetworkIndex)); + + if (NW_data) { + success = NW_data->handle_priority_route_changed(); + } + break; + } + + case NWPlugin::Function::NWPLUGIN_WRITE: + { + ESPEasy::net::ppp::NW005_data_struct_PPP_modem *NW_data = + static_cast(getNWPluginData(event->NetworkIndex)); + + if (NW_data) { + success = NW_data->handle_nwplugin_write(event, string); + } + break; + } + + default: + break; + + } + return success; +} + +} // namespace net +} // namespace ESPEasy + +#endif // ifdef USES_NW005 diff --git a/src/ESPEasy/net/_NWPlugin_Helper.cpp b/src/ESPEasy/net/_NWPlugin_Helper.cpp new file mode 100644 index 000000000..b8cedd639 --- /dev/null +++ b/src/ESPEasy/net/_NWPlugin_Helper.cpp @@ -0,0 +1,143 @@ +#include "../net/_NWPlugin_Helper.h" + +#include "../../src/CustomBuild/ESPEasyLimits.h" + +#include "../net/Globals/NWPlugins.h" +#include "../../src/Globals/Settings.h" + +namespace ESPEasy { +namespace net { + + +NWPluginData_base *NWPlugin_task_data[NETWORK_MAX]{ 0 }; + +void resetNWPluginData() { + for (ESPEasy::net::networkIndex_t i = 0; i < NR_ELEMENTS(NWPlugin_task_data); ++i) { + NWPlugin_task_data[i] = nullptr; + } +} + +void clearNWPluginData(ESPEasy::net::networkIndex_t networkIndex) { + if (validNetworkIndex(networkIndex)) { + if (NWPlugin_task_data[networkIndex] != nullptr) { + delete NWPlugin_task_data[networkIndex]; + NWPlugin_task_data[networkIndex] = nullptr; + } + } +} + +bool initNWPluginData(ESPEasy::net::networkIndex_t networkIndex, NWPluginData_base *data) { + if (!validNetworkIndex(networkIndex)) { + if (data != nullptr) { + delete data; + data = nullptr; + } + return false; + } + + // 2nd heap may have been active to allocate the NWPluginData, but here we need to keep the default heap active + #ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; + #endif // ifdef USE_SECOND_HEAP + + + clearNWPluginData(networkIndex); + + if (data != nullptr) { + if (Settings.getNetworkEnabled(networkIndex)) { +#if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + + if (!data->init_KVS()) { + delete data; + data = nullptr; + } else { + NWPlugin_task_data[networkIndex] = data; + } +#else // if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + NWPlugin_task_data[networkIndex] = data; +#endif // if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS +#if FEATURE_NETWORK_STATS + + NWPlugin_task_data[networkIndex]->initPluginStats(); +#endif // if FEATURE_NETWORK_STATS + + } else { + delete data; + data = nullptr; + } + } + return getNWPluginData(networkIndex) != nullptr; +} + +NWPluginData_base* getNWPluginData(ESPEasy::net::networkIndex_t networkIndex) { + if (nwpluginTaskData_initialized(networkIndex)) { + return NWPlugin_task_data[networkIndex]; + } + return nullptr; +} + +bool nwpluginTaskData_initialized(ESPEasy::net::networkIndex_t networkIndex) { + if (!validNetworkIndex(networkIndex)) { + return false; + } + return NWPlugin_task_data[networkIndex] != nullptr && + (NWPlugin_task_data[networkIndex]->getNWPluginID() == Settings.getNWPluginID_for_network(networkIndex)); +} + +#if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + +bool load_nwpluginTaskData_KVS(ESPEasy_key_value_store *kvs, ESPEasy::net::networkIndex_t networkIndex, ESPEasy::net::nwpluginID_t nwPluginID) +{ + if (!kvs) { return false; } + return kvs->load( + SettingsType::Enum::NetworkInterfaceSettings_Type, + networkIndex, + 0, + nwPluginID.value); +} + +bool store_nwpluginTaskData_KVS(ESPEasy_key_value_store *kvs, ESPEasy::net::networkIndex_t networkIndex, + ESPEasy::net::nwpluginID_t nwPluginID) +{ + if (!kvs) { return false; } + +# if defined(USES_NW003) || defined(USES_NW004) + + if (!kvs->isEmpty()) { + // Check if NW003_KEY_ETH_INDEX or NW004_KEY_ETH_INDEX is set. + // If not, then make sure there is no conflict with existing other network interfaces. + int8_t value = -1; + + if (kvs->getValue(1, value)) { + if (value == -1) { + // Value has not been set. Check if there are other Ethernet adapters set. + ++value; + + for (uint8_t i = 2; i < NETWORK_MAX; ++i) { + if (i != networkIndex) { + auto id = Settings.getNWPluginID_for_network(i); + + if ((id == ESPEasy::net::nwpluginID_t(3)) || + (id == ESPEasy::net::nwpluginID_t(4))) { + ++value; + } + } + } + kvs->setValue(1, value); + } + } + + } +# endif // if defined(USES_NW003) || defined(USES_NW004) + + return kvs->store( + SettingsType::Enum::NetworkInterfaceSettings_Type, + networkIndex, + 0, + nwPluginID.value); +} + +#endif // if FEATURE_ESPEASY_KEY_VALUE_STORE + +} // namespace net +} // namespace ESPEasy diff --git a/src/ESPEasy/net/_NWPlugin_Helper.h b/src/ESPEasy/net/_NWPlugin_Helper.h new file mode 100644 index 000000000..04137ca2a --- /dev/null +++ b/src/ESPEasy/net/_NWPlugin_Helper.h @@ -0,0 +1,46 @@ +#pragma once + +#include "../../ESPEasy_common.h" + +#include "../../src/CustomBuild/ESPEasyLimits.h" +#include "../../src/DataStructs/ESPEasy_EventStruct.h" +#include "../../src/Helpers/LongTermTimer.h" +#include "../../src/WebServer/KeyValueWriter_WebForm.h" +#include "../net/DataStructs/NWPluginData_base.h" +#include "../net/ESPEasyNetwork.h" + +#if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS +#include "../../src/Helpers/_ESPEasy_key_value_store.h" +#endif + +namespace ESPEasy { +namespace net { + + +extern NWPluginData_base *NWPlugin_task_data[NETWORK_MAX]; + +// Try to allocate in PSRAM or 2nd heap if possible +#define special_initNWPluginData(I, T) void *ptr = special_calloc(1, sizeof(T)); \ + if (ptr) { initNWPluginData(I, new (ptr) T()); } + + +// ============================================== +// Data used by instances of NWPlugins. +// ============================================= + +void resetNWPluginData(); + +void clearNWPluginData(ESPEasy::net::networkIndex_t networkIndex); + +bool initNWPluginData(ESPEasy::net::networkIndex_t networkIndex, + NWPluginData_base * data); + +NWPluginData_base* getNWPluginData(ESPEasy::net::networkIndex_t networkIndex); + +bool nwpluginTaskData_initialized(ESPEasy::net::networkIndex_t networkIndex); +#if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS +bool load_nwpluginTaskData_KVS(ESPEasy_key_value_store *kvs, ESPEasy::net::networkIndex_t networkIndex, ESPEasy::net::nwpluginID_t nwPluginID); +bool store_nwpluginTaskData_KVS(ESPEasy_key_value_store *kvs, ESPEasy::net::networkIndex_t networkIndex, ESPEasy::net::nwpluginID_t nwPluginID); +#endif +} // namespace net +} // namespace ESPEasy diff --git a/src/ESPEasy/net/eth/ESPEasyEth.cpp b/src/ESPEasy/net/eth/ESPEasyEth.cpp new file mode 100644 index 000000000..68f6f7ada --- /dev/null +++ b/src/ESPEasy/net/eth/ESPEasyEth.cpp @@ -0,0 +1,81 @@ +#include "../eth/ESPEasyEth.h" + +#if FEATURE_ETHERNET + +# include "../../../src/CustomBuild/ESPEasyLimits.h" +# include "../ESPEasyNetwork.h" +# include "../wifi/ESPEasyWifi.h" +# include "../../../src/ESPEasyCore/ESPEasy_Log.h" +# include "../../../src/ESPEasyCore/ESPEasyGPIO.h" +# include "../Globals/NetworkState.h" +# include "../../../src/Globals/Settings.h" +# include "../../../src/Helpers/Hardware_GPIO.h" +# include "../../../src/Helpers/StringConverter.h" +# include "../../../src/Helpers/Networking.h" + +# include +# include +# include + +# include + +namespace ESPEasy { +namespace net { +namespace eth { + +bool ethUseStaticIP() { return Settings.ETH_IP[0] != 0 && Settings.ETH_IP[0] != 255; } + +void ethSetupStaticIPconfig() { + const IPAddress IP_zero(0, 0, 0, 0); + + if (!ethUseStaticIP()) { + if (!ETH.config(IP_zero, IP_zero, IP_zero, IP_zero)) { + addLog(LOG_LEVEL_ERROR, F("ETH : Cannot set IP config")); + } + return; + } + const IPAddress ip = Settings.ETH_IP; + const IPAddress gw = Settings.ETH_Gateway; + const IPAddress subnet = Settings.ETH_Subnet; + const IPAddress dns = Settings.ETH_DNS; + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("ETH IP : Static IP : "); + log += formatIP(ip); + log += F(" GW: "); + log += formatIP(gw); + log += F(" SN: "); + log += formatIP(subnet); + log += F(" DNS: "); + log += formatIP(dns); + addLogMove(LOG_LEVEL_INFO, log); + } + ETH.config(ip, gw, subnet, dns); +} + +MAC_address ETHMacAddress() { + MAC_address mac; + + /* + if (!EthEventData.ethInitSuccess) { + addLog(LOG_LEVEL_ERROR, F("Call NetworkMacAddress() only on connected Ethernet!")); + } else { + ETH.macAddress(mac.mac); + } + */ + return mac; +} + + +bool ETHConnected() { + auto data = getFirst_Enabled_ETH_interface(); + + if (data) { return data->connected(); } + return false; +} + +} // namespace eth +} // namespace net +} // namespace ESPEasy + +#endif // if FEATURE_ETHERNET diff --git a/src/ESPEasy/net/eth/ESPEasyEth.h b/src/ESPEasy/net/eth/ESPEasyEth.h new file mode 100644 index 000000000..27b2d7b1d --- /dev/null +++ b/src/ESPEasy/net/eth/ESPEasyEth.h @@ -0,0 +1,30 @@ +#ifndef ESPEASY_ETH_H +#define ESPEASY_ETH_H + +#include "../../../ESPEasy_common.h" + +#if FEATURE_ETHERNET + +# include "../DataStructs/NWPluginData_static_runtime.h" +# include "../DataStructs/MAC_address.h" + +# include + +#include + +namespace ESPEasy { +namespace net { +namespace eth { + +bool ethUseStaticIP(); +void ethSetupStaticIPconfig(); +bool ETHConnected(); +MAC_address ETHMacAddress(); + + +} // namespace eth +} // namespace net +} // namespace ESPEasy + +#endif // if FEATURE_ETHERNET +#endif // ifndef ESPEASY_ETH_H diff --git a/src/ESPEasy/net/eth/ETH_NWPluginData_static_runtime.cpp b/src/ESPEasy/net/eth/ETH_NWPluginData_static_runtime.cpp new file mode 100644 index 000000000..f14a59057 --- /dev/null +++ b/src/ESPEasy/net/eth/ETH_NWPluginData_static_runtime.cpp @@ -0,0 +1,225 @@ +#include "../eth/ETH_NWPluginData_static_runtime.h" + +#if FEATURE_ETHERNET + + +# include "../../../src/Helpers/StringConverter.h" + + +namespace ESPEasy { +namespace net { +namespace eth { + +struct ETH_stats_and_cache_t { + ETH_stats_and_cache_t(uint8_t eth_index, networkIndex_t networkIndex) : + _networkIndex(networkIndex), + _eth_index(eth_index), + _eth(eth_index), + _stats_and_cache(&_eth, concat(F("eth"), eth_index)) { + _stats_and_cache.clear(networkIndex); +# if FEATURE_USE_IPV6 + _eth.enableIPv6(_stats_and_cache._enableIPv6); +# endif + + if (_eth.getStatusBits() & ESP_NETIF_STARTED_BIT) { + // Bit has been set, so it might be for this interface + _stats_and_cache.mark_start(); + } + } + + ~ETH_stats_and_cache_t() { + _eth.end(); + _stats_and_cache.processEvent_and_clear(); + } + + void mark_start(esp_eth_handle_t handle) + { + if (_eth.handle() == handle) { + _stats_and_cache.mark_start(); + + // _eth.enableIPv6(_stats_and_cache._enableIPv6); + } + } + + void mark_stop(esp_eth_handle_t handle) + { + if (_eth.handle() == handle) { + _stats_and_cache.mark_stop(); + } + } + + void mark_got_IP(ip_event_got_ip_t *event) + { + if (event && (_eth.netif() == event->esp_netif)) + { + _stats_and_cache.mark_got_IP(); + } + } + +# if FEATURE_USE_IPV6 + + void mark_got_IPv6(ip_event_got_ip6_t *event) + { + if (event && (_eth.netif() == event->esp_netif)) + { + _stats_and_cache.mark_got_IPv6(event); + } + } + +# endif // if FEATURE_USE_IPV6 + + void mark_lost_IP(ip_event_got_ip_t *event) + { + if (event && (_eth.netif() == event->esp_netif)) { + _stats_and_cache.mark_lost_IP(); + } + } + + void mark_connected(esp_eth_handle_t handle) + { + if (_eth.handle() == handle) { + # if FEATURE_USE_IPV6 + _eth.enableIPv6(_stats_and_cache._enableIPv6); +# endif + + _stats_and_cache.mark_connected(); + } + } + + void mark_disconnected(esp_eth_handle_t handle) + { + if (_eth.handle() == handle) { + _stats_and_cache.mark_disconnected(); + } + } + + const networkIndex_t _networkIndex; + + const uint8_t _eth_index; + ETHClass _eth; + NWPluginData_static_runtime _stats_and_cache; + +}; + +DEF_UP(ETH_stats_and_cache_t); + +network_event_handle_t nw_event_id = 0; +UP_ETH_stats_and_cache_t ETH_stats_and_cache[3]{}; + +ETH_stats_and_cache_t* getStatsAndCache(networkIndex_t networkIndex) +{ + for (size_t i = 0; i < NR_ELEMENTS(ETH_stats_and_cache); ++i) { + if (ETH_stats_and_cache[i] && (ETH_stats_and_cache[i]->_networkIndex == networkIndex)) { + return ETH_stats_and_cache[i].get(); + } + } + return nullptr; +} + +ETHClass * ETH_NWPluginData_static_runtime::getInterface(networkIndex_t networkIndex) +{ + auto data = getStatsAndCache(networkIndex); + + if (data) { return &data->_eth; } + + return nullptr; +} + +NWPluginData_static_runtime * ETH_NWPluginData_static_runtime::getNWPluginData_static_runtime(networkIndex_t networkIndex) +{ + auto data = getStatsAndCache(networkIndex); + + if (data) { return &data->_stats_and_cache; } + + return nullptr; +} + +ETHClass * ETH_NWPluginData_static_runtime::init(networkIndex_t networkIndex) +{ + exit(networkIndex); + + for (size_t i = 0; i < NR_ELEMENTS(ETH_stats_and_cache); ++i) { + if (!ETH_stats_and_cache[i]) { + // Found an empty slot + if (nw_event_id != 0) { + Network.removeEvent(nw_event_id); + nw_event_id = 0; + } + + ETH_stats_and_cache[i].reset(new (std::nothrow) ETH_stats_and_cache_t(i, networkIndex)); + + if (!ETH_stats_and_cache[i]) { return nullptr; } + nw_event_id = Network.onEvent(ETH_NWPluginData_static_runtime::onEvent); + + return Ð_stats_and_cache[i]->_eth; + } + } + return nullptr; +} + +void ETH_NWPluginData_static_runtime::exit(networkIndex_t networkIndex) +{ + for (size_t i = 0; i < NR_ELEMENTS(ETH_stats_and_cache); ++i) { + if (ETH_stats_and_cache[i]) { + if (ETH_stats_and_cache[i]->_networkIndex == networkIndex) { + ETH_stats_and_cache[i].reset(nullptr); + + if (nw_event_id != 0) { + Network.removeEvent(nw_event_id); + nw_event_id = 0; + } + } + } + } + + if (nw_event_id == 0) { + nw_event_id = Network.onEvent(ETH_NWPluginData_static_runtime::onEvent); + } +} + +void ETH_NWPluginData_static_runtime::onEvent( + arduino_event_id_t event, + arduino_event_info_t info) +{ + for (size_t i = 0; i < NR_ELEMENTS(ETH_stats_and_cache); ++i) { + if (ETH_stats_and_cache[i]) { + switch (event) + { + case ARDUINO_EVENT_ETH_START: + ETH_stats_and_cache[i]->mark_start(info.eth_started); + break; + case ARDUINO_EVENT_ETH_STOP: + ETH_stats_and_cache[i]->mark_stop(info.eth_stopped); + break; + case ARDUINO_EVENT_ETH_CONNECTED: + ETH_stats_and_cache[i]->mark_connected(info.eth_connected); + break; + case ARDUINO_EVENT_ETH_DISCONNECTED: + ETH_stats_and_cache[i]->mark_disconnected(info.eth_disconnected); + break; + case ARDUINO_EVENT_ETH_GOT_IP: + ETH_stats_and_cache[i]->mark_got_IP(&info.got_ip); + break; +# if FEATURE_USE_IPV6 + case ARDUINO_EVENT_ETH_GOT_IP6: + ETH_stats_and_cache[i]->mark_got_IPv6(&info.got_ip6); + break; +# endif // if FEATURE_USE_IPV6 + case ARDUINO_EVENT_ETH_LOST_IP: + ETH_stats_and_cache[i]->mark_lost_IP(&info.lost_ip); + break; + + default: + // addLog(LOG_LEVEL_INFO, concat(F("ETH Event: "), event)); + break; + } + } + } +} + +} // namespace eth +} // namespace net +} // namespace ESPEasy + + +#endif // if FEATURE_ETHERNET diff --git a/src/ESPEasy/net/eth/ETH_NWPluginData_static_runtime.h b/src/ESPEasy/net/eth/ETH_NWPluginData_static_runtime.h new file mode 100644 index 000000000..f762d0c95 --- /dev/null +++ b/src/ESPEasy/net/eth/ETH_NWPluginData_static_runtime.h @@ -0,0 +1,40 @@ +#pragma once + +#include "../../../ESPEasy_common.h" + +#if FEATURE_ETHERNET +# include "../DataStructs/NWPluginData_static_runtime.h" + +# include + +namespace ESPEasy { +namespace net { +namespace eth { + + +class ETH_NWPluginData_static_runtime +{ +public: + + static ETHClass* getInterface(networkIndex_t networkIndex); + + static NWPluginData_static_runtime* getNWPluginData_static_runtime(networkIndex_t networkIndex); + + static ETHClass* init(networkIndex_t networkIndex); + + static void exit(networkIndex_t networkIndex); + +private: + + static void onEvent(arduino_event_id_t event, + arduino_event_info_t info); + + + +}; // class ETH_NWPluginData_static_runtime + +} // namespace eth +} // namespace net +} // namespace ESPEasy + +#endif // if FEATURE_ETHERNET diff --git a/src/ESPEasy/net/wifi/ESPEasyWiFi_STA_Event_ESP32.cpp b/src/ESPEasy/net/wifi/ESPEasyWiFi_STA_Event_ESP32.cpp new file mode 100644 index 000000000..45bb3d6ca --- /dev/null +++ b/src/ESPEasy/net/wifi/ESPEasyWiFi_STA_Event_ESP32.cpp @@ -0,0 +1,317 @@ +#include "../wifi/ESPEasyWiFi_STA_Event_ESP32.h" + +#if FEATURE_WIFI + +# ifdef ESP32 +# if FEATURE_ETHERNET +# include +# endif // if FEATURE_ETHERNET +# if FEATURE_PPP_MODEM +# include +# endif + +# include "../../../src/DataStructs/RTCStruct.h" +# include "../../../src/DataTypes/ESPEasyTimeSource.h" +# include "../../../src/ESPEasyCore/ESPEasy_Log.h" +# include "../../../src/Globals/RTC.h" +# include "../../../src/Helpers/ESPEasy_time_calc.h" +# include "../../../src/Helpers/StringConverter.h" +# include "../../../src/Helpers/StringGenerator_WiFi.h" +# include "../../net/ESPEasyNetwork.h" +# include "../DataStructs/NWPluginData_static_runtime.h" +# include "../Globals/ESPEasyWiFiEvent.h" +# include "../Globals/NetworkState.h" +# include "../Globals/WiFi_AP_Candidates.h" +# include "../eth/ESPEasyEth.h" +# include "../wifi/ESPEasyWifi.h" + +namespace ESPEasy { +namespace net { +namespace wifi { + +static NWPluginData_static_runtime stats_and_cache(&WiFi.STA, F("WiFi")); + +static wifi_event_sta_connected_t _wifi_event_sta_connected; +static WiFiDisconnectReason _wifi_disconnect_reason = WiFiDisconnectReason::WIFI_DISCONNECT_REASON_UNSPECIFIED; + +static bool _ESPEasyWiFi_STA_EventHandler_initialized{}; + +ESPEasyWiFi_STA_EventHandler::ESPEasyWiFi_STA_EventHandler(networkIndex_t networkIndex) +{ + stats_and_cache.clear(networkIndex); + nw_event_id = Network.onEvent(ESPEasyWiFi_STA_EventHandler::WiFiEvent); + memset(&_wifi_event_sta_connected, 0, sizeof(_wifi_event_sta_connected)); + _ESPEasyWiFi_STA_EventHandler_initialized = true; +} + +ESPEasyWiFi_STA_EventHandler::~ESPEasyWiFi_STA_EventHandler() +{ + if (nw_event_id != 0) { + Network.removeEvent(nw_event_id); + } + nw_event_id = 0; + _ESPEasyWiFi_STA_EventHandler_initialized = false; + stats_and_cache.processEvent_and_clear(); +} + +bool ESPEasyWiFi_STA_EventHandler::initialized() { + return _ESPEasyWiFi_STA_EventHandler_initialized; +} + +NWPluginData_static_runtime * ESPEasyWiFi_STA_EventHandler::getNWPluginData_static_runtime() { return &stats_and_cache; } + +WiFiDisconnectReason ESPEasyWiFi_STA_EventHandler::getLastDisconnectReason() const { return _wifi_disconnect_reason; } + +uint8_t ESPEasyWiFi_STA_EventHandler::getAuthMode() const { return _wifi_event_sta_connected.authmode; } + +bool ESPEasyWiFi_STA_EventHandler::restore_dns_from_cache() const +{ + bool res{}; + + if (WiFi.STA.isDefault()) { + // Check to see if we may need to restore any cached DNS server + for (size_t i = 0; i < NR_ELEMENTS(stats_and_cache._dns_cache); ++i) { + auto tmp = WiFi.STA.dnsIP(i); + + if ((stats_and_cache._dns_cache[i] != INADDR_NONE) && (stats_and_cache._dns_cache[i] != tmp)) { + addLog(LOG_LEVEL_INFO, strformat( + F("WiFi STA: Restore cached DNS server %d from %s to %s"), + i, + tmp.toString().c_str(), + stats_and_cache._dns_cache[i].toString().c_str() + )); + WiFi.STA.dnsIP(i, stats_and_cache._dns_cache[i]); + res = true; + } + } + } + return res; +} + +const __FlashStringHelper * ESPEasyWiFi_STA_EventHandler::getWiFi_encryptionType() const +{ + return WiFi_encryptionType(_wifi_event_sta_connected.authmode); +} + +// ******************************************************************************** +// Functions called on events. +// Make sure not to call anything in these functions that result in delay() or yield() +// ******************************************************************************** +# include + +static bool ignoreDisconnectEvent = false; + +void ESPEasyWiFi_STA_EventHandler::WiFiEvent(WiFiEvent_t event_id, arduino_event_info_t info) { + bool clear_wifi_event_sta_connected = false; + + switch (event_id) + { + case ARDUINO_EVENT_WIFI_OFF: + clear_wifi_event_sta_connected = true; + addLog(LOG_LEVEL_INFO, F("WIFI_OFF")); + break; + case ARDUINO_EVENT_WIFI_READY: + // clear_wifi_event_sta_connected = true; + addLog(LOG_LEVEL_INFO, F("WIFI_READY")); + break; + case ARDUINO_EVENT_WIFI_SCAN_DONE: + addLog(LOG_LEVEL_INFO, F("SCAN_DONE")); + break; + case ARDUINO_EVENT_WIFI_STA_START: + clear_wifi_event_sta_connected = true; + stats_and_cache.mark_start(); + break; + case ARDUINO_EVENT_WIFI_STA_STOP: + clear_wifi_event_sta_connected = true; + stats_and_cache.mark_stop(); + break; + case ARDUINO_EVENT_WIFI_STA_CONNECTED: + memcpy(&_wifi_event_sta_connected, &info.wifi_sta_connected, sizeof(_wifi_event_sta_connected)); + stats_and_cache.mark_connected(); + break; + case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: + clear_wifi_event_sta_connected = true; + _wifi_disconnect_reason = static_cast(info.wifi_sta_disconnected.reason); + stats_and_cache.mark_disconnected(); + break; + case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE: + _wifi_event_sta_connected.authmode = info.wifi_sta_authmode_change.new_mode; + addLog(LOG_LEVEL_INFO, F("STA_AUTHMODE_CHANGE")); + break; + case ARDUINO_EVENT_WIFI_STA_GOT_IP: + stats_and_cache.mark_got_IP(); + break; +# if FEATURE_USE_IPV6 + case ARDUINO_EVENT_WIFI_STA_GOT_IP6: + stats_and_cache.mark_got_IPv6(&info.got_ip6); + break; +# endif // if FEATURE_USE_IPV6 + case ARDUINO_EVENT_WIFI_STA_LOST_IP: + stats_and_cache.mark_lost_IP(); + break; + + default: + break; + } + + if (clear_wifi_event_sta_connected) { + memset(&_wifi_event_sta_connected, 0, sizeof(_wifi_event_sta_connected)); + } + + /* + switch (event_id) + { + case ARDUINO_EVENT_WIFI_READY: + // ESP32 WiFi ready + break; + case ARDUINO_EVENT_WIFI_STA_START: + # ifndef BUILD_NO_DEBUG + + // addLog(LOG_LEVEL_INFO, F("WiFi : Event STA Started")); + # endif // ifndef BUILD_NO_DEBUG + break; + case ARDUINO_EVENT_WIFI_STA_STOP: + # ifndef BUILD_NO_DEBUG + + // addLog(LOG_LEVEL_INFO, F("WiFi : Event STA Stopped")); + # endif // ifndef BUILD_NO_DEBUG + break; + case ARDUINO_EVENT_WIFI_OFF: + # ifndef BUILD_NO_DEBUG + + // addLog(LOG_LEVEL_INFO, F("WiFi : Event WIFI off")); + # endif // ifndef BUILD_NO_DEBUG + break; + + case ARDUINO_EVENT_WIFI_AP_START: + # ifndef BUILD_NO_DEBUG + + // addLog(LOG_LEVEL_INFO, F("WiFi : Event AP Started")); + # endif // ifndef BUILD_NO_DEBUG + break; + case ARDUINO_EVENT_WIFI_AP_STOP: + # ifndef BUILD_NO_DEBUG + + // addLog(LOG_LEVEL_INFO, F("WiFi : Event AP Stopped")); + # endif // ifndef BUILD_NO_DEBUG + break; + case ARDUINO_EVENT_WIFI_STA_LOST_IP: + // ESP32 station lost IP and the IP is reset to 0 + # if FEATURE_ETHERNET + + if (active_network_medium == NetworkMedium_t::Ethernet) { + // DNS records are shared among WiFi and Ethernet (very bad design!) + // So we must restore the DNS records for Ethernet in case we started with WiFi and then plugged in Ethernet. + // As soon as WiFi is turned off, the DNS entry for Ethernet is cleared. + EthEventData.markLostIP(); + } + # endif // if FEATURE_ETHERNET + WiFiEventData.markLostIP(); + # ifndef BUILD_NO_DEBUG + + // addLog(LOG_LEVEL_INFO, + + // F("WiFi : Event Lost IP")); + # endif // ifndef BUILD_NO_DEBUG + break; + + case ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED: + // Receive probe request packet in soft-AP interface + // TODO TD-er: Must implement like onProbeRequestAPmode for ESP8266 + # ifndef BUILD_NO_DEBUG + + // addLog(LOG_LEVEL_INFO, F("WiFi : Event AP got probed")); + # endif // ifndef BUILD_NO_DEBUG + break; + + case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE: + WiFiEventData.setAuthMode(info.wifi_sta_authmode_change.new_mode); + break; + + case ARDUINO_EVENT_WIFI_STA_CONNECTED: + { + char ssid_copy[33]; // Ensure space for maximum len SSID (32) plus trailing 0 + memcpy(ssid_copy, info.wifi_sta_connected.ssid, info.wifi_sta_connected.ssid_len); + ssid_copy[32] = 0; // Potentially add 0-termination if none present earlier + WiFiEventData.markConnected((const char *)ssid_copy, info.wifi_sta_connected.bssid, info.wifi_sta_connected.channel); + WiFiEventData.setAuthMode(info.wifi_sta_connected.authmode); + + // addLog(LOG_LEVEL_INFO, F("WiFi : Event WIFI_STA_CONNECTED")); + break; + } + case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: + WiFiEventData.markDisconnect(static_cast(info.wifi_sta_disconnected.reason)); + break; + case ARDUINO_EVENT_WIFI_STA_GOT_IP: + { + // FIXME TD-er: Must also check event->esp_netif to see which interface got this event. + ignoreDisconnectEvent = false; + ip_event_got_ip_t *event = static_cast(&info.got_ip); + const IPAddress ip(event->ip_info.ip.addr); + const IPAddress netmask(event->ip_info.netmask.addr); + const IPAddress gw(event->ip_info.gw.addr); + WiFiEventData.markGotIP(ip, netmask, gw); + break; + } + # if FEATURE_USE_IPV6 + case ARDUINO_EVENT_WIFI_STA_GOT_IP6: + { + ip_event_got_ip6_t *event = static_cast(&info.got_ip6); + const IPAddress ip(IPv6, (const uint8_t *)event->ip6_info.ip.addr, event->ip6_info.ip.zone); + WiFiEventData.markGotIPv6(ip); + break; + } + case ARDUINO_EVENT_WIFI_AP_GOT_IP6: + addLog(LOG_LEVEL_INFO, F("WIFI : AP got IP6")); + break; + # endif // if FEATURE_USE_IPV6 + case ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED: + addLog(LOG_LEVEL_INFO, F("WIFI : AP assigned IP to STA")); + break; + case ARDUINO_EVENT_WIFI_AP_STACONNECTED: + WiFiEventData.markConnectedAPmode(info.wifi_ap_staconnected.mac); + break; + case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED: + WiFiEventData.markDisconnectedAPmode(info.wifi_ap_stadisconnected.mac); + break; + case ARDUINO_EVENT_WIFI_SCAN_DONE: + break; + # if FEATURE_ETHERNET + case ARDUINO_EVENT_ETH_START: + case ARDUINO_EVENT_ETH_CONNECTED: + case ARDUINO_EVENT_ETH_GOT_IP: + case ARDUINO_EVENT_ETH_DISCONNECTED: + case ARDUINO_EVENT_ETH_STOP: + case ARDUINO_EVENT_ETH_GOT_IP6: + + // Handled in EthEvent + break; + # endif // FEATURE_ETHERNET + + # if FEATURE_PPP_MODEM + + case ARDUINO_EVENT_PPP_START: + case ARDUINO_EVENT_PPP_CONNECTED: + case ARDUINO_EVENT_PPP_GOT_IP: + case ARDUINO_EVENT_PPP_LOST_IP: + case ARDUINO_EVENT_PPP_DISCONNECTED: + case ARDUINO_EVENT_PPP_STOP: + // Handled in ESPEasyWiFi_STA_EventHandler + break; + + # endif // if FEATURE_PPP_MODEM + + default: + { + addLogMove(LOG_LEVEL_ERROR, concat(F("UNKNOWN WIFI/ETH EVENT: "), event_id)); + break; + } + } + */ +} + +} // namespace wifi +} // namespace net +} // namespace ESPEasy +# endif // ifdef ESP32 +#endif // if FEATURE_WIFI diff --git a/src/ESPEasy/net/wifi/ESPEasyWiFi_STA_Event_ESP32.h b/src/ESPEasy/net/wifi/ESPEasyWiFi_STA_Event_ESP32.h new file mode 100644 index 000000000..e79eea1cb --- /dev/null +++ b/src/ESPEasy/net/wifi/ESPEasyWiFi_STA_Event_ESP32.h @@ -0,0 +1,70 @@ +#pragma once + +#include "../../../ESPEasy_common.h" + +#if FEATURE_WIFI +# ifdef ESP32 + +# include "../../../src/Helpers/LongTermOnOffTimer.h" + +# include + +// ******************************************************************************** + +// Work-around for setting _useStaticIP +// See reported issue: https://github.com/esp8266/Arduino/issues/4114 +// ******************************************************************************** +# include +# include +# include +# include + +# include "../DataStructs/NWPluginData_static_runtime.h" +# include "../wifi/WiFiDisconnectReason.h" + + +namespace ESPEasy { +namespace net { +namespace wifi { + + +class ESPEasyWiFi_STA_EventHandler +{ +public: + + ESPEasyWiFi_STA_EventHandler(networkIndex_t networkIndex); + ~ESPEasyWiFi_STA_EventHandler(); + + static bool initialized(); + + NWPluginData_static_runtime* getNWPluginData_static_runtime(); + + + WiFiDisconnectReason getLastDisconnectReason() const; + + uint8_t getAuthMode() const; + + bool restore_dns_from_cache() const; + + const __FlashStringHelper * getWiFi_encryptionType() const; + +private: + + // ******************************************************************************** + // Functions called on events. + // Make sure not to call anything in these functions that result in delay() or yield() + // ******************************************************************************** + static void WiFiEvent(WiFiEvent_t event, + arduino_event_info_t info); + + network_event_handle_t nw_event_id = 0; + + +}; // class ESPEasyWiFi_STA_EventHandler + +} // namespace wifi +} // namespace net +} // namespace ESPEasy + +# endif // ifdef ESP32 +#endif // if FEATURE_WIFI diff --git a/src/ESPEasy/net/wifi/ESPEasyWiFi_STA_Event_ESP8266.cpp b/src/ESPEasy/net/wifi/ESPEasyWiFi_STA_Event_ESP8266.cpp new file mode 100644 index 000000000..25aff7ac1 --- /dev/null +++ b/src/ESPEasy/net/wifi/ESPEasyWiFi_STA_Event_ESP8266.cpp @@ -0,0 +1,124 @@ +#include "../wifi/ESPEasyWiFi_STA_Event_ESP8266.h" + +#if FEATURE_WIFI +# ifdef ESP8266 + +# include "../../../src/DataStructs/RTCStruct.h" +# include "../../../src/DataTypes/ESPEasyTimeSource.h" +# include "../../../src/ESPEasyCore/ESPEasy_Log.h" +# include "../../../src/Globals/RTC.h" +# include "../../../src/Helpers/ESPEasy_time_calc.h" +# include "../../../src/Helpers/StringGenerator_WiFi.h" + + +# include "../ESPEasyNetwork.h" +# include "../wifi/ESPEasyWifi.h" + +# include "../Globals/ESPEasyWiFiEvent.h" +# include "../Globals/NetworkState.h" +# include "../Globals/WiFi_AP_Candidates.h" + + +namespace ESPEasy { +namespace net { +namespace wifi { + +static NWPluginData_static_runtime stats_and_cache(false, "WiFi"); // Cannot use flash strings during init of static objects +static WiFiDisconnectReason _wifi_disconnect_reason = WiFiDisconnectReason::WIFI_DISCONNECT_REASON_UNSPECIFIED; + +static uint8_t _enc_type{}; + +static bool _ESPEasyWiFi_STA_EventHandler_initialized{}; + +ESPEasyWiFi_STA_EventHandler::ESPEasyWiFi_STA_EventHandler(networkIndex_t networkIndex) +{ + stats_and_cache.clear(networkIndex); + + // WiFi event handlers + + if (!_ESPEasyWiFi_STA_EventHandler_initialized) { + stationConnectedHandler = WiFi.onStationModeConnected(onConnected); + stationDisconnectedHandler = WiFi.onStationModeDisconnected(onDisconnect); + stationGotIpHandler = WiFi.onStationModeGotIP(onGotIP); + stationModeDHCPTimeoutHandler = WiFi.onStationModeDHCPTimeout(onDHCPTimeout); + stationModeAuthModeChangeHandler = WiFi.onStationModeAuthModeChanged(onStationModeAuthModeChanged); + APModeStationConnectedHandler = WiFi.onSoftAPModeStationConnected(onConnectedAPmode); + APModeStationDisconnectedHandler = WiFi.onSoftAPModeStationDisconnected(onDisconnectedAPmode); + _ESPEasyWiFi_STA_EventHandler_initialized = true; + } +} + +ESPEasyWiFi_STA_EventHandler::~ESPEasyWiFi_STA_EventHandler() +{ + stats_and_cache.processEvent_and_clear(); +} + +bool ESPEasyWiFi_STA_EventHandler::initialized() { + return _ESPEasyWiFi_STA_EventHandler_initialized; +} + +NWPluginData_static_runtime* ESPEasyWiFi_STA_EventHandler::getNWPluginData_static_runtime() { return &stats_and_cache; } + +WiFiDisconnectReason ESPEasyWiFi_STA_EventHandler::getLastDisconnectReason() const { return _wifi_disconnect_reason; } + +uint8_t ESPEasyWiFi_STA_EventHandler::getAuthMode() const { return _enc_type; } + +const __FlashStringHelper * ESPEasyWiFi_STA_EventHandler::getWiFi_encryptionType() const +{ + return WiFi_encryptionType(_enc_type); +} + +// ******************************************************************************** +// Functions called on events. +// Make sure not to call anything in these functions that result in delay() or yield() +// ******************************************************************************** +void ESPEasyWiFi_STA_EventHandler::onConnected(const WiFiEventStationModeConnected& event) { + stats_and_cache.mark_connected(); + + _enc_type = WiFi_AP_Candidates.getCurrent().enc_type; +} + +void ESPEasyWiFi_STA_EventHandler::onDisconnect(const WiFiEventStationModeDisconnected& event) { + stats_and_cache.mark_disconnected(); + stats_and_cache.mark_lost_IP(); // ESP8266 doesn't have a separate event for lost IP + _wifi_disconnect_reason = event.reason; + + if (WiFi.status() == WL_CONNECTED) { + // See https://github.com/esp8266/Arduino/issues/5912 + WiFi.persistent(false); + WiFi.disconnect(false); + delay(0); + } +} + +void ESPEasyWiFi_STA_EventHandler::onGotIP(const WiFiEventStationModeGotIP& event) +{ + stats_and_cache.mark_got_IP(); + + // WiFiEventData.markGotIP(event.ip, event.mask, event.gw); +} + +void ESPEasyWiFi_STA_EventHandler::onDHCPTimeout() { + // WiFiEventData.processedDHCPTimeout = false; +} + +void ESPEasyWiFi_STA_EventHandler::onConnectedAPmode(const WiFiEventSoftAPModeStationConnected& event) { + // WiFiEventData.markConnectedAPmode(event.mac); +} + +void ESPEasyWiFi_STA_EventHandler::onDisconnectedAPmode(const WiFiEventSoftAPModeStationDisconnected& event) { + // WiFiEventData.markDisconnectedAPmode(event.mac); +} + +void ESPEasyWiFi_STA_EventHandler::onStationModeAuthModeChanged(const WiFiEventStationModeAuthModeChanged& event) { + _enc_type = event.newMode; + + // WiFiEventData.setAuthMode(event.newMode); +} + +} // namespace wifi +} // namespace net +} // namespace ESPEasy + +# endif // ifdef ESP8266 +#endif // if FEATURE_WIFI diff --git a/src/ESPEasy/net/wifi/ESPEasyWiFi_STA_Event_ESP8266.h b/src/ESPEasy/net/wifi/ESPEasyWiFi_STA_Event_ESP8266.h new file mode 100644 index 000000000..93b65d52d --- /dev/null +++ b/src/ESPEasy/net/wifi/ESPEasyWiFi_STA_Event_ESP8266.h @@ -0,0 +1,71 @@ +#pragma once + +#include "../../../ESPEasy_common.h" + +#if FEATURE_WIFI + + +# ifdef ESP8266 + +# include "../DataStructs/NWPluginData_static_runtime.h" + +# include + +// ******************************************************************************** + +// Work-around for setting _useStaticIP +// See reported issue: https://github.com/esp8266/Arduino/issues/4114 +// ******************************************************************************** + +# include +# include + +namespace ESPEasy { +namespace net { +namespace wifi { + + +class ESPEasyWiFi_STA_EventHandler +{ +public: + + ESPEasyWiFi_STA_EventHandler(networkIndex_t networkIndex); + ~ESPEasyWiFi_STA_EventHandler(); + + static bool initialized(); + + NWPluginData_static_runtime* getNWPluginData_static_runtime(); + + WiFiDisconnectReason getLastDisconnectReason() const; + uint8_t getAuthMode() const; + + const __FlashStringHelper * getWiFi_encryptionType() const; + + + // ******************************************************************************** + // Functions called on events. + // Make sure not to call anything in these functions that result in delay() or yield() + // ******************************************************************************** + static void onConnected(const WiFiEventStationModeConnected& event); + + static void onDisconnect(const WiFiEventStationModeDisconnected& event); + + static void onGotIP(const WiFiEventStationModeGotIP& event); + + static void onDHCPTimeout(); + + static void onConnectedAPmode(const WiFiEventSoftAPModeStationConnected& event); + + static void onDisconnectedAPmode(const WiFiEventSoftAPModeStationDisconnected& event); + + static void onStationModeAuthModeChanged(const WiFiEventStationModeAuthModeChanged& event); + +}; // class ESPEasyWiFi_STA_EventHandler + +} // namespace wifi +} // namespace net +} // namespace ESPEasy + +# endif // ifdef ESP8266 + +#endif // if FEATURE_WIFI diff --git a/src/ESPEasy/net/wifi/ESPEasyWiFi_state_machine.cpp b/src/ESPEasy/net/wifi/ESPEasyWiFi_state_machine.cpp new file mode 100644 index 000000000..847b3fb2e --- /dev/null +++ b/src/ESPEasy/net/wifi/ESPEasyWiFi_state_machine.cpp @@ -0,0 +1,616 @@ +#include "../wifi/ESPEasyWiFi_state_machine.h" + +#if FEATURE_WIFI + +# include "../../../src/ESPEasyCore/ESPEasy_Log.h" +# include "../../../src/Globals/EventQueue.h" +# include "../../../src/Globals/RTC.h" +# include "../../../src/Globals/SecuritySettings.h" +# include "../../../src/Globals/Settings.h" +# include "../../../src/Helpers/NetworkStatusLED.h" +# include "../../../src/Helpers/StringConverter.h" +# include "../../../src/Helpers/StringGenerator_WiFi.h" + + +# include "../ESPEasyNetwork.h" // for setNetworkMedium, however this should not be part of the WiFi code +# include "../Globals/ESPEasyWiFiEvent.h" +# include "../Globals/WiFi_AP_Candidates.h" +# include "../wifi/ESPEasyWifi.h" +# include "../wifi/ESPEasyWifi_abstracted.h" + + +namespace ESPEasy { +namespace net { +namespace wifi { + + # define WIFI_STATE_MACHINE_STA_CONNECTING_TIMEOUT 10000 + # define WIFI_STATE_MACHINE_STA_AP_SCANNING_TIMEOUT 10000 + # define WIFI_STATE_MACHINE_STA_SCANNING_TIMEOUT 10000 + # define WIFI_STATE_MACHINE_AP_ONLY_TIMEOUT 60000 + +void ESPEasyWiFi_t::setup() { + if (!Settings.getNetworkEnabled(NETWORK_INDEX_WIFI_STA)) return; + + // TODO TD-er: Must maybe also call 'disable()' first? + + // TODO TD-er: Load settings + + // TODO TD-er: Check if settings have changed. + + if (_disabledAtBoot) { + disable(); + return; + } + + if (WiFi_pre_setup()) { begin(); } +} + +void ESPEasyWiFi_t::enable() {} + +void ESPEasyWiFi_t::disable() { setState(WiFiState_e::Disabled, 100); } + +void ESPEasyWiFi_t::begin() { + if (WiFi_AP_Candidates.hasCandidates()) { + setState(WiFiState_e::IdleWaiting, 100); + } else { +// if (!Settings.DoNotStartAPfallback_ConnectFail()) { +// setState(WiFiState_e::AP_only, WIFI_STATE_MACHINE_AP_ONLY_TIMEOUT); +// } else + { + if (WifiIsAP(WiFi.getMode())) { + // TODO TD-er: Must check if any client is connected. + // If not, then we can disable AP mode and switch to WiFiState_e::STA_Scanning + setState(WiFiState_e::STA_AP_Scanning, WIFI_STATE_MACHINE_STA_AP_SCANNING_TIMEOUT); + } else { + // setState(WiFiState_e::STA_AP_Scanning, WIFI_STATE_MACHINE_STA_AP_SCANNING_TIMEOUT); + setState(WiFiState_e::STA_Scanning, WIFI_STATE_MACHINE_STA_SCANNING_TIMEOUT); + } + } + } +} + +void ESPEasyWiFi_t::loop() +{ + // TODO TD-er: Must inspect WiFiEventData to see if we need to update some state here. + auto wifi_STA_data = getWiFi_STA_NWPluginData_static_runtime(); + + if (!wifi_STA_data) { return; } + + if (_state != WiFiState_e::IdleWaiting) { + if (_callbackError || + (_state_timeout.isSet() && _state_timeout.timeReached())) + { + // TODO TD-er: Must check what error was given??? + _callbackError = false; + setState(WiFiState_e::WiFiOFF); + } + } + + switch (_state) + { + case WiFiState_e::Disabled: + // Do nothing here, as the device is disabled. + break; + case WiFiState_e::WiFiOFF: + begin(); + + // setState(WiFiState_e::IdleWaiting, 100); + break; + case WiFiState_e::AP_only: + case WiFiState_e::AP_Fallback: + + if (WiFi_AP_Candidates.hasCandidates() || + (_state_timeout.timeReached() && + !ESPEasy::net::wifi::wifiAPmodeActivelyUsed())) { + setState(WiFiState_e::IdleWaiting, 100); + } + break; + case WiFiState_e::IdleWaiting: + + if (connected()) { + setState(WiFiState_e::STA_Connected, 100); + break; + } + + if (_state_timeout.timeReached() || (getSTA_connected_state() == STA_connected_state::Idle)) { + // This is where we decide what to do next: + // - Reconnect + // - Scan + // + + + // Do we have candidate to connect to ? + if (WiFi_AP_Candidates.hasCandidates()) { + setState(WiFiState_e::STA_Connecting, WIFI_STATE_MACHINE_STA_CONNECTING_TIMEOUT); + } else if ((WiFi_AP_Candidates.scanComplete() == 0) + || (WiFi_AP_Candidates.scanComplete() == -3)) { + if (WifiIsAP(WiFi.getMode())) { + // TODO TD-er: Must check if any client is connected. + // If not, then we can disable AP mode and switch to WiFiState_e::STA_Scanning + setState(WiFiState_e::STA_AP_Scanning, WIFI_STATE_MACHINE_STA_AP_SCANNING_TIMEOUT); + } else { + // setState(WiFiState_e::STA_AP_Scanning, WIFI_STATE_MACHINE_STA_AP_SCANNING_TIMEOUT); + setState(WiFiState_e::STA_Scanning, WIFI_STATE_MACHINE_STA_SCANNING_TIMEOUT); + } + + // Move up? + } else if (!WiFi_AP_Candidates.hasCandidateCredentials() || + !Settings.DoNotStartAPfallback_ConnectFail()) { + if (!WiFi_AP_Candidates.hasCandidateCredentials() + + // && !WiFiEventData.warnedNoValidWiFiSettings + ) + { + addLog(LOG_LEVEL_ERROR, F("WIFI : No valid wifi settings")); + + // WiFiEventData.warnedNoValidWiFiSettings = true; + } + + wifi_STA_data->_establishConnectStats.clear(); + + // WiFiEventData.wifiConnectAttemptNeeded = false; + // end move up?? + } + } + break; + case WiFiState_e::STA_Scanning: + { + // -1 if scan not finished + auto scanCompleteStatus = WiFi_AP_Candidates.scanComplete(); + + if (scanCompleteStatus >= 0) { + WiFi_AP_Candidates.load_knownCredentials(); + WiFi_AP_Candidates.process_WiFiscan(); +# ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, strformat( + F("WiFi : Scan done, found %d APs"), + WiFi_AP_Candidates.scanComplete())); +# endif // ifndef BUILD_NO_DEBUG + } else if (scanCompleteStatus == -2) { // WIFI_SCAN_FAILED + addLog(LOG_LEVEL_ERROR, F("WiFi : Scan failed")); + + // WiFi.scanDelete(); + setState(WiFiState_e::WiFiOFF, 1000); + } + + if (_state_timeout.timeReached() || (scanCompleteStatus >= 0)) { + // WiFi.scanDelete(); + + if (_state_timeout.timeReached()) { +# ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_ERROR, F("WiFi : Scan Running Timeout")); +# endif + } + + if (WiFi_AP_Candidates.hasCandidates()) { + setState(WiFiState_e::WiFiOFF, 100); + } else { + if (shouldStartAP_fallback()) { + setState(WiFiState_e::AP_Fallback, Settings.APfallback_minimal_on_time_sec() * 1000); + // TODO TD-er: Must keep track of whether the user has forced AP to be autostarted. + } else { + setState(WiFiState_e::WiFiOFF, 1000); + } + } + } + break; + } + case WiFiState_e::STA_AP_Scanning: + { + // -1 if scan not finished + auto scanCompleteStatus = WiFi_AP_Candidates.scanComplete(); + + if (scanCompleteStatus >= 0) { + WiFi_AP_Candidates.load_knownCredentials(); + WiFi_AP_Candidates.process_WiFiscan(); +# ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, strformat( + F("WiFi : Scan channel %d done, found %d APs"), + _scan_channel, + WiFi_AP_Candidates.scanComplete())); +# endif // ifndef BUILD_NO_DEBUG + } else if (scanCompleteStatus == -2) { // WIFI_SCAN_FAILED + addLog(LOG_LEVEL_ERROR, F("WiFi : Scan failed")); + + // WiFi.scanDelete(); + setState(WiFiState_e::WiFiOFF, 1000); + } + + if (_state_timeout.timeReached() || (scanCompleteStatus >= 0)) { + // WiFi.scanDelete(); + + if (_state_timeout.timeReached()) { +# ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_ERROR, F("WiFi : Scan Running Timeout")); +# endif + } + ++_scan_channel; + + if (_scan_channel > 14) { + _scan_channel = 0; + + if (!WiFi_AP_Candidates.hasCandidateCredentials() && + !Settings.DoNotStartAPfallback_ConnectFail()) { + setState(WiFiState_e::AP_only, WIFI_STATE_MACHINE_AP_ONLY_TIMEOUT); + } else { + setState(WiFiState_e::WiFiOFF, 100); + } + } + else { + setState(WiFiState_e::STA_AP_Scanning, 500); + } + } + break; + + // Check if scanning is finished + // When scanning per channel, call for scanning next channel + } + case WiFiState_e::STA_Connecting: + case WiFiState_e::STA_Reconnecting: + + // Check if (re)connecting has finished + { + const STA_connected_state sta_connected_state = getSTA_connected_state(); + + if (sta_connected_state == STA_connected_state::Connected) { + setState(WiFiState_e::STA_Connected); + } else if (_state_timeout.timeReached()) { + if (_state == WiFiState_e::STA_Connecting) { + setState(WiFiState_e::STA_Reconnecting, WIFI_STATE_MACHINE_STA_CONNECTING_TIMEOUT); + } else { + setState(WiFiState_e::WiFiOFF); + } + } + + break; + } + case WiFiState_e::STA_Connected: + + // Check if still connected + if (getSTA_connected_state() != STA_connected_state::Connected) { + // setState(WiFiState_e::WiFiOFF); + if (WiFi_AP_Candidates.hasCandidates()) { + setState(WiFiState_e::STA_Connecting, WIFI_STATE_MACHINE_STA_CONNECTING_TIMEOUT); + } else { + setState(WiFiState_e::STA_Scanning, WIFI_STATE_MACHINE_STA_SCANNING_TIMEOUT); + } + + /* + if (Settings.UseRules) + { + eventQueue.add(F("WiFi#Disconnected")); + } + statusLED(false); + */ + + } else { + // Else mark last timestamp seen as connected + _last_seen_connected.setNow(); + # if FEATURE_SET_WIFI_TX_PWR + SetWiFiTXpower(); + # endif + } + break; + } + + + { + // Check if we need to start AP + // Flag captive portal in webserver and/or whether we might be in setup mode + } +} + +bool ESPEasyWiFi_t::connected() const +{ + return getSTA_connected_state() == STA_connected_state::Connected; +} + +void ESPEasyWiFi_t::disconnect() { doWiFiDisconnect(); } + +void ESPEasyWiFi_t::setState(WiFiState_e newState, uint32_t timeout) { + if (newState == _state) { return; } +# ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog( + LOG_LEVEL_INFO, + concat(F("WiFi : Set state from: "), toString(_state)) + + concat(F(" to: "), toString(newState)) + + concat(F(" timeout: "), timeout)); + } +# endif // ifndef BUILD_NO_DEBUG + + if (_state == WiFiState_e::AP_only || + _state == WiFiState_e::AP_Fallback) { + setAPinternal(false); + setAP(false); + } + + if (_state == WiFiState_e::STA_Connected) + { + auto wifi_STA_data = getWiFi_STA_NWPluginData_static_runtime(); + + if (wifi_STA_data) { + wifi_STA_data->mark_disconnected(); + + if (WiFi.status() == WL_CONNECTED) { + WiFi.disconnect(); + } + } + } + + if ((_state == WiFiState_e::STA_AP_Scanning) || + (_state == WiFiState_e::STA_Scanning)) + { + WiFi_AP_Candidates.process_WiFiscan(); + } + + if (timeout == 0) + { + _state_timeout.clear(); + + } else { + _state_timeout.setMillisFromNow(timeout); + } + + _last_state_change.setNow(); + _state = newState; + + + switch (newState) + { + case WiFiState_e::Disabled: + // Do nothing here, as the device is disabled. + setSTA(false); + break; + case WiFiState_e::WiFiOFF: + // TODO TD-er: Must cancel all and turn off WiFi. + setSTA_AP(false, false); + break; + case WiFiState_e::AP_only: + case WiFiState_e::AP_Fallback: + setAPinternal(true); + break; + case WiFiState_e::IdleWaiting: + // Do nothing here as we're waiting till the timeout is over + break; + case WiFiState_e::STA_AP_Scanning: + + // Start scanning per channel + if (_scan_channel == 0) { _scan_channel = 1; } + + // break; + case WiFiState_e::STA_Scanning: + // Start scanning + startScanning(); + break; + case WiFiState_e::STA_Connecting: + case WiFiState_e::STA_Reconnecting: + + // Start connecting + ++_connect_attempt; + if (!connectSTA()) { + // TODO TD-er: Must keep track of failed attempts and start AP when either no credentials present or nr. of attempts failed > some + // threshold. + if (!WiFi_AP_Candidates.hasCandidates()) { + setState(WiFiState_e::STA_Scanning, WIFI_STATE_MACHINE_STA_CONNECTING_TIMEOUT); + } else { + setState(WiFiState_e::IdleWaiting, 100); + } + } + break; + case WiFiState_e::STA_Connected: + { +# ifdef ESP32 + + // FIXME TD-er: Must move to ESP32-specific cpp file + // WiFi.STA.setDefault(); +# endif // ifdef ESP32 + _connect_attempt = 0; + _last_seen_connected.setNow(); + _state_timeout.clear(); + auto wifi_STA_data = getWiFi_STA_NWPluginData_static_runtime(); + + if (wifi_STA_data) { + wifi_STA_data->mark_connected(); + } + + + /* + if (Settings.UseRules) + { + eventQueue.add(F("WiFi#Connected")); + } + statusLED(true); + */ + break; + + } + } +} + +void ESPEasyWiFi_t::checkConnectProgress() {} + +void ESPEasyWiFi_t::startScanning() +{ + _state = _scan_channel == 0 ? WiFiState_e::STA_Scanning : WiFiState_e::STA_AP_Scanning; + setSTA(true); + WifiScan(true, _scan_channel); + _last_state_change.setNow(); +} + +bool ESPEasyWiFi_t::connectSTA() +{ + auto wifi_STA_data = getWiFi_STA_NWPluginData_static_runtime(); + + if (!wifi_STA_data) { return false; } + + // Make sure the timer is set to off. + // TODO TD-er: Should we check to see if it is still on and then do what???? + wifi_STA_data->_establishConnectStats.setOff(); + + if (!WiFi_AP_Candidates.hasCandidateCredentials()) + { + /* + if (!WiFiEventData.warnedNoValidWiFiSettings) + { + addLog(LOG_LEVEL_ERROR, F("WIFI : No valid wifi settings")); + WiFiEventData.warnedNoValidWiFiSettings = true; + } + wifi_STA_data->_establishConnectStats.clear(); + + // WiFiEventData.last_wifi_connect_attempt_moment.clear(); + // _connect_attempt = 1; + WiFiEventData.wifiConnectAttemptNeeded = false; + */ + + // No need to wait longer to start AP mode. + if (!Settings.DoNotStartAPfallback_ConnectFail()) + { + // setAPinternal(true); + } + return false; + } + + /* + if (WiFiEventData.lastDisconnectReason != WIFI_DISCONNECT_REASON_UNSPECIFIED) { + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, concat( + F("WiFi : Disconnect reason: "), + getWiFi_disconnectReason_str())); + # endif // ifndef BUILD_NO_DEBUG + WiFiEventData.processedDisconnect = true; + } + + WiFiEventData.warnedNoValidWiFiSettings = false; + */ + WiFi_pre_STA_setup(); +# if defined(ESP8266) + wifi_station_set_hostname(NetworkCreateRFCCompliantHostname().c_str()); + +# endif // if defined(ESP8266) +# if defined(ESP32) + + // WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE); +# endif // if defined(ESP32) + doSetConnectionSpeed(); + setupStaticIPconfig(); + + // Start the process of connecting or starting AP + if (!WiFi_AP_Candidates.getNext(true)) + { + return false; + } + + const WiFi_AP_Candidate candidate = WiFi_AP_Candidates.getCurrent(); +# ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, strformat( + F("WIFI : Connecting %s attempt #%u"), + candidate.toString().c_str(), + wifi_STA_data->_establishConnectStats.getCycleCount() + 1)); + } +# endif // ifndef BUILD_NO_DEBUG + + // WiFiEventData.markWiFiBegin(); + + if (prepareWiFi()) { + setNetworkMedium(NetworkMedium_t::WIFI); + RTC.clearLastWiFi(); + RTC.lastWiFiSettingsIndex = candidate.index; + +# if FEATURE_SET_WIFI_TX_PWR + float tx_pwr = 0; // Will be set higher based on RSSI when needed. + // FIXME TD-er: Must check wifi_STA_data->_establishConnectStats.getCycleCount() to increase TX power + + if (Settings.UseMaxTXpowerForSending()) { + tx_pwr = Settings.getWiFi_TX_power(); + } + SetWiFiTXpower(tx_pwr, candidate.rssi); +# endif // if FEATURE_SET_WIFI_TX_PWR + + // Start connect attempt now, so no longer needed to attempt new connection. + // WiFiEventData.wifiConnectAttemptNeeded = false; + + // WiFiEventData.wifiConnectInProgress = true; + const String key = WiFi_AP_CandidatesList::get_key(candidate.index); + +# if FEATURE_USE_IPV6 + + if (Settings.EnableIPv6()) { + WiFi.enableIPv6(true); + } +# endif // if FEATURE_USE_IPV6 + +# ifdef ESP32 + + if (Settings.IncludeHiddenSSID()) { + doSetWiFiCountryPolicyManual(); + } +# endif // ifdef ESP32 + wifi_STA_data->mark_begin_establish_connection(); + + if (candidate.bits.isHidden /*&& Settings.HiddenSSID_SlowConnectPerBSSID()*/) { + // WiFi.disconnect(false, true); +# ifdef ESP32 + WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); + WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL); +# endif // ifdef ESP32 + delay(100); + WiFi.begin(candidate.ssid.c_str(), key.c_str(), candidate.channel, candidate.bssid.mac); + + // If the ssid returned from the scan is empty, it is a hidden SSID + // it appears that the WiFi.begin() function is asynchronous and takes + // additional time to connect to a hidden SSID. Therefore a delay of 1000ms + // is added for hidden SSIDs before calling WiFi.status() + delay(1000); + + // WiFi.waitForConnectResult(6000); + } else { + if (candidate.allowQuickConnect()) { +# ifdef ESP32 + WiFi.setScanMethod(WIFI_FAST_SCAN); +# endif + WiFi.begin(candidate.ssid.c_str(), key.c_str(), candidate.channel, candidate.bssid.mac); + } else { +# ifdef ESP32 + WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); + WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL); +# endif // ifdef ESP32 + WiFi.begin(candidate.ssid.c_str(), key.c_str()); + } + } + + // Always wait for a second + WiFi.waitForConnectResult(1000); // https://github.com/arendst/Tasmota/issues/14985 + + } + + return true; +} + +bool ESPEasyWiFi_t::shouldStartAP_fallback() const +{ + if ((Settings.APfallback_autostart_max_uptime_m() * 1000) > millis()) { + return false; + } + + if (Settings.StartAPfallback_NoCredentials() && !SecuritySettings.hasWiFiCredentials()) { + return true; + } + + if (Settings.DoNotStartAPfallback_ConnectFail()) { + return false; + } + + return _connect_attempt > Settings.ConnectFailRetryCount; +} + + + +} // namespace wifi +} // namespace net +} // namespace ESPEasy + +#endif // if FEATURE_WIFI diff --git a/src/ESPEasy/net/wifi/ESPEasyWiFi_state_machine.h b/src/ESPEasy/net/wifi/ESPEasyWiFi_state_machine.h new file mode 100644 index 000000000..4f47ef814 --- /dev/null +++ b/src/ESPEasy/net/wifi/ESPEasyWiFi_state_machine.h @@ -0,0 +1,138 @@ +#pragma once + +#include "../../../ESPEasy_common.h" + +#if FEATURE_WIFI + +# include "../DataStructs/WiFi_AP_Candidate.h" +# include "../../../src/Helpers/LongTermTimer.h" +# include "../../../src/Helpers/LongTermOnOffTimer.h" + +# include "../wifi/WiFi_STA_connected_state.h" +# include "../wifi/WiFi_State.h" + +# include + + +namespace ESPEasy { +namespace net { +namespace wifi { + +class ESPEasyWiFi_t +{ +public: + + // Called after settings have changed or at boot + void setup(); + + // Start the process of connecting or start AP, depending on the existing configuration. + void enable(); + + // Terminate WiFi activity + void disable(); + +private: + + void begin(); + +public: + + // Process the state machine for managing WiFi connection + void loop(); + + WiFiState_e getState() const + { + return _state; + } + + bool connected() const; + + // Get the IP-address in this order: + // - STA interface if connected, + // - AP interface if active + // - 0.0.0.0 if neither connected nor active. + IPAddress getIP() const; + + void disconnect(); + +private: + + void setState(WiFiState_e newState, + uint32_t timeout = 0); + + // Handle timeouts + start of AP mode + void checkConnectProgress(); + + // Check to see if we already have some AP to connect to. + void checkScanningProgress(); + + void startScanning(); + + bool connectSTA(); + + + STA_connected_state getSTA_connected_state() const; + + bool shouldStartAP_fallback() const; + + // WiFi_AP_Candidate _active_sta; + // WiFi_AP_Candidate _AP_conf; + + // String _last_ssid; + // MAC_address _last_bssid; + // uint8_t _last_channel = 0; + WiFiState_e _state = WiFiState_e::Disabled; + + LongTermTimer _last_state_change; + LongTermTimer _state_timeout; + LongTermTimer _last_seen_connected; + + uint8_t _scan_channel{}; + + // LongTermTimer _AP_start_timestamp; + + uint32_t _connect_attempt = 0; + + + + // uint32_t _nrReconnects = 0; + + bool _callbackError = false; + + // Special modes for initial setup or re-configuration of a node via serial port/IMPROV + // bool _improvActive = false; + // bool _setupActive = false; + + + // WiFi settings => Move to separate class/struct + #ifdef ESP32P4 + bool _disabledAtBoot = false; + #else + bool _disabledAtBoot = false; + #endif + + // bool _passiveScan = false; + // bool _includeHiddenSSID = false; + // float _TX_power = -1; + + // Manual IP settings + // IP + // Gateway + // Subnet + // DNS + + + // soft AP configuration => TODO TD-er: Implement + // bool _must_Start_AP; // "Do Not Start AP" + // bool _has_ap{false}; // Maybe just check for active mode? + // WiFiAP _ap_config; + // WPA AP mode key + // Don't force /setup in AP-Mode + + +}; // class ESPEasyWiFi_t + +} // namespace wifi +} // namespace net +} // namespace ESPEasy +#endif // if FEATURE_WIFI diff --git a/src/ESPEasy/net/wifi/ESPEasyWiFi_state_machine_ESP32.cpp b/src/ESPEasy/net/wifi/ESPEasyWiFi_state_machine_ESP32.cpp new file mode 100644 index 000000000..d8a11675a --- /dev/null +++ b/src/ESPEasy/net/wifi/ESPEasyWiFi_state_machine_ESP32.cpp @@ -0,0 +1,89 @@ +#include "../wifi/ESPEasyWiFi_state_machine.h" + +#ifdef ESP32 +# if FEATURE_WIFI + +# include +# include // Needed to call ESP-IDF functions like esp_wifi_.... + +# ifndef ESP32P4 +# include +# endif + +# include +# include +# include + +# include "../wifi/ESPEasyWifi_abstracted.h" + +# include "../../../src/Globals/Settings.h" + + +namespace ESPEasy { +namespace net { +namespace wifi { + +IPAddress ESPEasyWiFi_t::getIP() const +{ + if (WiFi.STA.hasIP()) { + return WiFi.STA.localIP(); + } + + /* + if (WiFi.AP.hasIP()) { + return WiFi.AP.localIP(); + } + */ + return IPAddress(); +} + +STA_connected_state ESPEasyWiFi_t::getSTA_connected_state() const +{ + if (WiFi.STA.connected()) { + if (WiFi.STA.hasIP()) { + return STA_connected_state::Connected; + } + return STA_connected_state::Connecting; + } + + + switch (WiFi.status()) + { + case WL_CONNECTED: + break; // return STA_connected_state::Connected; + + case WL_NO_SSID_AVAIL: + return STA_connected_state::Error_Not_Found; + + case WL_CONNECT_FAILED: + case WL_CONNECTION_LOST: + return STA_connected_state::Error_Connect_Failed; + + case WL_IDLE_STATUS: + break; // return STA_connected_state::Connecting; + + + case WL_NO_SHIELD: + case WL_STOPPED: + case WL_SCAN_COMPLETED: + case WL_DISCONNECTED: + // TODO: what to do here? Do we need an extra connected state? + break; + } + + // TODO: Keep track of whether connection is in progress + // The status() function does not return a reply stating "connecting" + + /* if (_sta_connecting) { + return STA_connected_state::Connecting; + } + */ + return STA_connected_state::Idle; +} + + +} // namespace wifi +} // namespace net +} // namespace ESPEasy +# endif // if FEATURE_WIFI +#endif // ifdef ESP32 diff --git a/src/ESPEasy/net/wifi/ESPEasyWiFi_state_machine_ESP8266.cpp b/src/ESPEasy/net/wifi/ESPEasyWiFi_state_machine_ESP8266.cpp new file mode 100644 index 000000000..5715fefaa --- /dev/null +++ b/src/ESPEasy/net/wifi/ESPEasyWiFi_state_machine_ESP8266.cpp @@ -0,0 +1,58 @@ +#include "../wifi/ESPEasyWiFi_state_machine.h" + +#ifdef ESP8266 +# if FEATURE_WIFI + +# include "../wifi/ESPEasyWifi_abstracted.h" + +namespace ESPEasy { +namespace net { +namespace wifi { + +IPAddress ESPEasyWiFi_t::getIP() const +{ + IPAddress ip = WiFi.localIP(); + + if (ip.isSet()) { + return ip; + } +/* + ip = WiFi.softAPIP(); + + if (ip.isSet()) { + return ip; + } +*/ + return IPAddress(); +} + +STA_connected_state ESPEasyWiFi_t::getSTA_connected_state() const +{ + // Perform check on SDK function, see: https://github.com/esp8266/Arduino/issues/7432 + station_status_t status = wifi_station_get_connect_status(); + + switch (status) + { + case STATION_GOT_IP: + return STA_connected_state::Connected; + case STATION_NO_AP_FOUND: + return STA_connected_state::Error_Not_Found; + case STATION_CONNECT_FAIL: + case STATION_WRONG_PASSWORD: + return STA_connected_state::Error_Connect_Failed; + case STATION_CONNECTING: + return STA_connected_state::Connecting; + case STATION_IDLE: + break; + + default: + break; + } + return STA_connected_state::Idle; +} + +} // namespace wifi +} // namespace net +} // namespace ESPEasy +# endif // if FEATURE_WIFI +#endif // ifdef ESP8266 diff --git a/src/ESPEasy/net/wifi/ESPEasyWifi.cpp b/src/ESPEasy/net/wifi/ESPEasyWifi.cpp new file mode 100644 index 000000000..c9a2c8a04 --- /dev/null +++ b/src/ESPEasy/net/wifi/ESPEasyWifi.cpp @@ -0,0 +1,591 @@ +#include "../wifi/ESPEasyWifi.h" + +#if FEATURE_WIFI + +# include "../../../ESPEasy-Globals.h" +# include "../../../src/DataStructs/TimingStats.h" +# include "../../net/ESPEasyNetwork.h" +# include "../../../src/ESPEasyCore/ESPEasy_Log.h" +# include "../../../src/ESPEasyCore/Serial.h" +# include "../../../src/Globals/EventQueue.h" +# include "../../../src/Globals/Nodes.h" +# include "../../../src/Globals/RTC.h" +# include "../../../src/Globals/SecuritySettings.h" +# include "../../../src/Globals/Services.h" +# include "../../../src/Globals/Settings.h" +# include "../Globals/WiFi_AP_Candidates.h" +# include "../../../src/Helpers/ESPEasy_time_calc.h" +# include "../../../src/Helpers/ESPEasy_UnitOfMeasure.h" +# include "../../../src/Helpers/Hardware_defines.h" +# include "../../../src/Helpers/Misc.h" +# include "../../../src/Helpers/Networking.h" +# include "../../../src/Helpers/StringConverter.h" +# include "../../../src/Helpers/StringGenerator_WiFi.h" +# include "../../../src/Helpers/StringProvider.h" +# include "../Globals/ESPEasyWiFi.h" +# include "../Globals/ESPEasyWiFiEvent.h" +# include "../Globals/NetworkState.h" +# include "../wifi/ESPEasyWifi_abstracted.h" + + +# ifdef ESP32 +# include +# include // Needed to call ESP-IDF functions like esp_wifi_.... + +# ifndef ESP32P4 +# include +# else +# include +extern "C" { +# include "esp_hosted_transport_config.h" +} + +# include "port/esp/freertos/include/port_esp_hosted_host_config.h" +# endif // ifndef ESP32P4 + +# endif // ifdef ESP32 + + +namespace ESPEasy { +namespace net { +namespace wifi { + +// ******************************************************************************** +// WiFi state +// ******************************************************************************** + +/* + WiFi STA states: + 1 STA off => ESPEASY_WIFI_DISCONNECTED + 2 STA connecting + 3 STA connected => ESPEASY_WIFI_CONNECTED + 4 STA got IP => ESPEASY_WIFI_GOT_IP + 5 STA connected && got IP => ESPEASY_WIFI_SERVICES_INITIALIZED + + N.B. the states are flags, meaning both "connected" and "got IP" must be set + to be considered ESPEASY_WIFI_SERVICES_INITIALIZED + + The flag wifiConnectAttemptNeeded indicates whether a new connect attempt is needed. + This is set to true when: + - Security settings have been saved with AP mode enabled. FIXME TD-er, this may not be the best check. + - WiFi connect timeout reached & No client is connected to the AP mode of the node. + - Wifi is reset + - WiFi setup page has been loaded with SSID/pass values. + + + WiFi AP mode states: + 1 AP on => reset AP disable timer + 2 AP client connect/disconnect => reset AP disable timer + 3 AP off => AP disable timer = 0; + + AP mode will be disabled when both apply: + - AP disable timer (timerAPoff) expired + - No client is connected to the AP. + + AP mode will be enabled when at least one applies: + - No valid WiFi settings + - Start AP timer (timerAPstart) expired + + Start AP timer is set or cleared at: + - Set timerAPstart when "valid WiFi connection" state is observed. + - Disable timerAPstart when ESPEASY_WIFI_SERVICES_INITIALIZED wifi state is reached. + + For the first attempt to connect after a cold boot (RTC values are 0), a WiFi scan will be + performed to find the strongest known SSID. + This will set RTC.lastBSSID and RTC.lastWiFiChannel + + Quick reconnect (using BSSID/channel of last connection) when both apply: + - If wifi_connect_attempt < 3 + - RTC.lastBSSID is known + - RTC.lastWiFiChannel != 0 + + Change of wifi settings when both apply: + - "other" settings valid + - (wifi_connect_attempt % 2) == 0 + + Reset of wifi_connect_attempt to 0 when both apply: + - connection successful + - Connection stable (connected for > 5 minutes) + + */ + +// ******************************************************************************** +// Check WiFi connected status +// This is basically the state machine to switch between states: +// - Initiate WiFi reconnect +// - Start/stop of AP mode +// ******************************************************************************** +bool WiFiConnected() { + if (!Settings.getNetworkEnabled(NETWORK_INDEX_WIFI_STA)) { return false; } + static uint32_t lastCheckedTime = 0; + + const int32_t timePassed = timePassedSince(lastCheckedTime); + + if (lastCheckedTime != 0) { + if (timePassed < 10) { + // Rate limit time spent in WiFiConnected() to max. 100x per sec to process the rest of this function + return ESPEasyWiFi.connected(); + } + lastCheckedTime = millis(); + } + + ESPEasyWiFi.loop(); + return ESPEasyWiFi.connected(); +} + +// void WiFiConnectRelaxed() { AttemptWiFiConnect(); } + +// ******************************************************************************** +// Set Wifi config +// ******************************************************************************** +bool prepareWiFi() { + // ESPEasyWiFi.setup(); + + return true; +} + +bool checkAndResetWiFi() { return true; } + +void resetWiFi() { + /* //if (wifiAPmodeActivelyUsed()) return; + if (WiFiEventData.lastWiFiResetMoment.isSet() && !WiFiEventData.lastWiFiResetMoment.timeoutReached(1000)) { + // Don't reset WiFi too often + return; + } + FeedSW_watchdog(); + WiFiEventData.clearAll(); + WifiDisconnect(); + + // Send this log only after WifiDisconnect() or else sending to syslog may cause issues + addLog(LOG_LEVEL_INFO, F("Reset WiFi.")); + + // setWifiMode(WIFI_OFF); + + initWiFi(); + */ +} + +void initWiFi() { ESPEasyWiFi.setup(); } + +void exitWiFi() { ESPEasyWiFi.disable(); } + +void loopWiFi() { ESPEasyWiFi.loop(); } + +# ifdef BOARD_HAS_SDIO_ESP_HOSTED + +// ******************************************************************************** +// ESP-Hosted-MCU +// ******************************************************************************** +// Part of these ESP-Hosted-MCU related commands are original from Tasmota +// and parts are developed as a cooporation between ESPEasy and Tasmota. +uint32_t GetHostFwVersion() +{ + const uint32_t host_version = + (ESP_HOSTED_VERSION_MAJOR_1 << 16) | + (ESP_HOSTED_VERSION_MINOR_1 << 8) | + (ESP_HOSTED_VERSION_PATCH_1); + + return host_version; +} + +int32_t GetHostedMCUFwVersion() +{ + static int hosted_version = -1; + + if (!esp_hosted_is_config_valid()) { + return 0; + } + + if (-1 == hosted_version) { + hosted_version = 6; // v0.0.6 + esp_hosted_coprocessor_fwver_t ver_info; + esp_err_t err = esp_hosted_get_coprocessor_fwversion(&ver_info); // This takes almost 4 seconds on > 16; + const uint8_t minor1 = version >> 8; + const uint8_t patch1 = version; + + return strformat(F("%d.%d.%d"), major1, minor1, patch1); +} + +String GetHostedMCU() +{ + // Function is not yet implemented in Arduino Core so emulate it here + if (equals(F(CONFIG_ESP_HOSTED_IDF_SLAVE_TARGET), F("esp32c6"))) { + return String("ESP32-C6"); + } + return String("Unknown"); +} + +void HostedMCUStatus() +{ + // Execute after HostedMCU is init by WiFi.mode() + static bool once_shown = false; + + if (once_shown) { return; } + + if (esp_hosted_is_config_valid()) { + once_shown = true; + char config[128] = { 0 }; + struct esp_hosted_transport_config *pconfig; + + if (ESP_TRANSPORT_OK == esp_hosted_transport_get_config(&pconfig)) { + if (pconfig->transport_in_use == H_TRANSPORT_SDIO) { + struct esp_hosted_sdio_config *psdio_config; + + if (ESP_TRANSPORT_OK == esp_hosted_sdio_get_config(&psdio_config)) { + snprintf_P(config, + sizeof(config), + PSTR(" using GPIO%02d(CLK), GPIO%02d(CMD), GPIO%02d(D0), GPIO%02d(D1), GPIO%02d(D2), GPIO%02d(D3) and GPIO%02d(RST)"), + psdio_config->pin_clk.pin, + psdio_config->pin_cmd.pin, + psdio_config->pin_d0.pin, + psdio_config->pin_d1.pin, + psdio_config->pin_d2.pin, + psdio_config->pin_d3.pin, + psdio_config->pin_reset.pin); + } + } + } + addLog(LOG_LEVEL_INFO, strformat( + F("WiFi : ESP-Hosted-MCU %s v%s%s"), + GetHostedMCU().c_str(), + GetHostedFwVersion(EspHostTypes::ESP_HOSTED).c_str(), + config)); + } +} + +bool write_WiFi_Hosted_MCU_pins(KeyValueWriter*writer) +{ + if (!esp_hosted_is_config_valid()) { return false; } + struct esp_hosted_transport_config *pconfig; + + if (ESP_TRANSPORT_OK != esp_hosted_transport_get_config(&pconfig)) { return false; } + + switch (pconfig->transport_in_use) + { + case H_TRANSPORT_NONE: + case H_TRANSPORT_SPI_HD: + case H_TRANSPORT_SPI: + case H_TRANSPORT_UART: + break; + case H_TRANSPORT_SDIO: + { + struct esp_hosted_sdio_config *psdio_config; + + if (ESP_TRANSPORT_OK == esp_hosted_sdio_get_config(&psdio_config)) { + if (writer->summaryValueOnly()) { + KeyValueStruct kv(EMPTY_STRING); + + kv.appendValue(concat(F("CLK: "), psdio_config->pin_clk.pin)); + kv.appendValue(concat(F("CMD: "), psdio_config->pin_cmd.pin)); + kv.appendValue(concat(F("RST: "), psdio_config->pin_reset.pin)); + kv.appendValue(strformat( + F("D0..3: %d/%d/%d/%d"), + psdio_config->pin_d0.pin, + psdio_config->pin_d1.pin, + psdio_config->pin_d2.pin, + psdio_config->pin_d3.pin + )); + + writer->write(kv); + } else { + KeyValueStruct freq(F("SDIO Freq"), psdio_config->clock_freq_khz / 1000); + freq.setUnit(UOM_MHz); + writer->write(freq); + writer->write({ F("SDIO D0"), psdio_config->pin_d0.pin }); + writer->write({ F("SDIO D1"), psdio_config->pin_d1.pin }); + writer->write({ F("SDIO D2"), psdio_config->pin_d2.pin }); + writer->write({ F("SDIO D3"), psdio_config->pin_d3.pin }); + writer->write({ F("SDIO CLK"), psdio_config->pin_clk.pin }); + writer->write({ F("SDIO CMD"), psdio_config->pin_cmd.pin }); + writer->write({ F("SDIO RST"), psdio_config->pin_reset.pin }); + // Hide TX/RX queue size for now as it is unclear what these mean + //writer->write({ F("SDIO TX queue"), psdio_config->tx_queue_size }); + //writer->write({ F("SDIO RX queue"), psdio_config->rx_queue_size }); + } + return true; + } + break; + } + + } + return false; +} + +bool write_WiFi_Hosted_MCU_info(KeyValueWriter*writer) +{ + if (writer == nullptr) { return false; } + + if (writer->summaryValueOnly()) { + KeyValueStruct kv(EMPTY_STRING); + kv.appendValue(strformat(F("%s @ %s"), GetHostedMCU().c_str(), GetHostedFwVersion(EspHostTypes::ESP_HOSTED).c_str())); + kv.appendValue(concat(F("MAC: "), WiFi.macAddress())); + writer->write(kv); + } else { + writer->write({ F("ESP-Host Fw Version"), GetHostedFwVersion(EspHostTypes::ESP_HOST) }); + writer->write({ F("ESP-Hosted-MCU Fw Version"), GetHostedFwVersion(EspHostTypes::ESP_HOSTED) }); + writer->write({ F("ESP-Hosted-MCU Chip"), GetHostedMCU() }); + writer->write({ + F("MAC"), + WiFi.macAddress(), + KeyValueStruct::Format::PreFormatted }); + + } + return true; +} + +# endif // ifdef BOARD_HAS_SDIO_ESP_HOSTED + + +// ******************************************************************************** +// Configure WiFi TX power +// ******************************************************************************** +# if FEATURE_SET_WIFI_TX_PWR + +void SetWiFiTXpower() { + SetWiFiTXpower(0); // Just some minimal value, will be adjusted in SetWiFiTXpower +} + +void SetWiFiTXpower(float dBm) { doSetWiFiTXpower(dBm, WiFi.RSSI()); } + +void SetWiFiTXpower(float dBm, float rssi) { doSetWiFiTXpower(dBm, rssi); } + +float GetWiFiTXpower() { return doGetWiFiTXpower(); } + +# endif // if FEATURE_SET_WIFI_TX_PWR + +float GetRSSIthreshold(float& maxTXpwr) { + maxTXpwr = Settings.getWiFi_TX_power(); + return doGetRSSIthreshold(maxTXpwr); +} + +int GetRSSI_quality() { + long rssi = WiFi.RSSI(); + + if (-50 < rssi) { return 10; } + + if (rssi <= -98) { return 0; } + rssi = rssi + 97; // Range 0..47 => 1..9 + return (rssi / 5) + 1; +} + +WiFiConnectionProtocol getConnectionProtocol() { return doGetConnectionProtocol(); } + +# ifdef ESP32 + +int64_t WiFi_get_TSF_time() { + # ifndef SOC_WIFI_SUPPORTED + return 0; + # else + return esp_wifi_get_tsf_time(WIFI_IF_STA); + # endif // ifndef SOC_WIFI_SUPPORTED +} + +# endif // ifdef ESP32 + +// ******************************************************************************** +// Disconnect from Wifi AP +// ******************************************************************************** +void WifiDisconnect() { ESPEasyWiFi.disconnect(); } + +// ******************************************************************************** +// Scan WiFi network +// ******************************************************************************** +bool WiFiScanAllowed() { return doWiFiScanAllowed(); } + +// ******************************************************************************** +// Scan all Wifi Access Points +// ******************************************************************************** +void WiFiScan_log_to_serial() +{ + // Direct Serial is allowed here, since this function will only be called from serial input. + serialPrintln(F("WIFI : SSID Scan start")); + + if (WiFi_AP_Candidates.scanComplete() <= 0) { + WiFiMode_t cur_wifimode = WiFi.getMode(); + WifiScan(false); + WiFi_AP_Candidates.process_WiFiscan(); + setWifiMode(cur_wifimode); + } + + const int8_t scanCompleteStatus = WiFi_AP_Candidates.scanComplete(); + + if (scanCompleteStatus <= 0) { + serialPrintln(concat(F("WIFI : No networks found. Status: "), scanCompleteStatus)); + } + else + { + serialPrint(F("WIFI : ")); + serialPrint(String(scanCompleteStatus)); + serialPrintln(F(" networks found")); + + int i = 0; + + for (auto it = WiFi_AP_Candidates.scanned_begin(); it != WiFi_AP_Candidates.scanned_end(); ++it) + { + ++i; + + // Print SSID and RSSI for each network found + serialPrint(F("WIFI : ")); + serialPrint(String(i)); + serialPrint(": "); + serialPrintln(it->toString()); + delay(10); + } + } + serialPrintln(""); +} + +// Only internal scope +void setAPinternal(bool enable) { doSetAPinternal(enable); } + +void setUseStaticIP(bool enabled) { doSetUseStaticIP(enabled); } + +bool WiFiUseStaticIP() { return Settings.IP[0] != 0 && Settings.IP[0] != 255; } + +bool wifiAPmodeActivelyUsed() +{ + if (!WifiIsAP(WiFi.getMode()) // || (!WiFiEventData.timerAPoff.isSet()) + ) { + // AP not active or soon to be disabled in processDisableAPmode() + return false; + } + return SOFTAP_STATION_COUNT != 0; + + // FIXME TD-er: is effectively checking for AP active enough or must really check for connected clients to prevent automatic wifi + // reconnect? +} + +void setupStaticIPconfig() { + setUseStaticIP(WiFiUseStaticIP()); + + if (!WiFiUseStaticIP()) { return; } + const IPAddress ip(Settings.IP); + const IPAddress gw(Settings.Gateway); + const IPAddress subnet(Settings.Subnet); + const IPAddress dns(Settings.DNS); + + // WiFiEventData.dns0_cache = dns; + + WiFi.config(ip, gw, subnet, dns); +# ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, strformat( + F("IP : Static IP : %s GW: %s SN: %s DNS: %s"), + formatIP(ip).c_str(), + formatIP(gw).c_str(), + formatIP(subnet).c_str(), + getValue(LabelType::DNS).c_str())); + } +# endif // ifndef BUILD_NO_DEBUG +} + +// ******************************************************************************** +// Formatting WiFi related strings +// ******************************************************************************** +String formatScanResult(int i, const String& separator) { + int32_t rssi = 0; + + return formatScanResult(i, separator, rssi); +} + +String formatScanResult(int i, const String& separator, int32_t& rssi) { + WiFi_AP_Candidate tmp(i); + + rssi = tmp.rssi; + return tmp.toString(separator); +} + +void logConnectionStatus() { + static unsigned long lastLog = 0; + + if ((lastLog != 0) && (timePassedSince(lastLog) < 1000)) { + return; + } + lastLog = millis(); +# ifndef BUILD_NO_DEBUG + # ifdef ESP8266 + const uint8_t arduino_corelib_wifistatus = WiFi.status(); + const uint8_t sdk_wifistatus = wifi_station_get_connect_status(); + + if ((arduino_corelib_wifistatus == WL_CONNECTED) != (sdk_wifistatus == STATION_GOT_IP)) { + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + String log = F("WiFi : SDK station status differs from Arduino status. SDK-status: "); + log += SDKwifiStatusToString(sdk_wifistatus); + log += F(" Arduino status: "); + log += ArduinoWifiStatusToString(arduino_corelib_wifistatus); + addLogMove(LOG_LEVEL_ERROR, log); + } + } + # endif // ifdef ESP8266 + + /* + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, strformat( + F("WIFI : Arduino wifi status: %s ESPeasy internal wifi status: %s"), + ArduinoWifiStatusToString(WiFi.status()).c_str(), + WiFiEventData.ESPeasyWifiStatusToString().c_str())); + } + */ +# endif // ifndef BUILD_NO_DEBUG +} + +bool WifiIsAP(WiFiMode_t wifimode) { return doWifiIsAP(wifimode); } + +bool WifiIsSTA(WiFiMode_t wifimode) { return doWifiIsSTA(wifimode); } + +const __FlashStringHelper* getWifiModeString(WiFiMode_t wifimode) { return doGetWifiModeString(wifimode); } + +# if CONFIG_SOC_WIFI_SUPPORT_5G + +const __FlashStringHelper* getWifiBandModeString(wifi_band_mode_t wifiBandMode) { return doGetWifiBandModeString(wifiBandMode); } + +# endif // if CONFIG_SOC_WIFI_SUPPORT_5G + +bool setSTA(bool enable) { return doSetSTA(enable); } + +bool setAP(bool enable) { return doSetAP(enable); } + +bool setSTA_AP(bool sta_enable, + bool ap_enable) { return doSetSTA_AP(sta_enable, ap_enable); } + +bool setWifiMode(WiFiMode_t new_mode) { + const bool res = doSetWifiMode(new_mode); + +# ifdef ESP32P4 + + if (new_mode != WIFI_OFF) { + ESPEasy::net::wifi::HostedMCUStatus(); + } +# endif // ifdef ESP32P4 + return res; +} + +void WifiScan(bool async, + uint8_t channel) { doWifiScan(async, channel); } + + +} // namespace wifi +} // namespace net +} // namespace ESPEasy + +#endif // if FEATURE_WIFI diff --git a/src/ESPEasy/net/wifi/ESPEasyWifi.h b/src/ESPEasy/net/wifi/ESPEasyWifi.h new file mode 100644 index 000000000..def0dfb99 --- /dev/null +++ b/src/ESPEasy/net/wifi/ESPEasyWifi.h @@ -0,0 +1,146 @@ +#pragma once + +#include "../../../ESPEasy_common.h" + +#if FEATURE_WIFI + +# if defined(ESP8266) + # include +# endif // if defined(ESP8266) +# if defined(ESP32) + # include +# endif // if defined(ESP32) + +# include "../wifi/WiFiConnectionProtocol.h" +# include "../DataStructs/WiFi_AP_Candidate.h" + +# include "../../../src/Helpers/LongTermTimer.h" +# include "../../../src/Helpers/KeyValueWriter.h" + +# ifdef ESP32 +# define SOFTAP_STATION_COUNT WiFi.AP.stationCount() +# endif +# ifdef ESP8266 +# define SOFTAP_STATION_COUNT WiFi.softAPgetStationNum() +# endif + + +namespace ESPEasy { +namespace net { +namespace wifi { + +# define WIFI_RECONNECT_WAIT 30000 // in milliSeconds +# define WIFI_AP_OFF_TIMER_DURATION 300000 // in milliSeconds +# if FEATURE_CUSTOM_PROVISIONING +# define WIFI_CONNECTION_CONSIDERED_STABLE 60000 // in milliSeconds +# else +# define WIFI_CONNECTION_CONSIDERED_STABLE 60000 // in milliSeconds +# endif // if FEATURE_CUSTOM_PROVISIONING +# define WIFI_ALLOW_AP_AFTERBOOT_PERIOD 5 // in minutes +# define WIFI_SCAN_INTERVAL_AP_USED 125000 // in milliSeconds +# define WIFI_SCAN_INTERVAL_MINIMAL 60000 // in milliSeconds + + +bool WiFiConnected(); + +// void WiFiConnectRelaxed(); +bool prepareWiFi(); +bool checkAndResetWiFi(); +void resetWiFi(); +void initWiFi(); +void exitWiFi(); +void loopWiFi(); + +# ifdef BOARD_HAS_SDIO_ESP_HOSTED + +// ******************************************************************************** +// ESP-Hosted-MCU +// ******************************************************************************** +// Part of these ESP-Hosted-MCU related commands are original from Tasmota +// and parts are developed as a cooporation between ESPEasy and Tasmota. + +enum class EspHostTypes { + ESP_HOST, + ESP_HOSTED + +}; + +uint32_t GetHostFwVersion(); +int32_t GetHostedMCUFwVersion(); +String GetHostedFwVersion(EspHostTypes hostType); +String GetHostedMCU(); +void HostedMCUStatus(); + +bool write_WiFi_Hosted_MCU_pins(KeyValueWriter*writer); +bool write_WiFi_Hosted_MCU_info(KeyValueWriter*writer); + +# endif + +# if FEATURE_SET_WIFI_TX_PWR +void SetWiFiTXpower(); +void SetWiFiTXpower(float dBm); // 0-20.5 +void SetWiFiTXpower(float dBm, + float rssi); +float GetWiFiTXpower(); +# endif // if FEATURE_SET_WIFI_TX_PWR +float GetRSSIthreshold(float& maxTXpwr); + +// Return some quality based on RSSI. +// <-97 => 0 , >-50 => 10 +// -97 ... -50 => 1 ... 9 +int GetRSSI_quality(); +WiFiConnectionProtocol getConnectionProtocol(); +# ifdef ESP32 + +// TSF time is 64-bit timer in usec, sent by the AP along with other packets. +// On tested access points, this seems to be the uptime in usec. +// Could be used among nodes connected to the same AP to increase time sync accuracy. +int64_t WiFi_get_TSF_time(); +# endif // ifdef ESP32 +void WifiDisconnect(); +bool WiFiScanAllowed(); +void WiFiScan_log_to_serial(); + +void setAPinternal(bool enable); // FIXME TD-er: Move to ESPEasyWifi_abstracted... + +void setUseStaticIP(bool enabled); +bool WiFiUseStaticIP(); +bool wifiAPmodeActivelyUsed(); +void setupStaticIPconfig(); +String formatScanResult(int i, + const String& separator); +String formatScanResult(int i, + const String& separator, + int32_t & rssi); + +void logConnectionStatus(); + + +// ******************************************************************************** +// Manage Wifi Modes +// ******************************************************************************** +bool WifiIsAP(WiFiMode_t wifimode); +bool WifiIsSTA(WiFiMode_t wifimode); + +const __FlashStringHelper* getWifiModeString(WiFiMode_t wifimode); + +# if CONFIG_SOC_WIFI_SUPPORT_5G +const __FlashStringHelper* getWifiBandModeString(wifi_band_mode_t wifiBandMode); +# endif + +bool setSTA(bool enable); +bool setAP(bool enable); +bool setSTA_AP(bool sta_enable, + bool ap_enable); + +bool setWifiMode(WiFiMode_t new_mode); + + +void WifiScan(bool async, + uint8_t channel = 0); + + +} // namespace wifi +} // namespace net +} // namespace ESPEasy +#endif // if FEATURE_WIFI diff --git a/src/ESPEasy/net/wifi/ESPEasyWifi_abstracted.cpp b/src/ESPEasy/net/wifi/ESPEasyWifi_abstracted.cpp new file mode 100644 index 000000000..4a0c4069a --- /dev/null +++ b/src/ESPEasy/net/wifi/ESPEasyWifi_abstracted.cpp @@ -0,0 +1,311 @@ +#include "../wifi/ESPEasyWifi_abstracted.h" + +#if FEATURE_WIFI + +# include "../../net/ESPEasyNetwork.h" +# include "../../../src/Globals/EventQueue.h" +# include "../../../src/Globals/SecuritySettings.h" +# include "../../../src/Globals/Services.h" +# include "../../../src/Globals/Settings.h" +# include "../Globals/WiFi_AP_Candidates.h" +# include "../../../src/Helpers/StringConverter.h" +# include "../Globals/ESPEasyWiFiEvent.h" +# include "../Globals/ESPEasyWiFi.h" +# include "../Globals/NetworkState.h" + +namespace ESPEasy { +namespace net { +namespace wifi { + +const __FlashStringHelper* doGetWifiModeString(WiFiMode_t wifimode) +{ + switch (wifimode) + { + case WIFI_OFF: return F("OFF"); + case WIFI_STA: return F("STA"); + case WIFI_AP: return F("AP"); + case WIFI_AP_STA: return F("AP+STA"); + default: + break; + } + return F("Unknown"); +} + +# if CONFIG_SOC_WIFI_SUPPORT_5G + +const __FlashStringHelper* doGetWifiBandModeString(wifi_band_mode_t wifiBandMode) +{ + switch (wifiBandMode) + { + case WIFI_BAND_MODE_2G_ONLY: return F("2.4 GHz only"); + case WIFI_BAND_MODE_5G_ONLY: return F("5 GHz only"); + default: break; + } + return F("2.4 GHz + 5 GHz"); +} + +# endif // if CONFIG_SOC_WIFI_SUPPORT_5G + +bool doSetSTA(bool enable) { return doSetSTA_AP(enable, doWifiIsAP(WiFi.getMode())); } + +bool doSetAP(bool enable) { return doSetSTA_AP(doWifiIsSTA(WiFi.getMode()), enable); } + +bool doSetSTA_AP(bool sta_enable, bool ap_enable) +{ +# ifndef SOC_WIFI_SUPPORTED + return true; +# else + + if (ap_enable) { + return doSetWifiMode(sta_enable ? WIFI_AP_STA : WIFI_AP); + } + return doSetWifiMode(sta_enable ? WIFI_STA : WIFI_OFF); +# endif // ifndef SOC_WIFI_SUPPORTED +} + +// ******************************************************************************** +// Scan WiFi network +// ******************************************************************************** +bool doWiFiScanAllowed() { + if (WiFi_AP_Candidates.scanComplete() == WIFI_SCAN_RUNNING) { + return false; + } + return true; // WiFiEventData.processedConnect; +} + +// Only internal scope +void doSetAPinternal(bool enable) +{ + if (enable) { + if (!Settings.getNetworkEnabled(NETWORK_INDEX_WIFI_AP)) { return; } + + // create and store unique AP SSID/PW to prevent ESP from starting AP mode with default SSID and No password! + // setup ssid for AP Mode when needed + String softAPSSID = NetworkCreateRFCCompliantHostname(); + String pwd = SecuritySettings.WifiAPKey; + IPAddress subnet(DEFAULT_AP_SUBNET); + # ifdef ESP32 + IPAddress dhcp_lease_start = (uint32_t)0; + + IPAddress dns(DEFAULT_AP_DNS); + + if (Settings.ApCaptivePortal()) { + dns = apIP; + } else { + if (WiFi.STA.dnsIP()) { + dns = WiFi.STA.dnsIP(); + } + } + + if (!WiFi.softAPConfig(apIP, apIP, subnet, dhcp_lease_start, dns)) { + addLog(LOG_LEVEL_ERROR, strformat( + ("WIFI : [AP] softAPConfig failed! IP: %s, GW: %s, SN: %s DNS: %s"), + apIP.toString().c_str(), + apIP.toString().c_str(), + subnet.toString().c_str(), + dns.toString().c_str()) + ); + } + WiFi.AP.bandwidth(WIFI_BW_HT20); + # endif // ifdef ESP32 + # ifdef ESP8266 + + if (!WiFi.softAPConfig(apIP, apIP, subnet)) { +# ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_ERROR, strformat( + ("WIFI : [AP] softAPConfig failed! IP: %s, GW: %s, SN: %s"), + apIP.toString().c_str(), + apIP.toString().c_str(), + subnet.toString().c_str()) + ); +#endif + } + # endif // ifdef ESP8266 + + int channel = 1; + + if (Settings.WiFiAP_channel) { + channel = Settings.WiFiAP_channel; + } + + if (WifiIsSTA(WiFi.getMode()) && WiFiConnected()) { + channel = WiFi.channel(); + } + + doSetAP(true); + + if (WiFi.softAP(softAPSSID.c_str(), pwd.c_str(), channel)) { + auto data = getWiFi_AP_NWPluginData_static_runtime(); + + if (data) { data->mark_start(); } +#ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, strformat( + F("WIFI : AP Mode enabled. SSID: %s IP: %s ch: %d"), + softAPSSID.c_str(), + formatIP(WiFi.softAPIP()).c_str(), + channel)); + } + } else { + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLogMove(LOG_LEVEL_ERROR, strformat( + F("WIFI : Error while starting AP Mode with SSID: %s IP: %s"), + softAPSSID.c_str(), + formatIP(apIP).c_str())); + } +#endif + } + + if (Settings.ApCaptivePortal()) { +# ifdef ESP32 +# if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 2) + + if (WiFi.AP.enableDhcpCaptivePortal()) { + addLog(LOG_LEVEL_INFO, F("WIFI : AP Captive Portal enabled")); + } + else { + addLog(LOG_LEVEL_ERROR, F("WIFI : Failed to enable AP Captive Portal")); + } +# endif // if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 2) + +# if FEATURE_DNS_SERVER + + if (!dnsServerActive) { + dnsServerActive = true; + dnsServer.start(); + } +# endif // if FEATURE_DNS_SERVER +# else // ifdef ESP32 +# if FEATURE_DNS_SERVER + + if (!dnsServerActive) { + dnsServerActive = true; + dnsServer.start(DNS_PORT, "*", WiFi.softAPIP()); + } +# endif // if FEATURE_DNS_SERVER +# endif // ifdef ESP32 + } + } else { + # if FEATURE_DNS_SERVER + + if (dnsServerActive) { + dnsServerActive = false; + dnsServer.stop(); + } + # endif // if FEATURE_DNS_SERVER + auto data = getWiFi_AP_NWPluginData_static_runtime(); + + if (data) { data->mark_stop(); } + + doSetAP(false); + } +} + +void doSetConnectionSpeed() { + doSetConnectionSpeed( + Settings.ForceWiFi_bg_mode() +# if CONFIG_SOC_WIFI_SUPPORT_5G + , Settings.WiFi_band_mode() +# endif + ); +} + +// ******************************************************************************** +// Configure WiFi TX power +// ******************************************************************************** +# if FEATURE_SET_WIFI_TX_PWR + +void doSetWiFiTXpower() { + doSetWiFiTXpower(0, WiFi.RSSI()); + + // Just some minimal value, will be adjusted in doSetWiFiTXpower +} + +void doSetWiFiTXpower(float dBm, float rssi) { + const WiFiMode_t cur_mode = WiFi.getMode(); + + if (cur_mode == WIFI_OFF) { + return; + } + + if (Settings.UseMaxTXpowerForSending()) { + dBm = 30; // Just some max, will be limited later + } + + // Range ESP32 : -1dBm - 20dBm + // Range ESP8266: 0dBm - 20.5dBm + float maxTXpwr; + float threshold = GetRSSIthreshold(maxTXpwr); + # ifdef ESP8266 + float minTXpwr{}; + # endif + # ifdef ESP32 + float minTXpwr = -1.0f; + # endif + + threshold += Settings.WiFi_sensitivity_margin; // Margin in dBm on top of threshold + + // Assume AP sends with max set by ETSI standard. + // 2.4 GHz: 100 mWatt (20 dBm) + // US and some other countries allow 1000 mW (30 dBm) + // We cannot send with over 20 dBm, thus it makes no sense to force higher TX power all the time. + const float newrssi = rssi - 20; + + if (newrssi < threshold) { + minTXpwr = threshold - newrssi; + } + + if (minTXpwr > maxTXpwr) { + minTXpwr = maxTXpwr; + } + + if (dBm > maxTXpwr) { + dBm = maxTXpwr; + } else if (dBm < minTXpwr) { + dBm = minTXpwr; + } + + doSetWiFiTXpower(dBm); + + // if (WiFiEventData.wifi_TX_pwr < dBm) { + // Will increase the TX power, give power supply of the unit some rest + // delay(1); + // } + + // WiFiEventData.wifi_TX_pwr = dBm; + + delay(0); + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + const int TX_pwr_int = dBm * 4; + const int maxTXpwr_int = maxTXpwr * 4; + + if (TX_pwr_int != maxTXpwr_int) { + static int last_log = -1; + + if (TX_pwr_int != last_log) { + last_log = TX_pwr_int; + String log = strformat( + F("WiFi : Set TX power to %ddBm sensitivity: %ddBm"), + static_cast(dBm), + static_cast(threshold)); + + if (rssi < 0) { + log += strformat(F(" RSSI: %ddBm"), static_cast(rssi)); + } + addLogMove(LOG_LEVEL_DEBUG, log); + } + } + } + # endif // ifndef BUILD_NO_DEBUG +} + +# endif // if FEATURE_SET_WIFI_TX_PWR + +} // namespace wifi +} // namespace net +} // namespace ESPEasy + +#endif // if FEATURE_WIFI diff --git a/src/ESPEasy/net/wifi/ESPEasyWifi_abstracted.h b/src/ESPEasy/net/wifi/ESPEasyWifi_abstracted.h new file mode 100644 index 000000000..d5abe21e2 --- /dev/null +++ b/src/ESPEasy/net/wifi/ESPEasyWifi_abstracted.h @@ -0,0 +1,114 @@ +#pragma once + +#include "../../../ESPEasy_common.h" + +#if FEATURE_WIFI + +# include "../wifi/WiFiConnectionProtocol.h" +# include "../wifi/WiFi_STA_connected_state.h" +# include "../wifi/WiFi_State.h" + + +# if defined(ESP8266) +# include "../wifi/ESPEasyWiFi_STA_Event_ESP8266.h" +# include +# endif // if defined(ESP8266) +# if defined(ESP32) +# include "../wifi/ESPEasyWiFi_STA_Event_ESP32.h" +# include +# include +# endif // if defined(ESP32) + +namespace ESPEasy { +namespace net { +namespace wifi { + + +// Call before starting WiFi +bool WiFi_pre_setup(); + +// Call before setting WiFi into STA mode +bool WiFi_pre_STA_setup(); + + +void doWiFiDisconnect(); + +// ******************************************************************************** +// Manage Wifi Modes +// ******************************************************************************** +bool doWifiIsAP(WiFiMode_t wifimode); +bool doWifiIsSTA(WiFiMode_t wifimode); + +const __FlashStringHelper* doGetWifiModeString(WiFiMode_t wifimode); + +# if CONFIG_SOC_WIFI_SUPPORT_5G +const __FlashStringHelper* doGetWifiBandModeString(wifi_band_mode_t wifiBandMode); +# endif + +bool doSetSTA(bool enable); +bool doSetAP(bool enable); +bool doSetSTA_AP(bool sta_enable, + bool ap_enable); + +bool doSetWifiMode(WiFiMode_t new_mode); + + +void doWifiScan(bool async, + uint8_t channel = 0); + + +bool doWiFiScanAllowed(); + +// Only internal scope +void doSetAPinternal(bool enable); + +// ******************************************************************************** +// Event handlers +// ******************************************************************************** + +WiFiConnectionProtocol doGetConnectionProtocol(); +float doGetRSSIthreshold(float& maxTXpwr); + +// ******************************************************************************** +// Configure WiFi TX power +// ******************************************************************************** + +# if FEATURE_SET_WIFI_TX_PWR + +// Actually set the TX power using platform specific calls. +void doSetWiFiTXpower(float& dBm); + + +void doSetWiFiTXpower(); + +void doSetWiFiTXpower(float dBm, + float rssi); + +float doGetWiFiTXpower(); + +# endif // if FEATURE_SET_WIFI_TX_PWR + + +void doSetConnectionSpeed(); +# if CONFIG_SOC_WIFI_SUPPORT_5G +void doSetConnectionSpeed(bool ForceWiFi_bg_mode, + wifi_band_mode_t WiFi_band_mode); +# else // if CONFIG_SOC_WIFI_SUPPORT_5G +void doSetConnectionSpeed(bool ForceWiFi_bg_mode); +# endif // if CONFIG_SOC_WIFI_SUPPORT_5G + +void doSetWiFiNoneSleep(); +void doSetWiFiEcoPowerMode(); +void doSetWiFiDefaultPowerMode(); + +void doSetWiFiCountryPolicyManual(); + + +void doSetUseStaticIP(bool enabled); + +} // namespace wifi +} // namespace net +} // namespace ESPEasy + + +#endif // if FEATURE_WIFI diff --git a/src/ESPEasy/net/wifi/ESPEasyWifi_abstracted_ESP32.cpp b/src/ESPEasy/net/wifi/ESPEasyWifi_abstracted_ESP32.cpp new file mode 100644 index 000000000..c6ad5ef33 --- /dev/null +++ b/src/ESPEasy/net/wifi/ESPEasyWifi_abstracted_ESP32.cpp @@ -0,0 +1,625 @@ +#include "../wifi/ESPEasyWifi_abstracted.h" + +#if FEATURE_WIFI +# ifdef ESP32 + +# include "../../../src/DataStructs/TimingStats.h" +# include "../../../src/Globals/EventQueue.h" +# include "../../../src/Globals/Services.h" +# include "../../../src/Globals/Settings.h" +# include "../Globals/WiFi_AP_Candidates.h" +# include "../../../src/Helpers/StringConverter.h" + +# include "../../net/ESPEasyNetwork.h" // Needed for NetworkCreateRFCCompliantHostname, WiFi code should not include network code +# include "../Globals/ESPEasyWiFiEvent.h" +# include "../wifi/ESPEasyWiFi_STA_Event_ESP32.h" + +# include +# include // Needed to call ESP-IDF functions like esp_wifi_.... + +# ifndef ESP32P4 +# include +# endif + + +namespace ESPEasy { +namespace net { +namespace wifi { + +bool WiFi_pre_setup() { + if (!ESPEasyWiFi_STA_EventHandler::initialized()) { return false; } + + // registerWiFiEventHandler(); + WiFi.persistent(false); + + return doSetSTA_AP(false, false); +} + +bool WiFi_pre_STA_setup() +{ + if (!doSetSTA(true)) { return false; } + + WiFi.setAutoReconnect(false); + delay(10); + return true; +} + +void doWiFiDisconnect() { + uint8_t retry = 3; + while (!WiFi.disconnect(Settings.WiFiRestart_connection_lost()) && retry) { + --retry; + delay(100); + } +/* + { + const IPAddress ip; + const IPAddress gw; + const IPAddress subnet; + const IPAddress dns; + WiFi.config(ip, gw, subnet, dns); + } +*/ +} + +bool doWifiIsAP(WiFiMode_t wifimode) { return (wifimode == WIFI_MODE_AP) || (wifimode == WIFI_MODE_APSTA); } + +bool doWifiIsSTA(WiFiMode_t wifimode) { return (wifimode == WIFI_MODE_STA) || (wifimode == WIFI_MODE_APSTA); } + +bool doSetWifiMode(WiFiMode_t new_mode) +{ + if (!Settings.getNetworkEnabled(NETWORK_INDEX_WIFI_AP)) { + if (new_mode == WIFI_MODE_AP) new_mode = WIFI_OFF; + if (new_mode == WIFI_MODE_APSTA) new_mode = WIFI_MODE_STA; + } + if (!Settings.getNetworkEnabled(NETWORK_INDEX_WIFI_STA)) { + if (new_mode == WIFI_MODE_STA) new_mode = WIFI_OFF; + if (new_mode == WIFI_MODE_APSTA) new_mode = WIFI_MODE_AP; + } + + const WiFiMode_t cur_mode = WiFi.getMode(); + + // Made this static flag an int as ESP8266 and ESP32 differ in the "not set" values + static int8_t processing_wifi_mode = -1; + + if (cur_mode == new_mode) { + if (cur_mode != WIFI_OFF) { + // registerWiFiEventHandler(); + } + return true; + } + + if (processing_wifi_mode == static_cast(new_mode)) { + // Prevent loops + return true; + } + processing_wifi_mode = static_cast(new_mode); + + + if (cur_mode == WIFI_OFF) { + // Needs to be set while WiFi is off + WiFi.hostname(NetworkCreateRFCCompliantHostname()); + + // WiFiEventData.markWiFiTurnOn(); + } + + if (new_mode != WIFI_OFF) { + # ifdef ESP8266 + + // See: https://github.com/esp8266/Arduino/issues/6172#issuecomment-500457407 + WiFi.forceSleepWake(); // Make sure WiFi is really active. + # endif // ifdef ESP8266 + delay(100); + } else { + doWiFiDisconnect(); + + // delay(100); + //processDisconnect(); + + // WiFiEventData.clear_processed_flags(); + } + + addLog(LOG_LEVEL_INFO, concat(F("WIFI : Set WiFi to "), doGetWifiModeString(new_mode))); + + # if FEATURE_DNS_SERVER + if (!doWifiIsAP(new_mode)) { + if (dnsServerActive) { + dnsServerActive = false; + dnsServer.stop(); + } + } + #endif + + int retry = 2; + + while (!WiFi.mode(new_mode) && retry > 0) { + delay(100); + --retry; + } + retry = 2; + + while (WiFi.getMode() != new_mode && retry > 0) { + addLog(LOG_LEVEL_INFO, F("WIFI : mode not yet set")); + delay(100); + --retry; + } + + if (WiFi.getMode() != new_mode) { + WiFi.mode(WIFI_OFF); + delay(100); + addLog(LOG_LEVEL_ERROR, F("WIFI : Cannot set mode!!!!!")); + return false; + } + + + if (new_mode == WIFI_OFF) { + + // FIXME TD-er: Is this correct to mark Turn ON ???? + // WiFiEventData.markWiFiTurnOn(); + + // Needs to be set while WiFi is off + WiFi.hostname(NetworkCreateRFCCompliantHostname()); + delay(100); +# ifndef ESP32P4 + esp_wifi_set_ps(WIFI_PS_NONE); +# endif + + // esp_wifi_set_ps(WIFI_PS_MAX_MODEM); + delay(1); + # ifdef ESP32 + WiFi.STA.end(); + # endif + } else { + + /* + if (cur_mode == WIFI_OFF) { + registerWiFiEventHandler(); + } + */ + + // Only set power mode when AP is not enabled + // When AP is enabled, the sleep mode is already set to WIFI_NONE_SLEEP + if (!doWifiIsAP(new_mode)) { + if (Settings.WifiNoneSleep()) { + doSetWiFiNoneSleep(); + } else if (Settings.EcoPowerMode()) { + doSetWiFiEcoPowerMode(); + } else { + // Default + doSetWiFiDefaultPowerMode(); + } + } +# if FEATURE_SET_WIFI_TX_PWR + doSetWiFiTXpower(); +# endif + + if (doWifiIsSTA(new_mode)) { + // WiFi.setAutoConnect(Settings.SDK_WiFi_autoreconnect()); + WiFi.setAutoReconnect(Settings.SDK_WiFi_autoreconnect()); + } + delay(100); // Must allow for some time to init. + } + const bool new_mode_AP_enabled = doWifiIsAP(new_mode); + + if (doWifiIsAP(cur_mode) && !new_mode_AP_enabled) { + eventQueue.add(F("WiFi#APmodeDisabled")); + } + + if (doWifiIsAP(cur_mode) != new_mode_AP_enabled) { + // Mode has changed + doSetAPinternal(new_mode_AP_enabled); + } + # if FEATURE_MDNS + # ifdef ESP8266 + + // notifyAPChange() is not present in the ESP32 MDNSResponder + MDNS.notifyAPChange(); + # endif // ifdef ESP8266 + # endif // if FEATURE_MDNS + return true; +} + +void doWifiScan(bool async, uint8_t channel) { + doSetSTA(true); + + if (!doWiFiScanAllowed()) { + return; + } + +# if CONFIG_SOC_WIFI_SUPPORT_5G + const wifi_band_mode_t current_wifi_band_mode = WiFi.getBandMode(); + WiFi.setBandMode(Settings.WiFi_band_mode()); +# endif // if CONFIG_SOC_WIFI_SUPPORT_5G + + // TD-er: Don't run async scan on ESP32. + // Since IDF 4.4 it seems like the active channel may be messed up when running async scan + // Perform a disconnect after scanning. + // See: https://github.com/letscontrolit/ESPEasy/pull/3579#issuecomment-967021347 + // async = false; + + if (Settings.IncludeHiddenSSID()) { + doSetWiFiCountryPolicyManual(); + } + + START_TIMER; +// WiFiEventData.lastScanMoment.setNow(); + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + if (channel == 0) { + addLog(LOG_LEVEL_INFO, F("WiFi : Start network scan all channels")); + } else { + addLogMove(LOG_LEVEL_INFO, strformat(F("WiFi : Start network scan ch: %d "), channel)); + } + } + # endif // ifndef BUILD_NO_DEBUG + bool show_hidden = true; +// WiFiEventData.lastGetScanMoment.setNow(); +// WiFiEventData.lastScanChannel = channel; + + unsigned int nrScans = 1 + (async ? 0 : Settings.ConnectFailRetryCount); + + while (nrScans > 0) { + WiFi_AP_Candidates.begin_scan(); + if (!async) { + FeedSW_watchdog(); + } + --nrScans; + + const bool passive = Settings.PassiveWiFiScan(); + const uint32_t max_ms_per_chan = 120; +# if CONFIG_SOC_WIFI_SUPPORT_5G + + // ESP32-C5 scans both 2.4 and 5 GHz band, which takes much longer + // 14 channels on 2.4 GHz + // 28 channels on 5 GHz: 36~177 (36, 40, 44 ... 177) + // Typically a scan takes 40 ... 60 msec per channel longer. + // To be safe, set the timeout to 2x max_ms_per_chan + + WiFi.setScanTimeout((14 + 28) * max_ms_per_chan * 2); +# else // if CONFIG_SOC_WIFI_SUPPORT_5G + WiFi.setScanTimeout(14 * max_ms_per_chan * 2); +# endif // if CONFIG_SOC_WIFI_SUPPORT_5G + WiFi.scanNetworks(async, show_hidden, passive, max_ms_per_chan /*, channel */); + + if (!async) { + FeedSW_watchdog(); + WiFi_AP_Candidates.process_WiFiscan(); +// processScanDone(); + } + } +# if FEATURE_TIMING_STATS + + if (async) { + STOP_TIMER(WIFI_SCAN_ASYNC); + } else { + STOP_TIMER(WIFI_SCAN_SYNC); + } +# endif // if FEATURE_TIMING_STATS + +# if ESP_IDF_VERSION_MAJOR < 5 + RTC.clearLastWiFi(); + + if (WiFiConnected()) { + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, F("WiFi : Disconnect after scan")); + # endif + +// const bool needReconnect = WiFiEventData.wifiConnectAttemptNeeded; + WifiDisconnect(); +// WiFiEventData.wifiConnectAttemptNeeded = needReconnect; + } +# endif // if ESP_IDF_VERSION_MAJOR < 5 +# if CONFIG_SOC_WIFI_SUPPORT_5G + + // Restore band mode + WiFi.setBandMode(current_wifi_band_mode); +# endif // if CONFIG_SOC_WIFI_SUPPORT_5G +} + +float doGetRSSIthreshold(float& maxTXpwr) { + maxTXpwr = Settings.getWiFi_TX_power(); + + float threshold = WIFI_SENSITIVITY_n; + + switch (doGetConnectionProtocol()) + { + case WiFiConnectionProtocol::WiFi_Protocol_11b: + threshold = WIFI_SENSITIVITY_11b; + + if (maxTXpwr > MAX_TX_PWR_DBM_11b) { maxTXpwr = MAX_TX_PWR_DBM_11b; } + break; + case WiFiConnectionProtocol::WiFi_Protocol_11g: + threshold = WIFI_SENSITIVITY_54g; + + if (maxTXpwr > MAX_TX_PWR_DBM_54g) { maxTXpwr = MAX_TX_PWR_DBM_54g; } + break; + case WiFiConnectionProtocol::WiFi_Protocol_HT20: + case WiFiConnectionProtocol::WiFi_Protocol_HT40: + case WiFiConnectionProtocol::WiFi_Protocol_HE20: + + threshold = WIFI_SENSITIVITY_n; + + if (maxTXpwr > MAX_TX_PWR_DBM_n) { maxTXpwr = MAX_TX_PWR_DBM_n; } + break; +# if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(5, 2, 0) + case WiFiConnectionProtocol::WiFi_Protocol_11a: + case WiFiConnectionProtocol::WiFi_Protocol_VHT20: + // FIXME TD-er: Must determine max. TX power for these 5 GHz modi +# endif // if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(5, 2, 0) + case WiFiConnectionProtocol::WiFi_Protocol_LR: + case WiFiConnectionProtocol::Unknown: + break; + } + return threshold; +} + +WiFiConnectionProtocol doGetConnectionProtocol() +{ + // #ifndef ESP32P4 + if (WiFi.RSSI() < 0) { + wifi_phy_mode_t phymode; + esp_wifi_sta_get_negotiated_phymode(&phymode); + + switch (phymode) + { + case WIFI_PHY_MODE_11B: return WiFiConnectionProtocol::WiFi_Protocol_11b; + case WIFI_PHY_MODE_11G: return WiFiConnectionProtocol::WiFi_Protocol_11g; + case WIFI_PHY_MODE_HT20: return WiFiConnectionProtocol::WiFi_Protocol_HT20; + case WIFI_PHY_MODE_HT40: return WiFiConnectionProtocol::WiFi_Protocol_HT40; + case WIFI_PHY_MODE_HE20: return WiFiConnectionProtocol::WiFi_Protocol_HE20; + case WIFI_PHY_MODE_LR: return WiFiConnectionProtocol::WiFi_Protocol_LR; +# if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) + + // 5 GHz + case WIFI_PHY_MODE_11A: return WiFiConnectionProtocol::WiFi_Protocol_11a; + case WIFI_PHY_MODE_VHT20: return WiFiConnectionProtocol::WiFi_Protocol_VHT20; +# endif // if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) + } + } + + // #endif + return WiFiConnectionProtocol::Unknown; +} + +# if FEATURE_SET_WIFI_TX_PWR + +void doSetWiFiTXpower(float& dBm) +{ + int8_t power = dBm * 4; + + esp_wifi_set_max_tx_power(power); + + if (esp_wifi_get_max_tx_power(&power) == ESP_OK) { + dBm = static_cast(power) / 4.0f; + } +} + +float doGetWiFiTXpower() +{ + int8_t power{}; + + if (esp_wifi_get_max_tx_power(&power) == ESP_OK) { + float res = power; + res /= 4.0f; + return res; + } + + return NAN; +} + +# endif // if FEATURE_SET_WIFI_TX_PWR + +# if CONFIG_SOC_WIFI_SUPPORT_5G + +bool setProtocol(wifi_interface_t ifx, uint16_t protocol_2GHz, uint16_t protocol_5GHz) +{ + esp_err_t err; + + switch (WiFi.getBandMode()) + { + case WIFI_BAND_MODE_2G_ONLY: err = esp_wifi_set_protocol(ifx, protocol_2GHz); + break; + case WIFI_BAND_MODE_5G_ONLY: err = esp_wifi_set_protocol(ifx, protocol_5GHz); + break; + default: + { + wifi_protocols_t protocols { .ghz_2g = protocol_2GHz, .ghz_5g = protocol_5GHz }; + err = esp_wifi_set_protocols(WIFI_IF_STA, &protocols); + break; + } + } + + if (err != ESP_OK) { + // TODO TD-er: Log + } + + return err == ESP_OK; +} + +# else // if CONFIG_SOC_WIFI_SUPPORT_5G + +bool setProtocol(wifi_interface_t ifx, uint8_t protocol_2GHz) { + # ifndef SOC_WIFI_SUPPORTED + return false; + # else + return esp_wifi_set_protocol(ifx, protocol_2GHz) == ESP_OK; + # endif // ifndef SOC_WIFI_SUPPORTED +} + +# endif // if CONFIG_SOC_WIFI_SUPPORT_5G + + +# if CONFIG_SOC_WIFI_SUPPORT_5G + +void doSetConnectionSpeed(bool ForceWiFi_bg_mode, wifi_band_mode_t WiFi_band_mode) +# else // if CONFIG_SOC_WIFI_SUPPORT_5G + +void doSetConnectionSpeed(bool ForceWiFi_bg_mode) +# endif // if CONFIG_SOC_WIFI_SUPPORT_5G +{ + // Does not (yet) work, so commented out. + + // HT20 = 20 MHz channel width. + // HT40 = 40 MHz channel width. + // In theory, HT40 can offer upto 150 Mbps connection speed. + // However since HT40 is using nearly all channels on 2.4 GHz WiFi, + // Thus you are more likely to experience disturbances. + // The response speed and stability is better at HT20 for ESP units. + # ifndef SOC_WIFI_SUPPORTED + return; + # else + esp_wifi_set_bandwidth(WIFI_IF_STA, WIFI_BW_HT20); + + +# if CONFIG_SOC_WIFI_SUPPORT_5G + WiFi.setBandMode(WiFi_band_mode); +# if CONFIG_SOC_WIFI_HE_SUPPORT + uint8_t protocol_5GHz = WIFI_PROTOCOL_11N | WIFI_PROTOCOL_11A | WIFI_PROTOCOL_11AC | WIFI_PROTOCOL_11AX; +# else + uint8_t protocol_5GHz = WIFI_PROTOCOL_11N | WIFI_PROTOCOL_11A | WIFI_PROTOCOL_11AC; +# endif // if CONFIG_SOC_WIFI_HE_SUPPORT +# endif // if CONFIG_SOC_WIFI_SUPPORT_5G + + uint8_t protocol = 0; + + if (ForceWiFi_bg_mode) { + protocol = WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G; // Default to BG + } + + const WiFi_AP_Candidate candidate = WiFi_AP_Candidates.getCurrent(); + + if (candidate.phy_known()) { + if (protocol == 0) { + // No protocol preference set, so we try to match at least the modes supported by the AP + if (candidate.bits.phy_11b) { protocol |= WIFI_PROTOCOL_11B; } + + if (candidate.bits.phy_11g) { protocol |= WIFI_PROTOCOL_11G; } + + if (candidate.bits.phy_11n) { protocol |= WIFI_PROTOCOL_11N; } +# if CONFIG_SOC_WIFI_HE_SUPPORT + + if (candidate.bits.phy_11ax) { protocol |= WIFI_PROTOCOL_11AX; } +# endif // if CONFIG_SOC_WIFI_HE_SUPPORT + + /* + # if CONFIG_SOC_WIFI_SUPPORT_5G + + if (candidate.bits.phy_11a) { protocol |= WIFI_PROTOCOL_11A; } + + if (candidate.bits.phy_11ac) { protocol |= WIFI_PROTOCOL_11AC; } + # endif // if CONFIG_SOC_WIFI_SUPPORT_5G + */ + } else + + // Check to see if the access point is set to "N-only" + if ((protocol & WIFI_PROTOCOL_11N) == 0) { + if (!candidate.bits.phy_11b && !candidate.bits.phy_11g && candidate.bits.phy_11n) { + if (candidate.bits.phy_11n) { + // Set to use BGN + protocol |= WIFI_PROTOCOL_11N; + addLog(LOG_LEVEL_INFO, F("WIFI : AP is set to 802.11n only")); + } +# if CONFIG_SOC_WIFI_HE_SUPPORT + + if (candidate.bits.phy_11ax) { + // Set to use WiFi6 + protocol |= WIFI_PROTOCOL_11AX; + + // protocol_5GHz |= WIFI_PROTOCOL_11AX; + addLog(LOG_LEVEL_INFO, F("WIFI : AP allows 802.11ax, Wi-Fi 6")); + } +# if CONFIG_SOC_WIFI_SUPPORT_5G + + if (candidate.bits.phy_11a) { + // Set to use 5 GHz WiFi + // protocol_5GHz |= WIFI_PROTOCOL_11A; + addLog(LOG_LEVEL_INFO, F("WIFI : AP allows 802.11a, 5 GHz")); + } + + if (candidate.bits.phy_11ac) { + // Set to use 5 GHz WiFi-5 + // protocol_5GHz |= WIFI_PROTOCOL_11AC; + addLog(LOG_LEVEL_INFO, F("WIFI : AP allows 802.11ac, 5 GHz Wi-Fi 5")); + } +# endif // if CONFIG_SOC_WIFI_SUPPORT_5G +# endif // if CONFIG_SOC_WIFI_HE_SUPPORT + } + } + } + + + if (doWifiIsSTA(WiFi.getMode())) { + // Set to use "Long GI" making it more resilliant to reflections + // See: https://www.tp-link.com/us/configuration-guides/q_a_basic_wireless_concepts/?configurationId=2958#_idTextAnchor038 + esp_wifi_config_80211_tx_rate(WIFI_IF_STA, WIFI_PHY_RATE_MCS3_LGI); + # if CONFIG_SOC_WIFI_SUPPORT_5G + setProtocol(WIFI_IF_STA, protocol, protocol_5GHz); + # else + setProtocol(WIFI_IF_STA, protocol); + # endif // if CONFIG_SOC_WIFI_SUPPORT_5G + } + + if (doWifiIsAP(WiFi.getMode())) { + # if CONFIG_SOC_WIFI_SUPPORT_5G + setProtocol(WIFI_IF_AP, protocol, protocol_5GHz); + # else + setProtocol(WIFI_IF_STA, protocol); + # endif // if CONFIG_SOC_WIFI_SUPPORT_5G + } + # endif // ifndef SOC_WIFI_SUPPORTED +} + +void doSetWiFiNoneSleep() { + # ifndef ESP32P4 + WiFi.setSleep(WIFI_PS_NONE); + # endif +} + +void doSetWiFiEcoPowerMode() +{ + // Maximum modem power saving. + // In this mode, interval to receive beacons is determined by the listen_interval parameter in wifi_sta_config_t + // FIXME TD-er: Must test if this is desired behavior in ESP32. + # ifndef ESP32P4 + WiFi.setSleep(WIFI_PS_MAX_MODEM); + # endif +} + +void doSetWiFiDefaultPowerMode() +{ + // Minimum modem power saving. + // In this mode, station wakes up to receive beacon every DTIM period + # ifndef ESP32P4 + WiFi.setSleep(WIFI_PS_MIN_MODEM); + # endif +} + +void doSetWiFiCountryPolicyManual() +{ + /* wifi_country_t config = { + .cc = "01", + .schan = 1, + .nchan = 14, + .policy = WIFI_COUNTRY_POLICY_MANUAL, + }; + + esp_wifi_set_country(&config); + */ +} + +/* + class WiFi_Access_Static_IP : public WiFiSTAClass + { + public: + + void set_use_static_ip(bool enabled); + }; + */ +void doSetUseStaticIP(bool enabled) {} + +} // namespace wifi +} // namespace net +} // namespace ESPEasy + +# endif // ifdef ESP32 +#endif // if FEATURE_WIFI diff --git a/src/ESPEasy/net/wifi/ESPEasyWifi_abstracted_ESP8266.cpp b/src/ESPEasy/net/wifi/ESPEasyWifi_abstracted_ESP8266.cpp new file mode 100644 index 000000000..9cdddf39e --- /dev/null +++ b/src/ESPEasy/net/wifi/ESPEasyWifi_abstracted_ESP8266.cpp @@ -0,0 +1,355 @@ +#include "../wifi/ESPEasyWifi_abstracted.h" + +#if FEATURE_WIFI +# ifdef ESP8266 + +# include "../../../src/DataStructs/TimingStats.h" +# include "../../../src/Helpers/StringConverter.h" +# include "../../../src/Globals/Services.h" +# include "../../../src/Globals/Settings.h" +# include "../Globals/WiFi_AP_Candidates.h" + +# include "../Globals/ESPEasyWiFiEvent.h" +# include "../wifi/ESPEasyWiFi_STA_Event_ESP8266.h" +# include "../../net/ESPEasyNetwork.h" + +// # include "../wifi/ESPEasyWifi.h" + +namespace ESPEasy { +namespace net { +namespace wifi { + +bool WiFi_pre_setup() { + if (!ESPEasyWiFi_STA_EventHandler::initialized()) { return false; } + + doSetSTA_AP(false, false); + delay(100); + return true; +} + +bool WiFi_pre_STA_setup() { + if (!doSetSTA(true)) { return false; } + + // FIXME TD-er: Should ESP8266 first disable autoconnect/autoreconnect? + // On ESP32 this does clear the last used credentials, so it will be able to accept different credentials to connect to. + + // Assign to 2 separate bools to make sure both are executed. + const bool autoConnect = WiFi.setAutoConnect(true); + const bool autoReconnect = WiFi.setAutoReconnect(true); +#ifndef LIMIT_BUILD_SIZE + if (!autoConnect || !autoReconnect) { + addLog(LOG_LEVEL_ERROR, F("WiFi : Disabling auto (re)connect failed")); + } +#endif + delay(100); + return true; +} + +void doWiFiDisconnect() { + // Only call disconnect when STA is active + if (doWifiIsSTA(WiFi.getMode())) { + wifi_station_disconnect(); + } + station_config conf{}; + memset(&conf, 0, sizeof(conf)); + ETS_UART_INTR_DISABLE(); + wifi_station_set_config_current(&conf); + ETS_UART_INTR_ENABLE(); +} + +bool doWifiIsAP(WiFiMode_t wifimode) { return (wifimode == WIFI_AP) || (wifimode == WIFI_AP_STA); } + +bool doWifiIsSTA(WiFiMode_t wifimode) { return (wifimode == WIFI_STA) || (wifimode == WIFI_AP_STA); } + +bool doSetWifiMode(WiFiMode_t new_mode) +{ + if (!Settings.getNetworkEnabled(NETWORK_INDEX_WIFI_AP)) { + if (new_mode == WIFI_AP) { new_mode = WIFI_OFF; } + + if (new_mode == WIFI_AP_STA) { new_mode = WIFI_STA; } + } + + if (!Settings.getNetworkEnabled(NETWORK_INDEX_WIFI_STA)) { + if (new_mode == WIFI_STA) { new_mode = WIFI_OFF; } + + if (new_mode == WIFI_AP_STA) { new_mode = WIFI_AP; } + } + + const WiFiMode_t cur_mode = WiFi.getMode(); + + // Made this static flag an int as ESP8266 and ESP32 differ in the "not set" values + static int8_t processing_wifi_mode = -1; + + if (cur_mode == new_mode) { + return true; + } + + if (processing_wifi_mode == static_cast(new_mode)) { + // Prevent loops + return true; + } + processing_wifi_mode = static_cast(new_mode); + + + if (!doWifiIsSTA(new_mode)) { + // calls lwIP's dhcp_stop(), + // safe to call even if not started + // See: https://github.com/esp8266/Arduino/pull/5703/files + wifi_station_dhcpc_stop(); + } + + if (new_mode != WIFI_OFF) { + // See: https://github.com/esp8266/Arduino/issues/6172#issuecomment-500457407 + WiFi.forceSleepWake(); // Make sure WiFi is really active. + delay(100); + } +# ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, concat(F("WIFI : Set WiFi to "), doGetWifiModeString(new_mode))); +# endif + +# if FEATURE_DNS_SERVER + if (!doWifiIsAP(new_mode) && dnsServerActive) { + dnsServerActive = false; + dnsServer.stop(); + } +# endif // if FEATURE_DNS_SERVER + + int retry = 2; + + while (!WiFi.mode(new_mode) && retry > 0) { + delay(100); + --retry; + } + retry = 2; + + while (WiFi.getMode() != new_mode && retry > 0) { +# ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, F("WIFI : mode not yet set")); +# endif + delay(100); + --retry; + } + + if (WiFi.getMode() != new_mode) { +# ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_ERROR, F("WIFI : Cannot set mode!!!!!")); +# endif + return false; + } + + + if (new_mode == WIFI_OFF) { + WiFi.forceSleepBegin(); + delay(1); + } + + +# if FEATURE_MDNS + # ifdef ESP8266 + + // notifyAPChange() is not present in the ESP32 MDNSResponder + MDNS.notifyAPChange(); + # endif // ifdef ESP8266 + # endif // if FEATURE_MDNS + return true; +} + +void doWifiScan(bool async, uint8_t channel) { + doSetSTA(true); + + if (!doWiFiScanAllowed()) { + return; + } + + START_TIMER; + + // WiFiEventData.lastScanMoment.setNow(); + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + if (channel == 0) { + addLog(LOG_LEVEL_INFO, F("WiFi : Start network scan all channels")); + } else { + addLogMove(LOG_LEVEL_INFO, strformat(F("WiFi : Start network scan ch: %d "), channel)); + } + } + # endif // ifndef BUILD_NO_DEBUG + bool show_hidden = true; + + // WiFiEventData.lastGetScanMoment.setNow(); + // WiFiEventData.lastScanChannel = channel; + + unsigned int nrScans = 1 + (async ? 0 : Settings.ConnectFailRetryCount); + + while (nrScans > 0) { + WiFi_AP_Candidates.begin_scan(); + + if (!async) { + FeedSW_watchdog(); + } + --nrScans; + WiFi.scanNetworks(async, show_hidden, channel); + + if (!async) { + FeedSW_watchdog(); + WiFi_AP_Candidates.process_WiFiscan(); + + // FIXME TD-er: This should call WiFi_AP_Candidates.process_WiFiscan(); + // processScanDone(); + } + } +# if FEATURE_TIMING_STATS + + if (async) { + STOP_TIMER(WIFI_SCAN_ASYNC); + } else { + STOP_TIMER(WIFI_SCAN_SYNC); + } +# endif // if FEATURE_TIMING_STATS + + +} + +float doGetRSSIthreshold(float& maxTXpwr) { + maxTXpwr = Settings.getWiFi_TX_power(); + + float threshold = WIFI_SENSITIVITY_n; + + switch (doGetConnectionProtocol()) + { + case WiFiConnectionProtocol::WiFi_Protocol_11b: + threshold = WIFI_SENSITIVITY_11b; + + if (maxTXpwr > MAX_TX_PWR_DBM_11b) { maxTXpwr = MAX_TX_PWR_DBM_11b; } + break; + case WiFiConnectionProtocol::WiFi_Protocol_11g: + threshold = WIFI_SENSITIVITY_54g; + + if (maxTXpwr > MAX_TX_PWR_DBM_54g) { maxTXpwr = MAX_TX_PWR_DBM_54g; } + break; + case WiFiConnectionProtocol::WiFi_Protocol_11n: + threshold = WIFI_SENSITIVITY_n; + + if (maxTXpwr > MAX_TX_PWR_DBM_n) { maxTXpwr = MAX_TX_PWR_DBM_n; } + break; + case WiFiConnectionProtocol::Unknown: + break; + } + return threshold; +} + +WiFiConnectionProtocol doGetConnectionProtocol() +{ + if (WiFi.RSSI() < 0) { + switch (wifi_get_phy_mode()) + { + case PHY_MODE_11B: + return WiFiConnectionProtocol::WiFi_Protocol_11b; + case PHY_MODE_11G: + return WiFiConnectionProtocol::WiFi_Protocol_11g; + case PHY_MODE_11N: + return WiFiConnectionProtocol::WiFi_Protocol_11n; + } + } + return WiFiConnectionProtocol::Unknown; +} + +# if FEATURE_SET_WIFI_TX_PWR + +void doSetWiFiTXpower(float& dBm) { WiFi.setOutputPower(dBm); } + +float doGetWiFiTXpower() { return WiFi.getOutputPower(); } + +# endif // if FEATURE_SET_WIFI_TX_PWR + +void doSetConnectionSpeed(bool ForceWiFi_bg_mode) { + // ESP8266 only supports 802.11g mode when running in STA+AP + const bool forcedByAPmode = doWifiIsAP(WiFi.getMode()); + WiFiPhyMode_t phyMode = (ForceWiFi_bg_mode || forcedByAPmode) ? WIFI_PHY_MODE_11G : WIFI_PHY_MODE_11N; + + if (!forcedByAPmode) { + const WiFi_AP_Candidate candidate = WiFi_AP_Candidates.getCurrent(); + + if (candidate.phy_known() && (candidate.bits.phy_11g != candidate.bits.phy_11n)) { + if ((WIFI_PHY_MODE_11G == phyMode) && !candidate.bits.phy_11g) { + phyMode = WIFI_PHY_MODE_11N; +# ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, F("WIFI : AP is set to 802.11n only")); +# endif + } else if ((WIFI_PHY_MODE_11N == phyMode) && !candidate.bits.phy_11n) { + phyMode = WIFI_PHY_MODE_11G; +# ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, F("WIFI : AP is set to 802.11g only")); +# endif + } + + /* + } else { + bool useAlternate = WiFiEventData.connectionFailures > 10; + + if (useAlternate) { + phyMode = (WIFI_PHY_MODE_11G == phyMode) ? WIFI_PHY_MODE_11N : WIFI_PHY_MODE_11G; + } + */ + } + } else { + // No need to perform a next attempt. + WiFi_AP_Candidates.markAttempt(); + } + + if (WiFi.getPhyMode() == phyMode) { + return; + } + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = concat(F("WIFI : Set to 802.11"), (WIFI_PHY_MODE_11G == phyMode) ? 'g' : 'n'); + + if (forcedByAPmode) { + log += (F(" (AP+STA mode)")); + } + + if (Settings.ForceWiFi_bg_mode()) { + log += F(" Force B/G mode"); + } + addLogMove(LOG_LEVEL_INFO, log); + } + # endif // ifndef BUILD_NO_DEBUG + WiFi.setPhyMode(phyMode); +} + +void doSetWiFiNoneSleep() { WiFi.setSleepMode(WIFI_NONE_SLEEP); } + +void doSetWiFiEcoPowerMode() +{ + // Allow light sleep during idle times + WiFi.setSleepMode(WIFI_LIGHT_SLEEP); +} + +void doSetWiFiDefaultPowerMode() { WiFi.setSleepMode(WIFI_MODEM_SLEEP); } + +void doSetWiFiCountryPolicyManual() { + // Not yet implemented/required for ESP8266 +} + +class WiFi_Access_Static_IP : public ESP8266WiFiSTAClass +{ +public: + + void set_use_static_ip(bool enabled); +}; + +void WiFi_Access_Static_IP::set_use_static_ip(bool enabled) { _useStaticIp = enabled; } + +void doSetUseStaticIP(bool enabled) { + WiFi_Access_Static_IP tmp; + + tmp.set_use_static_ip(enabled); +} + +} // namespace wifi +} // namespace net +} // namespace ESPEasy + +# endif // ifdef ESP8266 +#endif // if FEATURE_WIFI diff --git a/src/ESPEasy/net/wifi/WiFiConnectionProtocol.cpp b/src/ESPEasy/net/wifi/WiFiConnectionProtocol.cpp new file mode 100644 index 000000000..a7fa45ff5 --- /dev/null +++ b/src/ESPEasy/net/wifi/WiFiConnectionProtocol.cpp @@ -0,0 +1,62 @@ +#include "../wifi/WiFiConnectionProtocol.h" + +#if FEATURE_WIFI +namespace ESPEasy { +namespace net { +namespace wifi { + + +# ifdef ESP8266 + +const __FlashStringHelper* toString(WiFiConnectionProtocol proto) { + switch (proto) + { + case WiFiConnectionProtocol::WiFi_Protocol_11b: + return F("802.11b"); + case WiFiConnectionProtocol::WiFi_Protocol_11g: + return F("802.11g"); + case WiFiConnectionProtocol::WiFi_Protocol_11n: + return F("802.11n"); + case WiFiConnectionProtocol::Unknown: + break; + } + return F("-"); +} + +# endif // ifdef ESP8266 + +# ifdef ESP32 + +const __FlashStringHelper* toString(WiFiConnectionProtocol proto) { + switch (proto) + { + case WiFiConnectionProtocol::WiFi_Protocol_11b: + return F("Wi-Fi 1: 802.11b"); + case WiFiConnectionProtocol::WiFi_Protocol_11g: + return F("Wi-Fi 3: 802.11g"); + case WiFiConnectionProtocol::WiFi_Protocol_HT20: + return F("Wi-Fi 4: 802.11n (HT20)"); + case WiFiConnectionProtocol::WiFi_Protocol_HT40: + return F("Wi-Fi 4: 802.11n (HT40)"); + # if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(5, 2, 0) + case WiFiConnectionProtocol::WiFi_Protocol_11a: + return F("Wi-Fi 2: 802.11a"); + case WiFiConnectionProtocol::WiFi_Protocol_VHT20: + return F("Wi-Fi 5: 802.11ac (VHT20)"); + # endif // if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(5, 2, 0) + case WiFiConnectionProtocol::WiFi_Protocol_HE20: + return F("Wi-Fi 6: 802.11ax (HE20)"); + case WiFiConnectionProtocol::WiFi_Protocol_LR: + return F("802.11lr"); + + case WiFiConnectionProtocol::Unknown: + break; + } + return F("-"); +} + +# endif // ifdef ESP32 +} // namespace wifi +} // namespace net +} // namespace ESPEasy +#endif // if FEATURE_WIFI diff --git a/src/ESPEasy/net/wifi/WiFiConnectionProtocol.h b/src/ESPEasy/net/wifi/WiFiConnectionProtocol.h new file mode 100644 index 000000000..0dc34223d --- /dev/null +++ b/src/ESPEasy/net/wifi/WiFiConnectionProtocol.h @@ -0,0 +1,54 @@ +#pragma once + +#include "../../../ESPEasy_common.h" + +#if FEATURE_WIFI + + +namespace ESPEasy { +namespace net { +namespace wifi { + + +# ifdef ESP8266 + +enum class WiFiConnectionProtocol { + Unknown, + WiFi_Protocol_11b, + WiFi_Protocol_11g, + WiFi_Protocol_11n + +}; + + +# endif // ifdef ESP8266 + +# ifdef ESP32 + +enum class WiFiConnectionProtocol { + Unknown, + WiFi_Protocol_11b, + WiFi_Protocol_11g, + WiFi_Protocol_HT20, + WiFi_Protocol_HT40, +# if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(5, \ + 2, \ + 0) + WiFi_Protocol_11a, // traditional (old) 5 GHz + WiFi_Protocol_VHT20, // 5 GHz WiFi 5, 802.11ac +# endif // if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(5, 2, 0) + WiFi_Protocol_HE20, // WiFi 6 + WiFi_Protocol_LR + +}; + +# endif // ifdef ESP32 + +const __FlashStringHelper* toString(WiFiConnectionProtocol proto); + + +} // namespace wifi +} // namespace net +} // namespace ESPEasy + +#endif // if FEATURE_WIFI diff --git a/src/src/DataTypes/WiFiDisconnectReason.h b/src/ESPEasy/net/wifi/WiFiDisconnectReason.h similarity index 78% rename from src/src/DataTypes/WiFiDisconnectReason.h rename to src/ESPEasy/net/wifi/WiFiDisconnectReason.h index 76ef13f64..faefbb833 100644 --- a/src/src/DataTypes/WiFiDisconnectReason.h +++ b/src/ESPEasy/net/wifi/WiFiDisconnectReason.h @@ -1,7 +1,22 @@ -#ifndef DATATYPES_WIFIDISCONNECTREASON_H -#define DATATYPES_WIFIDISCONNECTREASON_H +#pragma once -#ifdef ESP32 +#include "../../../ESPEasy_common.h" + +#if FEATURE_WIFI + + +// The WiFi Disconnect Reason is not part of the ESPEasy::net::wifi namespace +// ESP8266 does have it defined as a type, but ESP32 doesn't. +// So to not further complicate every call where this is used, it is left +// out of this namespace + +/* + namespace ESPEasy { + namespace net { + namespace wifi { + */ + +# ifdef ESP32 enum WiFiDisconnectReason { WIFI_DISCONNECT_REASON_UNSPECIFIED = 1, @@ -33,7 +48,15 @@ enum WiFiDisconnectReason WIFI_DISCONNECT_REASON_AUTH_FAIL = 202, WIFI_DISCONNECT_REASON_ASSOC_FAIL = 203, WIFI_DISCONNECT_REASON_HANDSHAKE_TIMEOUT = 204 -}; -#endif -#endif \ No newline at end of file +}; + +# endif // ifdef ESP32 + +/* + } + } + } + */ + +#endif // if FEATURE_WIFI diff --git a/src/ESPEasy/net/wifi/WiFi_STA_connected_state.h b/src/ESPEasy/net/wifi/WiFi_STA_connected_state.h new file mode 100644 index 000000000..e8cabecce --- /dev/null +++ b/src/ESPEasy/net/wifi/WiFi_STA_connected_state.h @@ -0,0 +1,26 @@ +#pragma once + +#include "../../../ESPEasy_common.h" + +#if FEATURE_WIFI + +namespace ESPEasy { +namespace net { +namespace wifi { + + +enum class STA_connected_state { + Idle, + Connecting, + Connected, + Error_Not_Found, + Error_Connect_Failed + +}; + + +} // namespace wifi +} // namespace net +} // namespace ESPEasy + +#endif // if FEATURE_WIFI diff --git a/src/ESPEasy/net/wifi/WiFi_State.cpp b/src/ESPEasy/net/wifi/WiFi_State.cpp new file mode 100644 index 000000000..33b66b9b0 --- /dev/null +++ b/src/ESPEasy/net/wifi/WiFi_State.cpp @@ -0,0 +1,32 @@ +#include "../wifi/WiFi_State.h" + +#if FEATURE_WIFI + + +namespace ESPEasy { +namespace net { +namespace wifi { + +const __FlashStringHelper* toString(WiFiState_e state) +{ + switch (state) + { + case ESPEasy::net::wifi::WiFiState_e::Disabled: return F("Disabled"); + case ESPEasy::net::wifi::WiFiState_e::WiFiOFF: return F("OFF"); + case ESPEasy::net::wifi::WiFiState_e::AP_only: return F("AP_only"); + case ESPEasy::net::wifi::WiFiState_e::AP_Fallback: return F("AP_Fallback"); + case ESPEasy::net::wifi::WiFiState_e::IdleWaiting: return F("IdleWaiting"); + case ESPEasy::net::wifi::WiFiState_e::STA_Scanning: return F("STA_Scanning"); + case ESPEasy::net::wifi::WiFiState_e::STA_AP_Scanning: return F("STA_AP_Scanning"); + case ESPEasy::net::wifi::WiFiState_e::STA_Connecting: return F("STA_Connecting"); + case ESPEasy::net::wifi::WiFiState_e::STA_Reconnecting: return F("STA_Reconnecting"); + case ESPEasy::net::wifi::WiFiState_e::STA_Connected: return F("STA_Connected"); + } + return F(""); +} + +} // namespace wifi +} // namespace net +} // namespace ESPEasy + +#endif // if FEATURE_WIFI diff --git a/src/ESPEasy/net/wifi/WiFi_State.h b/src/ESPEasy/net/wifi/WiFi_State.h new file mode 100644 index 000000000..6842150e6 --- /dev/null +++ b/src/ESPEasy/net/wifi/WiFi_State.h @@ -0,0 +1,99 @@ +#pragma once + +#include "../../../ESPEasy_common.h" + +#if FEATURE_WIFI + + +namespace ESPEasy { +namespace net { +namespace wifi { + + +/* + + State machine: + + Start at boot: Disabled + + Disabled: + When AP or STA is needed -> OFF + + + OFF: + When STA is needed -> IdleWaiting + + + IdleWaiting: + After waiting period: + If candidate present -> start STA_Connecting + Else if AP enabled (and client connected to AP) -> Start STA_AP_Scanning + Else -> start STA_Scanning + + STA_Scanning: + When finished: + If known candidate present -> IdleWaiting + else if no known candidate present -> start AP_Fallback -> IdleWaiting + + STA_Connecting: + When failed -> STA_Reconnecting + Else -> STA_Connected + + STA_Reconnecting: + When failed -> IdleWaiting + Else -> STA_Connected + + + STA_Connected: + If disconnected -> OFF + If connected for X minutes -> mark as stable + + + */ + + +enum class WiFiState_e +{ + // WiFi radio is off and no new attempt should be made (e.g. low power mode or Ethernet active) + Disabled, + + // WiFi radio off, to continue, everything needs to be (re)initialized + WiFiOFF, + + // Only running in AP mode + // Typically this is only used when STA is off and AP Auto Start is checked + // TODO TD-er: Must implement this. + AP_only, + + // Fallback mode which is started when connecting to AP was not possible + AP_Fallback, + + // WiFi was in some kind of error state or needs waiting period + IdleWaiting, + + // STA mode + scanning + STA_Scanning, + + // STA+AP mode + scanning, + // needs some careful handling to prevent disconnecting the connected stations + STA_AP_Scanning, + + // Connecting to an AP + STA_Connecting, + + // Reconnecting to an AP + // May need to handle some specific disconnect reasons differently from connecting for the first time. + STA_Reconnecting, + + // Connected to an AP + STA_Connected + +}; + +const __FlashStringHelper* toString(WiFiState_e state); + + +} // namespace wifi +} // namespace net +} // namespace ESPEasy +#endif // if FEATURE_WIFI diff --git a/src/_C001.cpp b/src/_C001.cpp index 445177d35..f558ce1bc 100644 --- a/src/_C001.cpp +++ b/src/_C001.cpp @@ -27,6 +27,9 @@ bool CPlugin_001(CPlugin::Function function, struct EventStruct *event, String& proto.usesExtCreds = true; proto.defaultPort = 8080; proto.usesID = true; + # if FEATURE_HTTP_TLS + proto.usesTLS = true; + # endif // if FEATURE_HTTP_TLS break; } @@ -50,9 +53,10 @@ bool CPlugin_001(CPlugin::Function function, struct EventStruct *event, String& case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: { - if (C001_DelayHandler == nullptr || !validTaskIndex(event->TaskIndex)) { + if ((C001_DelayHandler == nullptr) || !validTaskIndex(event->TaskIndex)) { break; } + if (C001_DelayHandler->queueFull(event->ControllerIndex)) { break; } @@ -67,8 +71,8 @@ bool CPlugin_001(CPlugin::Function function, struct EventStruct *event, String& if (reserve_special(url, expectedSize)) { url = F("/json.htm?type=command¶m="); - if (sensorType == Sensor_VType::SENSOR_TYPE_SWITCH || - sensorType == Sensor_VType::SENSOR_TYPE_DIMMER) + if ((sensorType == Sensor_VType::SENSOR_TYPE_SWITCH) || + (sensorType == Sensor_VType::SENSOR_TYPE_DIMMER)) { url += F("switchlight&idx="); url += event->idx; @@ -102,9 +106,9 @@ bool CPlugin_001(CPlugin::Function function, struct EventStruct *event, String& constexpr unsigned size = sizeof(C001_queue_element); void *ptr = special_calloc(1, size); - + if (ptr != nullptr) { - std::unique_ptr element(new (ptr) C001_queue_element(event->ControllerIndex, event->TaskIndex, std::move(url))); + UP_C001_queue_element element(new (ptr) C001_queue_element(event->ControllerIndex, event->TaskIndex, std::move(url))); success = C001_DelayHandler->addToQueue(std::move(element)); } @@ -140,22 +144,22 @@ bool do_process_c001_delay_queue(cpluginID_t cpluginID, const Queue_element_base // *INDENT-ON* # ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLog(LOG_LEVEL_DEBUG, element.txt); - } +if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLog(LOG_LEVEL_DEBUG, element.txt); +} # endif // ifndef BUILD_NO_DEBUG - int httpCode = -1; - send_via_http( - cpluginID, - ControllerSettings, - element._controller_idx, - element.txt, - F("GET"), - EMPTY_STRING, - EMPTY_STRING, - httpCode); - return (httpCode >= 100) && (httpCode < 300); +int httpCode = -1; +send_via_http( + cpluginID, + ControllerSettings, + element._controller_idx, + element.txt, + F("GET"), + EMPTY_STRING, + EMPTY_STRING, + httpCode); +return (httpCode >= 100) && (httpCode < 300); } #endif // ifdef USES_C001 diff --git a/src/_C002.cpp b/src/_C002.cpp index 8ea6af9f9..a0088ee59 100644 --- a/src/_C002.cpp +++ b/src/_C002.cpp @@ -8,6 +8,7 @@ // ####################################################################################################### /** Changelog: + * 2025-08-23 tonhuisman: Add 10/sec call to poll background connection process while not connected * 2024-03-24 tonhuisman: Add support for 'Invert On/Off value' in P029 - Domoticz MQTT Helper * 2024-03-24 tonhuisman: Start Changelog (newest on top) */ @@ -237,6 +238,14 @@ bool CPlugin_002(CPlugin::Function function, struct EventStruct *event, String& break; } + # if FEATURE_MQTT_CONNECT_BACKGROUND + case CPlugin::Function::CPLUGIN_TEN_PER_SECOND: + { + MQTTConnectInBackground(CONTROLLER_MAX, true); // Report state + break; + } + # endif // if FEATURE_MQTT_CONNECT_BACKGROUND + default: break; } diff --git a/src/_C003.cpp b/src/_C003.cpp index 3a96f7678..b60773b8a 100644 --- a/src/_C003.cpp +++ b/src/_C003.cpp @@ -63,7 +63,7 @@ bool CPlugin_003(CPlugin::Function function, struct EventStruct *event, String& void *ptr = special_calloc(1, size); if (ptr != nullptr) { - std::unique_ptr element( + UP_C003_queue_element element( new (ptr) C003_queue_element( event->ControllerIndex, event->TaskIndex, diff --git a/src/_C004.cpp b/src/_C004.cpp index a450f0da9..84a9c9422 100644 --- a/src/_C004.cpp +++ b/src/_C004.cpp @@ -23,6 +23,9 @@ bool CPlugin_004(CPlugin::Function function, struct EventStruct *event, String& proto.usesPassword = true; proto.defaultPort = 80; proto.usesID = true; + # if FEATURE_HTTP_TLS + proto.usesTLS = true; + # endif // if FEATURE_HTTP_TLS break; } @@ -77,7 +80,7 @@ bool CPlugin_004(CPlugin::Function function, struct EventStruct *event, String& void *ptr = special_calloc(1, size); if (ptr != nullptr) { - std::unique_ptr element(new (ptr) C004_queue_element(event)); + UP_C004_queue_element element(new (ptr) C004_queue_element(event)); success = C004_DelayHandler->addToQueue(std::move(element)); } diff --git a/src/_C005.cpp b/src/_C005.cpp index e5bd86622..4b8485a79 100644 --- a/src/_C005.cpp +++ b/src/_C005.cpp @@ -16,6 +16,7 @@ // ####################################################################################################### /** Changelog: + * 2025-08-23 tonhuisman: Add 10/sec call to poll background connection process while not connected * 2025-06-17 tonhuisman: Enable sending Derived values when available * 2024-11-29 tonhuisman: Add Discovery trigger setting * 2024-11-11 tonhuisman: Add AutoDiscovery options @@ -86,9 +87,8 @@ bool CPlugin_005(CPlugin::Function function, struct EventStruct *event, String& LoadControllerSettings(event->ControllerIndex, *ControllerSettings); if (ControllerSettings->mqtt_autoDiscovery() && - - // (ControllerSettings->MqttAutoDiscoveryTrigger[0] != 0) && - (ControllerSettings->MqttAutoDiscoveryTopic[0] != 0)) { + (ControllerSettings->MqttAutoDiscoveryTopic[0] != 0) && + (0 == mqttDiscoveryTimeout)) { mqttDiscoveryController = event->ControllerIndex; mqttDiscoveryTimeout = random(10, MQTT_DISCOVERY_MAX_DELAY_0_1_SECONDS); @@ -103,9 +103,10 @@ bool CPlugin_005(CPlugin::Function function, struct EventStruct *event, String& break; } - # if FEATURE_MQTT_DISCOVER case CPlugin::Function::CPLUGIN_TEN_PER_SECOND: { + # if FEATURE_MQTT_DISCOVER + if ((mqttDiscoveryTimeout > 0) && MQTTclient_connected) { mqttDiscoveryTimeout--; @@ -116,9 +117,12 @@ bool CPlugin_005(CPlugin::Function function, struct EventStruct *event, String& success = MQTT_SendAutoDiscovery(event->ControllerIndex, CPLUGIN_ID_005); } } + # endif // if FEATURE_MQTT_DISCOVER + # if FEATURE_MQTT_CONNECT_BACKGROUND + MQTTConnectInBackground(CONTROLLER_MAX, true); // Report state + # endif // if FEATURE_MQTT_CONNECT_BACKGROUND break; } - # endif // if FEATURE_MQTT_DISCOVER case CPlugin::Function::CPLUGIN_EXIT: { diff --git a/src/_C006.cpp b/src/_C006.cpp index 24279077a..8644be4b5 100644 --- a/src/_C006.cpp +++ b/src/_C006.cpp @@ -6,6 +6,7 @@ // ####################################################################################################### /** Changelog: + * 2025-08-23 tonhuisman: Add 10/sec call to poll background connection process while not connected * 2025-06-17 tonhuisman: Enable sending Derived values when available and enabled * 2023-08-18 tonhuisman: Clean up source for pull request * 2023-03-15 tonhuisman: Handle setting payload to (Dummy) Devices via topic SysName/TaskName/ValueName/set @@ -17,7 +18,7 @@ # include "src/ESPEasyCore/Controller.h" # include "src/Globals/Settings.h" # include "src/Helpers/_CPlugin_Helper_mqtt.h" -# include "src/Helpers/Network.h" +# include "src/Helpers/NetworkStatusLED.h" # include "src/Helpers/PeriodicalActions.h" # include "_Plugin_Helper.h" @@ -145,6 +146,14 @@ bool CPlugin_006(CPlugin::Function function, struct EventStruct *event, String& break; } + # if FEATURE_MQTT_CONNECT_BACKGROUND + case CPlugin::Function::CPLUGIN_TEN_PER_SECOND: + { + MQTTConnectInBackground(CONTROLLER_MAX, true); // Report state + break; + } + # endif // if FEATURE_MQTT_CONNECT_BACKGROUND + default: break; } diff --git a/src/_C007.cpp b/src/_C007.cpp index f4de2157d..30e734899 100644 --- a/src/_C007.cpp +++ b/src/_C007.cpp @@ -28,6 +28,9 @@ bool CPlugin_007(CPlugin::Function function, struct EventStruct *event, String& proto.defaultPort = 80; proto.usesID = true; proto.usesTemplate = true; + # if FEATURE_HTTP_TLS + proto.usesTLS = true; + # endif // if FEATURE_HTTP_TLS break; } @@ -81,7 +84,7 @@ bool CPlugin_007(CPlugin::Function function, struct EventStruct *event, String& void *ptr = special_calloc(1, size); if (ptr != nullptr) { - std::unique_ptr element(new (ptr) C007_queue_element(event)); + UP_C007_queue_element element(new (ptr) C007_queue_element(event)); success = C007_DelayHandler->addToQueue(std::move(element)); } diff --git a/src/_C008.cpp b/src/_C008.cpp index 761850332..bf2231548 100644 --- a/src/_C008.cpp +++ b/src/_C008.cpp @@ -26,6 +26,9 @@ bool CPlugin_008(CPlugin::Function function, struct EventStruct *event, String& proto.usesExtCreds = true; proto.defaultPort = 80; proto.usesID = true; + # if FEATURE_HTTP_TLS + proto.usesTLS = true; + # endif // if FEATURE_HTTP_TLS break; } @@ -85,7 +88,7 @@ bool CPlugin_008(CPlugin::Function function, struct EventStruct *event, String& void *ptr = special_calloc(1, size); if (ptr != nullptr) { - std::unique_ptr element(new (ptr) C008_queue_element(event, valueCount)); + UP_C008_queue_element element(new (ptr) C008_queue_element(event, valueCount)); success = C008_DelayHandler->addToQueue(std::move(element)); } diff --git a/src/_C009.cpp b/src/_C009.cpp index 0b5e1a191..1be83eb72 100644 --- a/src/_C009.cpp +++ b/src/_C009.cpp @@ -3,6 +3,8 @@ # include "src/DataTypes/NodeTypeID.h" # include "src/Helpers/StringProvider.h" +# include "src/Helpers/KeyValueWriter_JSON.h" + # include "src/CustomBuild/ESPEasy_buildinfo.h" // ####################################################################################################### @@ -52,6 +54,9 @@ bool CPlugin_009(CPlugin::Function function, struct EventStruct *event, String& proto.usesExtCreds = true; proto.usesID = false; proto.defaultPort = 8383; + # if FEATURE_HTTP_TLS + proto.usesTLS = true; + # endif // if FEATURE_HTTP_TLS break; } @@ -84,7 +89,7 @@ bool CPlugin_009(CPlugin::Function function, struct EventStruct *event, String& void *ptr = special_calloc(1, size); if (ptr != nullptr) { - std::unique_ptr element(new (ptr) C009_queue_element(event)); + UP_C009_queue_element element(new (ptr) C009_queue_element(event)); success = C009_DelayHandler->addToQueue(std::move(element)); } Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_C009_DELAY_QUEUE, C009_DelayHandler->getNextScheduleTime()); @@ -114,110 +119,85 @@ bool CPlugin_009(CPlugin::Function function, struct EventStruct *event, String& bool do_process_c009_delay_queue(cpluginID_t cpluginID, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { const C009_queue_element& element = static_cast(element_base); // *INDENT-ON* -String jsonString; -// Make an educated guess on the actual length, based on earlier requests. -static size_t expectedJsonLength = 100; -{ - // Reserve on the heap with most space - if (!reserve_special(jsonString, expectedJsonLength)) { - // Not enough free memory - return false; - } -} -{ - jsonString += '{'; + // Make an educated guess on the actual length, based on earlier requests. + static size_t expectedJsonLength = 100; + PrintToString jsonPrint; + jsonPrint.reserve(expectedJsonLength); { - jsonString += to_json_object_value(F("module"), F("ESPEasy")); - jsonString += ','; - jsonString += to_json_object_value(F("version"), F("1.04")); - - // Create nested object "ESP" inside "data" - jsonString += ','; - jsonString += F("\"data\":{"); + KeyValueWriter_JSON mainLevelWriter(true, &jsonPrint); { - jsonString += F("\"ESP\":{"); { - // Create nested objects in "ESP": - jsonString += to_json_object_value(F("name"), Settings.getName()); - jsonString += ','; - jsonString += to_json_object_value(F("unit"), static_cast(Settings.Unit)); - jsonString += ','; - jsonString += to_json_object_value(F("version"), static_cast(Settings.Version)); - jsonString += ','; - jsonString += to_json_object_value(F("build"), static_cast(Settings.Build)); - jsonString += ','; - jsonString += to_json_object_value(F("build_notes"), F(BUILD_NOTES)); - jsonString += ','; - jsonString += to_json_object_value(F("build_git"), getValue(LabelType::GIT_BUILD)); - jsonString += ','; - jsonString += to_json_object_value(F("node_type_id"), static_cast(NODE_TYPE_ID)); - jsonString += ','; - jsonString += to_json_object_value(F("sleep"), static_cast(Settings.deepSleep_wakeTime)); + mainLevelWriter.write({F("module"), F("ESPEasy")}); + mainLevelWriter.write({F("version"), F("1.04")}); - // embed IP, important if there is NAT/PAT - // char ipStr[20]; - // IPAddress ip = NetworkLocalIP(); - // sprintf_P(ipStr, PSTR("%u.%u.%u.%u"), ip[0], ip[1], ip[2], ip[3]); - jsonString += ','; - jsonString += to_json_object_value(F("ip"), formatIP(NetworkLocalIP())); - } - jsonString += '}'; // End "ESP" - - jsonString += ','; - - // Create nested object "SENSOR" json object inside "data" - jsonString += F("\"SENSOR\":{"); - { - // char itemNames[valueCount][2]; - for (uint8_t x = 0; x < element.valueCount; x++) - { - // Each sensor value get an own object (0..n) - // sprintf(itemNames[x],"%d",x); - if (x != 0) { - jsonString += ','; - } - - jsonString += '"'; - jsonString += x; - jsonString += F("\":{"); + // Create nested object "ESP" inside "data" + auto data = mainLevelWriter.createChild(F("data")); + if (data) { { - jsonString += to_json_object_value(F("deviceName"), getTaskDeviceName(element._taskIndex)); - jsonString += ','; - jsonString += to_json_object_value(F("valueName"), Cache.getTaskDeviceValueName(element._taskIndex, x)); - jsonString += ','; - jsonString += to_json_object_value(F("type"), static_cast(element.sensorType)); - jsonString += ','; - jsonString += to_json_object_value(F("value"), element.txt[x]); - } - jsonString += '}'; // End "sensor value N" + auto esp = data->createChild(F("ESP")); + if (esp) + { + // Create nested objects in "ESP": + esp->write({F("name"), Settings.getName()}); + esp->write({F("unit"), static_cast(Settings.Unit)}); + esp->write({F("version"), static_cast(Settings.Version)}); + esp->write({F("build"), static_cast(Settings.Build)}); + esp->write({F("build_notes"), F(BUILD_NOTES)}); + esp->write({F("build_git"), getValue(LabelType::GIT_BUILD)}); + esp->write({F("node_type_id"), static_cast(NODE_TYPE_ID)}); + esp->write({F("sleep"), static_cast(Settings.deepSleep_wakeTime)}); + + // embed IP, important if there is NAT/PAT + // char ipStr[20]; + // IPAddress ip = NetworkLocalIP(); + // sprintf_P(ipStr, PSTR("%u.%u.%u.%u"), ip[0], ip[1], ip[2], ip[3]}); + esp->write({F("ip"), formatIP(ESPEasy::net::NetworkLocalIP())}); + } + } // End "ESP" + { + // Create nested object "SENSOR" json object inside "data" + auto sensor = data->createChild(F("SENSOR")); + if (sensor) + { + // char itemNames[valueCount][2]; + for (uint8_t x = 0; x < element.valueCount; x++) + { + // Each sensor value get an own object (0..n) + // sprintf(itemNames[x],"%d",x); + auto val_x = sensor->createChild(String(x)); + if (val_x) { + val_x->write({F("deviceName"), getTaskDeviceName(element._taskIndex)}); + val_x->write({F("valueName"), Cache.getTaskDeviceValueName(element._taskIndex, x)}); + val_x->write({F("type"), static_cast(element.sensorType)}); + val_x->write({F("value"), element.txt[x]}); + } + } // End "sensor value N" + } + } // End "SENSOR" } - } - jsonString += '}'; // End "SENSOR" + } // End "data" } - jsonString += '}'; // End "data" + } // End mainLevelWriter + + if (expectedJsonLength < jsonPrint.get().length()) { + expectedJsonLength = jsonPrint.get().length(); } - jsonString += '}'; // End JSON structure -} -if (expectedJsonLength < jsonString.length()) { - expectedJsonLength = jsonString.length(); -} + // addLog(LOG_LEVEL_INFO, F("C009 Test JSON:")); + // addLog(LOG_LEVEL_INFO, mainLevelWriter); -// addLog(LOG_LEVEL_INFO, F("C009 Test JSON:")); -// addLog(LOG_LEVEL_INFO, jsonString); - -int httpCode = -1; -send_via_http( - cpluginID, - ControllerSettings, - element._controller_idx, - F("/ESPEasy"), - F("POST"), - EMPTY_STRING, - jsonString, - httpCode); -return (httpCode >= 100) && (httpCode < 300); + int httpCode = -1; + send_via_http( + cpluginID, + ControllerSettings, + element._controller_idx, + F("/ESPEasy"), + F("POST"), + EMPTY_STRING, + jsonPrint.get(), + httpCode); + return (httpCode >= 100) && (httpCode < 300); } #endif // ifdef USES_C009 diff --git a/src/_C010.cpp b/src/_C010.cpp index 7322233ef..6e632914d 100644 --- a/src/_C010.cpp +++ b/src/_C010.cpp @@ -74,7 +74,7 @@ bool CPlugin_010(CPlugin::Function function, struct EventStruct *event, String& void *ptr = special_calloc(1, size); if (ptr != nullptr) { - std::unique_ptr element(new (ptr) C010_queue_element(event, valueCount)); + UP_C010_queue_element element(new (ptr) C010_queue_element(event, valueCount)); { String pubname; { diff --git a/src/_C011.cpp b/src/_C011.cpp index c29756c32..4e45f27bd 100644 --- a/src/_C011.cpp +++ b/src/_C011.cpp @@ -19,7 +19,6 @@ bool C011_sendBinary = false; struct C011_ConfigStruct { - void zero_last() { HttpMethod[C011_HTTP_METHOD_MAX_LEN - 1] = 0; HttpUri[C011_HTTP_URI_MAX_LEN - 1] = 0; @@ -31,9 +30,10 @@ struct C011_ConfigStruct char HttpUri[C011_HTTP_URI_MAX_LEN] = { 0 }; char HttpHeader[C011_HTTP_HEADER_MAX_LEN] = { 0 }; char HttpBody[C011_HTTP_BODY_MAX_LEN] = { 0 }; - }; +DEF_UP(C011_ConfigStruct); + // Forward declarations bool load_C011_ConfigStruct(controllerIndex_t ControllerIndex, @@ -63,6 +63,9 @@ bool CPlugin_011(CPlugin::Function function, struct EventStruct *event, String& proto.usesExtCreds = true; proto.defaultPort = 80; proto.usesID = false; + # if FEATURE_HTTP_TLS + proto.usesTLS = true; + # endif // if FEATURE_HTTP_TLS break; } @@ -153,7 +156,7 @@ bool CPlugin_011(CPlugin::Function function, struct EventStruct *event, String& void *ptr = special_calloc(1, size); if (ptr != nullptr) { - std::shared_ptr customConfig(new (ptr) C011_ConfigStruct); + UP_C011_ConfigStruct customConfig(new (ptr) C011_ConfigStruct); if (customConfig) { uint8_t choice = 0; @@ -216,7 +219,7 @@ bool do_process_c011_delay_queue(cpluginID_t cpluginID, const Queue_element_base const C011_queue_element& element = static_cast(element_base); // *INDENT-ON* -if (!NetworkConnected()) { return false; } +if (!ESPEasy::net::NetworkConnected()) { return false; } int httpCode = -1; @@ -245,7 +248,7 @@ bool load_C011_ConfigStruct(controllerIndex_t ControllerIndex, String& HttpMetho return false; } - std::shared_ptrcustomConfig(new (ptr) C011_ConfigStruct); + UP_C011_ConfigStruct customConfig(new (ptr) C011_ConfigStruct); if (!customConfig) { return false; @@ -280,7 +283,7 @@ boolean Create_schedule_HTTP_C011(struct EventStruct *event) // Add a new element to the queue with the minimal payload - std::unique_ptrelement(new (ptr) C011_queue_element(event)); + UP_C011_queue_element element(new (ptr) C011_queue_element(event)); bool success = C011_DelayHandler->addToQueue(std::move(element)); if (success) { @@ -305,7 +308,7 @@ boolean Create_schedule_HTTP_C011(struct EventStruct *event) } ReplaceTokenByValue(element.uri, event, false); - ReplaceTokenByValue(element.header, event, false); + ReplaceTokenByValue(element.header, event, true); // Header shouldn't be URL-encoded https://github.com/letscontrolit/ESPEasy/issues/4819 if (element.postStr.length() > 0) { diff --git a/src/_C012.cpp b/src/_C012.cpp index e4053f668..0be9339c6 100644 --- a/src/_C012.cpp +++ b/src/_C012.cpp @@ -67,7 +67,7 @@ bool CPlugin_012(CPlugin::Function function, struct EventStruct *event, String& break; } - std::unique_ptr element(new (ptr) C012_queue_element(event, valueCount)); + UP_C012_queue_element element(new (ptr) C012_queue_element(event, valueCount)); for (uint8_t x = 0; x < valueCount; x++) { @@ -124,7 +124,7 @@ bool do_process_c012_delay_queue(cpluginID_t cpluginID, const Queue_element_base } } - if (!NetworkConnected()) { + if (!ESPEasy::net::NetworkConnected()) { return false; } return element.checkDone(Blynk_get(element.txt[element.valuesSent], element._controller_idx)); diff --git a/src/_C013.cpp b/src/_C013.cpp index 3e33bb2f8..569766765 100644 --- a/src/_C013.cpp +++ b/src/_C013.cpp @@ -11,7 +11,7 @@ # include "src/DataStructs/C013_p2p_SensorInfoStruct.h" # include "src/ESPEasyCore/ESPEasyRules.h" # include "src/Helpers/Misc.h" -# include "src/Helpers/Network.h" +# include "src/Helpers/NetworkStatusLED.h" // ####################################################################################################### // ########################### Controller Plugin 013: ESPEasy P2P network ################################ @@ -119,23 +119,25 @@ void C013_SendUDPTaskInfo(uint8_t destUnit, uint8_t sourceTaskIndex, uint8_t des return; } - struct C013_SensorInfoStruct infoReply; - infoReply.sourceUnit = Settings.Unit; - infoReply.sourceTaskIndex = sourceTaskIndex; - infoReply.destTaskIndex = destTaskIndex; - infoReply.deviceNumber = pluginID; - infoReply.destUnit = destUnit; + MakeC013_SensorInfo(infoReply); + if (!AllocatedC013_SensorInfo(infoReply)) return; + + infoReply->sourceUnit = Settings.Unit; + infoReply->sourceTaskIndex = sourceTaskIndex; + infoReply->destTaskIndex = destTaskIndex; + infoReply->deviceNumber = pluginID; + infoReply->destUnit = destUnit; if (destUnit == 0) { // Send to broadcast address - infoReply.destUnit = 255; + infoReply->destUnit = 255; } size_t sizeToSend{}; - if (infoReply.prepareForSend(sizeToSend)) { - C013_sendUDP(infoReply.destUnit, reinterpret_cast(&infoReply), sizeToSend); + if (infoReply->prepareForSend(sizeToSend)) { + C013_sendUDP(infoReply->destUnit, reinterpret_cast(infoReply.get()), sizeToSend); } } @@ -144,32 +146,33 @@ void C013_SendUDPTaskData(struct EventStruct *event, uint8_t destUnit, uint8_t d if (!NetworkConnected(10)) { return; } - struct C013_SensorDataStruct dataReply; + MakeC013_SensorData(dataReply); + if (!dataReply) return; - dataReply.sourceUnit = Settings.Unit; - dataReply.sourceTaskIndex = event->TaskIndex; - dataReply.destTaskIndex = destTaskIndex; - dataReply.deviceNumber = Settings.getPluginID_for_task(event->TaskIndex); + dataReply->sourceUnit = Settings.Unit; + dataReply->sourceTaskIndex = event->TaskIndex; + dataReply->destTaskIndex = destTaskIndex; + dataReply->deviceNumber = Settings.getPluginID_for_task(event->TaskIndex); // FIXME TD-er: We should check for sensorType and pluginID on both sides. // For example sending different sensor type data from one dummy to another is probably not going to work well - dataReply.sensorType = event->getSensorType(); + dataReply->sensorType = event->getSensorType(); const TaskValues_Data_t *taskValues = UserVar.getRawTaskValues_Data(event->TaskIndex); if (taskValues != nullptr) { - memcpy(dataReply.taskValues_Data, taskValues->binary, sizeof(dataReply.taskValues_Data)); + memcpy(dataReply->taskValues_Data, taskValues->binary, sizeof(dataReply->taskValues_Data)); } - dataReply.destUnit = destUnit; + dataReply->destUnit = destUnit; if (destUnit == 0) { // Send to broadcast address - dataReply.destUnit = 255; + dataReply->destUnit = 255; } - dataReply.prepareForSend(); - C013_sendUDP(dataReply.destUnit, reinterpret_cast(&dataReply), sizeof(C013_SensorDataStruct)); + dataReply->prepareForSend(); + C013_sendUDP(dataReply->destUnit, reinterpret_cast(dataReply.get()), sizeof(C013_SensorDataStruct)); } /*********************************************************************************************\ @@ -255,69 +258,71 @@ void C013_Receive(struct EventStruct *event) { { // Allocate this is a separate scope since C013_SensorInfoStruct is a HUGE object // Should not be left allocated on the stack when calling PLUGIN_INIT and save, etc. - struct C013_SensorInfoStruct infoReply; - if (infoReply.setData(event->Data, event->Par2)) { + auto infoReply = C013_SensorInfoStruct::create(event->Data, event->Par2); + if (!infoReply) return; + + { // to prevent flash wear out (bugs in communication?) we can only write to an empty task // so it will write only once and has to be cleared manually through webgui // Also check the receiving end does support the plugin ID. - const pluginID_t currentPluginID = Settings.getPluginID_for_task(infoReply.destTaskIndex); + const pluginID_t currentPluginID = Settings.getPluginID_for_task(infoReply->destTaskIndex); bool mustUpdateCurrentTask = false; - if (currentPluginID == infoReply.deviceNumber) { + if (currentPluginID == infoReply->deviceNumber) { // Check to see if task already is set to receive from this host - if ((Settings.TaskDeviceDataFeed[infoReply.destTaskIndex] == infoReply.sourceUnit) && - Settings.TaskDeviceEnabled[infoReply.destTaskIndex]) { + if ((Settings.TaskDeviceDataFeed[infoReply->destTaskIndex] == infoReply->sourceUnit) && + Settings.TaskDeviceEnabled[infoReply->destTaskIndex]) { mustUpdateCurrentTask = true; } } if ((mustUpdateCurrentTask || !validPluginID_fullcheck(currentPluginID)) && - supportedPluginID(infoReply.deviceNumber)) + supportedPluginID(infoReply->deviceNumber)) { - taskClear(infoReply.destTaskIndex, false); - Settings.TaskDeviceNumber[infoReply.destTaskIndex] = infoReply.deviceNumber.value; - Settings.TaskDeviceDataFeed[infoReply.destTaskIndex] = infoReply.sourceUnit; // remote feed store unit nr sending the data + taskClear(infoReply->destTaskIndex, false); + Settings.TaskDeviceNumber[infoReply->destTaskIndex] = infoReply->deviceNumber.value; + Settings.TaskDeviceDataFeed[infoReply->destTaskIndex] = infoReply->sourceUnit; // remote feed store unit nr sending the data if (mustUpdateCurrentTask) { - Settings.TaskDeviceEnabled[infoReply.destTaskIndex] = true; + Settings.TaskDeviceEnabled[infoReply->destTaskIndex] = true; } constexpr pluginID_t DUMMY_PLUGIN_ID{ 33 }; - if ((infoReply.deviceNumber == DUMMY_PLUGIN_ID) && (infoReply.sensorType != Sensor_VType::SENSOR_TYPE_NONE)) { + if ((infoReply->deviceNumber == DUMMY_PLUGIN_ID) && (infoReply->sensorType != Sensor_VType::SENSOR_TYPE_NONE)) { // Received a dummy device and the sensor type is actually set - Settings.TaskDevicePluginConfig[infoReply.destTaskIndex][0] = static_cast(infoReply.sensorType); + Settings.TaskDevicePluginConfig[infoReply->destTaskIndex][0] = static_cast(infoReply->sensorType); } for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) { - Settings.TaskDeviceSendData[x][infoReply.destTaskIndex] = false; + Settings.TaskDeviceSendData[x][infoReply->destTaskIndex] = false; } - safe_strncpy(ExtraTaskSettings.TaskDeviceName, infoReply.taskName, sizeof(infoReply.taskName)); + safe_strncpy(ExtraTaskSettings.TaskDeviceName, infoReply->taskName, sizeof(infoReply->taskName)); for (uint8_t x = 0; x < VARS_PER_TASK; x++) { - safe_strncpy(ExtraTaskSettings.TaskDeviceValueNames[x], infoReply.ValueNames[x], sizeof(infoReply.ValueNames[x])); + safe_strncpy(ExtraTaskSettings.TaskDeviceValueNames[x], infoReply->ValueNames[x], sizeof(infoReply->ValueNames[x])); } - if (infoReply.sourceNodeBuild >= 20871) { - ExtraTaskSettings.version = infoReply.ExtraTaskSettings_version; + if (infoReply->sourceNodeBuild >= 20871) { + ExtraTaskSettings.version = infoReply->ExtraTaskSettings_version; for (uint8_t x = 0; x < VARS_PER_TASK; x++) { -// safe_strncpy(ExtraTaskSettings.TaskDeviceFormula[x], infoReply.TaskDeviceFormula[x], sizeof(infoReply.TaskDeviceFormula[x])); - ExtraTaskSettings.TaskDeviceValueDecimals[x] = infoReply.TaskDeviceValueDecimals[x]; - ExtraTaskSettings.TaskDeviceMinValue[x] = infoReply.TaskDeviceMinValue[x]; - ExtraTaskSettings.TaskDeviceMaxValue[x] = infoReply.TaskDeviceMaxValue[x]; - ExtraTaskSettings.TaskDeviceErrorValue[x] = infoReply.TaskDeviceErrorValue[x]; - ExtraTaskSettings.VariousBits[x] = infoReply.VariousBits[x]; +// safe_strncpy(ExtraTaskSettings.TaskDeviceFormula[x], infoReply->TaskDeviceFormula[x], sizeof(infoReply->TaskDeviceFormula[x])); + ExtraTaskSettings.TaskDeviceValueDecimals[x] = infoReply->TaskDeviceValueDecimals[x]; + ExtraTaskSettings.TaskDeviceMinValue[x] = infoReply->TaskDeviceMinValue[x]; + ExtraTaskSettings.TaskDeviceMaxValue[x] = infoReply->TaskDeviceMaxValue[x]; + ExtraTaskSettings.TaskDeviceErrorValue[x] = infoReply->TaskDeviceErrorValue[x]; + ExtraTaskSettings.VariousBits[x] = infoReply->VariousBits[x]; } for (uint8_t x = 0; x < PLUGIN_CONFIGVAR_MAX; ++x) { - Settings.TaskDevicePluginConfig[infoReply.destTaskIndex][x] = infoReply.TaskDevicePluginConfig[x]; + Settings.TaskDevicePluginConfig[infoReply->destTaskIndex][x] = infoReply->TaskDevicePluginConfig[x]; } } - ExtraTaskSettings.TaskIndex = infoReply.destTaskIndex; - taskIndex = infoReply.destTaskIndex; + ExtraTaskSettings.TaskIndex = infoReply->destTaskIndex; + taskIndex = infoReply->destTaskIndex; mustSave = true; } } @@ -346,16 +351,16 @@ void C013_Receive(struct EventStruct *event) { case 5: // sensor data { - struct C013_SensorDataStruct dataReply; + auto dataReply = C013_SensorDataStruct::create(event->Data, event->Par2); + if (!dataReply) return; // FIXME TD-er: We should check for sensorType and pluginID on both sides. // For example sending different sensor type data from one dummy to another is probably not going to work well - - if (dataReply.setData(event->Data, event->Par2)) { + { // only if this task has a remote feed, update values - const uint8_t remoteFeed = Settings.TaskDeviceDataFeed[dataReply.destTaskIndex]; + const uint8_t remoteFeed = Settings.TaskDeviceDataFeed[dataReply->destTaskIndex]; - if ((remoteFeed != 0) && (remoteFeed == dataReply.sourceUnit)) + if ((remoteFeed != 0) && (remoteFeed == dataReply->sourceUnit)) { // deviceNumber and sensorType were not present before build 2023-05-05. (build NR 20460) // See: @@ -365,47 +370,55 @@ void C013_Receive(struct EventStruct *event) { // If the node is not present in the nodes list (e.g. it had not announced itself in the last 10 minutes or announcement was // missed) // Then we cannot be sure about its build. - const bool mustMatch = dataReply.sourceNodeBuild >= 20460; + const bool mustMatch = dataReply->sourceNodeBuild >= 20460; - if (mustMatch && !dataReply.matchesPluginID(Settings.getPluginID_for_task(dataReply.destTaskIndex))) { + if (mustMatch && !dataReply->matchesPluginID(Settings.getPluginID_for_task(dataReply->destTaskIndex))) { // Mismatch in plugin ID from sending node + #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - String log = concat(F("P2P data : PluginID mismatch for task "), dataReply.destTaskIndex + 1); - log += concat(F(" from unit "), dataReply.sourceUnit); - log += concat(F(" remote: "), dataReply.deviceNumber.value); - log += concat(F(" local: "), Settings.getPluginID_for_task(dataReply.destTaskIndex).value); - addLogMove(LOG_LEVEL_ERROR, log); + addLogMove(LOG_LEVEL_ERROR, strformat( + F("P2P data : PluginID mismatch for task %d from unit %d remote: %d local: %d"), + dataReply->destTaskIndex + 1, + dataReply->sourceUnit, + dataReply->deviceNumber.value, + Settings.getPluginID_for_task(dataReply->destTaskIndex).value + )); } + #endif } else { - struct EventStruct TempEvent(dataReply.destTaskIndex); + struct EventStruct TempEvent(dataReply->destTaskIndex); TempEvent.Source = EventValueSource::Enum::VALUE_SOURCE_UDP; const Sensor_VType sensorType = TempEvent.getSensorType(); - if (!mustMatch || dataReply.matchesSensorType(sensorType)) { - TaskValues_Data_t *taskValues = UserVar.getRawTaskValues_Data(dataReply.destTaskIndex); + if (!mustMatch || dataReply->matchesSensorType(sensorType)) { + TaskValues_Data_t *taskValues = UserVar.getRawTaskValues_Data(dataReply->destTaskIndex); if (taskValues != nullptr) { - memcpy(taskValues->binary, dataReply.taskValues_Data, sizeof(dataReply.taskValues_Data)); + memcpy(taskValues->binary, dataReply->taskValues_Data, sizeof(dataReply->taskValues_Data)); } STOP_TIMER(C013_RECEIVE_SENSOR_DATA); - if (node_time.systemTimePresent() && (dataReply.timestamp_sec != 0)) { + if (node_time.systemTimePresent() && (dataReply->timestamp_sec != 0)) { // Only use timestamp of remote unit when we got a system time ourselves // If not, then the order of samples can get messed up. // timestamp_fraq is 16 bit, so need to scale it to 32 bit - TempEvent.timestamp_frac = static_cast(dataReply.timestamp_frac) << 16; - SensorSendTask(&TempEvent, dataReply.timestamp_sec); + TempEvent.timestamp_frac = static_cast(dataReply->timestamp_frac) << 16; + SensorSendTask(&TempEvent, dataReply->timestamp_sec); } else { SensorSendTask(&TempEvent); } } else { + #ifndef BUILD_NO_DEBUG // Mismatch in sensor types if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - String log = concat(F("P2P data : SensorType mismatch for task "), dataReply.destTaskIndex + 1); - log += concat(F(" from unit "), dataReply.sourceUnit); - addLogMove(LOG_LEVEL_ERROR, log); + addLogMove(LOG_LEVEL_ERROR, strformat( + F("P2P data : SensorType mismatch for task %d from unit %d"), + dataReply->destTaskIndex + 1, + dataReply->sourceUnit + )); } + #endif } } } diff --git a/src/_C014.cpp b/src/_C014.cpp index 3dc1e176d..852af381f 100644 --- a/src/_C014.cpp +++ b/src/_C014.cpp @@ -8,6 +8,7 @@ # include "src/Globals/Plugins.h" # include "src/Globals/Statistics.h" # include "src/Helpers/_CPlugin_Helper_mqtt.h" +# include "src/Helpers/ESPEasy_UnitOfMeasure.h" # include "src/Helpers/PeriodicalActions.h" # include "_Plugin_Helper.h" @@ -16,6 +17,7 @@ // ####################################################################################################### /** Changelog: + * 2025-08-23 tonhuisman: Add 10/sec call to poll background connection process while not connected * 2025-06-18 tonhuisman: Add support for Unit of Measure attribute, when available in the build, sent in AutoDiscovery * Enable sending Derived values when available * 2024-03-02 tonhuisman: Fix using parseSystemVariables() for processing %sysname%. Might still break the same configurations, @@ -288,7 +290,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$localip"), formatIP(NetworkLocalIP()), errorCounter); // $mac Device → Controller Mac address of the device network interface. The format MUST be of the type A1:B2:C3:D4:E5:F6 Yes Yes - CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$mac"), NetworkMacAddress(), errorCounter); + CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$mac"), ESPEasy::net::NetworkMacAddress(), errorCounter); // $implementation Device → Controller An identifier for the Homie implementation (example esp8266) Yes Yes CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$implementation"), @@ -685,7 +687,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$state"), F("ready"), errorCounter); success = true; } - +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, strformat(F("C014 : autodiscover information of %d Devices and %d Nodes sent with %s errors! (%d messages)"), @@ -695,6 +697,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& msgCounter) ); } +#endif msgCounter = 0; errorCounter = 0; break; @@ -985,6 +988,14 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& break; } + # if FEATURE_MQTT_CONNECT_BACKGROUND + case CPlugin::Function::CPLUGIN_TEN_PER_SECOND: + { + MQTTConnectInBackground(CONTROLLER_MAX, true); // Report state + break; + } + # endif // if FEATURE_MQTT_CONNECT_BACKGROUND + default: break; } diff --git a/src/_C015.cpp b/src/_C015.cpp index c0d8869e0..c9afedd2f 100644 --- a/src/_C015.cpp +++ b/src/_C015.cpp @@ -193,7 +193,7 @@ bool CPlugin_015(CPlugin::Function function, struct EventStruct *event, String& void *ptr = special_calloc(1, size); if (ptr != nullptr) { - std::unique_ptr element(new (ptr) C015_queue_element(event, valueCount)); + UP_C015_queue_element element(new (ptr) C015_queue_element(event, valueCount)); success = C015_DelayHandler->addToQueue(std::move(element)); } @@ -275,7 +275,7 @@ if (!Settings.ControllerEnabled[element._controller_idx]) { return true; } -if (!NetworkConnected()) { +if (!ESPEasy::net::NetworkConnected()) { return false; } @@ -300,7 +300,7 @@ return element.checkDone(sendSuccess); } boolean Blynk_keep_connection_c015(int controllerIndex, ControllerSettingsStruct& ControllerSettings) { - if (!NetworkConnected()) { + if (!ESPEasy::net::NetworkConnected()) { return false; } diff --git a/src/_C017.cpp b/src/_C017.cpp index be1180826..042725380 100644 --- a/src/_C017.cpp +++ b/src/_C017.cpp @@ -71,7 +71,7 @@ bool CPlugin_017(CPlugin::Function function, struct EventStruct *event, String& void *ptr = special_calloc(1, size); if (ptr != nullptr) { - std::unique_ptr element(new (ptr) C017_queue_element(event)); + UP_C017_queue_element element(new (ptr) C017_queue_element(event)); success = C017_DelayHandler->addToQueue(std::move(element)); } Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_C017_DELAY_QUEUE, C017_DelayHandler->getNextScheduleTime()); diff --git a/src/_C018.cpp b/src/_C018.cpp index cd47ed974..ce4abbbcf 100644 --- a/src/_C018.cpp +++ b/src/_C018.cpp @@ -18,6 +18,7 @@ # include "src/Controller_struct/C018_data_struct.h" # include "src/DataTypes/ESPEasy_plugin_functions.h" # include "src/Globals/CPlugins.h" +# include "src/Helpers/_CPlugin_Helper_LoRa.h" # include "src/Helpers/_Plugin_Helper_serial.h" # include "src/Helpers/StringGenerator_GPIO.h" # include "src/WebServer/Markup.h" @@ -109,18 +110,18 @@ bool CPlugin_018(CPlugin::Function function, struct EventStruct *event, String& protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(event->ControllerIndex); html_add_script(false); addHtml(F("function joinChanged(elem){ var styleOTAA = elem.value == 0 ? '' : 'none'; var styleABP = elem.value == 1 ? '' : 'none';")); - addHtml(c018_add_joinChanged_script_element_line(getControllerParameterInternalName(ProtocolIndex, + LoRa_Helper::add_joinChanged_script_element_line(getControllerParameterInternalName(ProtocolIndex, ControllerSettingsStruct::CONTROLLER_USER), - true)); - addHtml(c018_add_joinChanged_script_element_line(getControllerParameterInternalName(ProtocolIndex, + LoRa_Helper::LoRaWAN_JoinMethod::OTAA); + LoRa_Helper::add_joinChanged_script_element_line(getControllerParameterInternalName(ProtocolIndex, ControllerSettingsStruct::CONTROLLER_PASS), - true)); - addHtml(c018_add_joinChanged_script_element_line(F("deveui"), true)); - addHtml(c018_add_joinChanged_script_element_line(F("deveui_note"), true)); + LoRa_Helper::LoRaWAN_JoinMethod::OTAA); + LoRa_Helper::add_joinChanged_script_element_line(F("deveui"), LoRa_Helper::LoRaWAN_JoinMethod::OTAA); + LoRa_Helper::add_joinChanged_script_element_line(F("deveui_note"), LoRa_Helper::LoRaWAN_JoinMethod::OTAA); - addHtml(c018_add_joinChanged_script_element_line(F("devaddr"), false)); - addHtml(c018_add_joinChanged_script_element_line(F("nskey"), false)); - addHtml(c018_add_joinChanged_script_element_line(F("appskey"), false)); + LoRa_Helper::add_joinChanged_script_element_line(F("devaddr"), LoRa_Helper::LoRaWAN_JoinMethod::ABP); + LoRa_Helper::add_joinChanged_script_element_line(F("nskey"), LoRa_Helper::LoRaWAN_JoinMethod::ABP); + LoRa_Helper::add_joinChanged_script_element_line(F("appskey"), LoRa_Helper::LoRaWAN_JoinMethod::ABP); addHtml('}'); html_add_script_end(); } @@ -134,7 +135,7 @@ bool CPlugin_018(CPlugin::Function function, struct EventStruct *event, String& if (ptr == nullptr) { break; } - std::shared_ptr customConfig(new (ptr) C018_ConfigStruct); + UP_C018_ConfigStruct customConfig(new (ptr) C018_ConfigStruct); if (!customConfig) { break; @@ -153,7 +154,7 @@ bool CPlugin_018(CPlugin::Function function, struct EventStruct *event, String& if (ptr == nullptr) { break; } - std::shared_ptr customConfig(new (ptr) C018_ConfigStruct); + UP_C018_ConfigStruct customConfig(new (ptr) C018_ConfigStruct); if (customConfig) { customConfig->webform_save(); @@ -206,7 +207,7 @@ bool CPlugin_018(CPlugin::Function function, struct EventStruct *event, String& break; } - std::unique_ptr element(new (ptr) C018_queue_element(event, C018_data->getSampleSetCount(event->TaskIndex))); + UP_C018_queue_element element(new (ptr) C018_queue_element(event, C018_data->getSampleSetCount(event->TaskIndex))); success = C018_DelayHandler->addToQueue(std::move(element)); Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_C018_DELAY_QUEUE, C018_DelayHandler->getNextScheduleTime()); @@ -225,8 +226,7 @@ bool CPlugin_018(CPlugin::Function function, struct EventStruct *event, String& case CPlugin::Function::CPLUGIN_PROTOCOL_RECV: { // FIXME TD-er: WHen should this be scheduled? - // protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(event->ControllerIndex); - // schedule_controller_event_timer(ProtocolIndex, CPlugin::Function::CPLUGIN_PROTOCOL_RECV, event); + // schedule_controller_event_timer(event->ControllerIndex, CPlugin::Function::CPLUGIN_PROTOCOL_RECV, event); break; } @@ -330,7 +330,7 @@ bool C018_init(struct EventStruct *event) { if (ptr == nullptr) { return false; } - std::shared_ptr customConfig(new (ptr) C018_ConfigStruct); + UP_C018_ConfigStruct customConfig(new (ptr) C018_ConfigStruct); if (!customConfig) { return false; diff --git a/src/_C023.cpp b/src/_C023.cpp new file mode 100644 index 000000000..d5aca983a --- /dev/null +++ b/src/_C023.cpp @@ -0,0 +1,515 @@ +#include "src/Helpers/_CPlugin_Helper.h" + +#ifdef USES_C023 + +// ####################################################################################################### +// ########################### Controller Plugin 023: AT-command LoRaWAN #################### +// ####################################################################################################### + + +// Mainly tested on Dragino LA66 +// However nearly all LoRaWAN modules supporting AT-commands will very likely work just fine. + +// Useful links: +// Notes for TTN: https://wiki.dragino.com/xwiki/bin/view/Main/Notes%20for%20TTN/#H4.A0ConfigurenodeconnectiontoTTNv3 +// Dragino WiKi LA66 User Manual: +// LA66 LoRaWAN Shield User Manual" +// https://wiki.dragino.com/xwiki/bin/view/Main/User%20Manual%20for%20LoRaWAN%20End%20Nodes/LA66%20LoRaWAN%20Shield%20User%20Manual/ +// +// https://wiki.dragino.com/xwiki/bin/view/Main/User%20Manual%20for%20LoRaWAN%20End%20Nodes/LA66%20LoRaWAN%20Shield%20User%20Manual/#H1.6A0Example:JoinTTNnetworkandsendanuplinkmessage2Cgetdownlinkmessage. +// End Device AT-Commands and Downlink Command +// https://wiki.dragino.com/xwiki/bin/view/Main/End%20Device%20AT%20Commands%20and%20Downlink%20Command/ +// Semtech LoRa Calculator: +// https://www.semtech.com/design-support/lora-calculator +// Examples of AT commands on I-Cube-LRWAN +// https://www.thethingsnetwork.org/forum/uploads/short-url/a5S0svOkbG9bTkvHkcfTNqRDlsq.pdf + +// Dragino LGT92_AT_Commands: https://www.dragino.com/downloads/downloads/LGT_92/DRAGINO_LGT92_AT_Commands_v1.5.3.pdf +// Dragino LT IO Controller AT Command Sets: +// https://www.digikey.nl/htmldatasheets/production/5521267/0/0/1/114992158.pdf + +// eWBM LoRa AT command: https://www.tme.eu/Document/6fd083ba11ae9feedaadc9c7221ab46e/eWBM_LoRa_AT_Command_v0.6.pdf + +// M5Stack LoRaWAN Atom DTU RAK 3172 AT-commands: +// https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/unit/Unit%20LoRaWAN-CN470/AT%20command%20manual.pdf +// https://github.com/m5stack/M5-LoRaWAN-RAK +// RAK RUI3 AT Command Manual +// https://docs.rakwireless.com/product-categories/software-apis-and-libraries/rui3/at-command-manual/ +// RAK AT Command Migration Guide to RUI3 +// https://learn.rakwireless.com/hc/en-us/articles/26687498449559-AT-Command-Migration-Guide-of-RAK3172-to-RUI3-RAKwireless-Unified-Interface-V3?_gl=1*kgdaj4*_gcl_au*MTI0NDgyNDMyMy4xNzY1ODQzMTg4 +// RAK3172 Overlooked but Useful LoRaWAN® AT Commands +// https://news.rakwireless.com/rak3172-overlooked-but-useful-lorawan-at-commands/ + + +# define CPLUGIN_023 +# define CPLUGIN_ID_023 23 +# define CPLUGIN_NAME_023 "AT-command LoRaWAN" + + +# include + +# include "src/ControllerQueue/C023_queue_element.h" +# include "src/Controller_config/C023_config.h" +# include "src/Controller_struct/C023_data_struct.h" +# include "src/DataTypes/ESPEasy_plugin_functions.h" +# include "src/Globals/CPlugins.h" +# include "src/Helpers/_Plugin_Helper_serial.h" +# include "src/Helpers/StringGenerator_GPIO.h" +# include "src/WebServer/Markup.h" +# include "src/WebServer/Markup_Forms.h" +# include "src/WebServer/HTML_wrappers.h" +# include "src/WebServer/KeyValueWriter_WebForm.h" + + +// Have this define after the includes, so we can set it in Custom.h +# ifndef C023_FORCE_SW_SERIAL +# define C023_FORCE_SW_SERIAL false +# endif // ifndef C023_FORCE_SW_SERIAL + + +// FIXME TD-er: Must add a controller data struct vector, like with plugins. +C023_data_struct *C023_data = nullptr; + + +// Forward declarations +bool C023_init(struct EventStruct *event); + +bool CPlugin_023(CPlugin::Function function, struct EventStruct *event, String& string) +{ + bool success = false; + + switch (function) + { + case CPlugin::Function::CPLUGIN_PROTOCOL_ADD: + { + ProtocolStruct& proto = getProtocolStruct(event->idx); // = CPLUGIN_ID_023; + proto.usesMQTT = false; + proto.usesAccount = true; + proto.usesPassword = true; + proto.defaultPort = 1; + proto.usesID = true; + proto.usesHost = false; + proto.usesCheckReply = false; + proto.usesTimeout = false; + proto.usesSampleSets = true; + proto.needsNetwork = false; + break; + } + + case CPlugin::Function::CPLUGIN_GET_DEVICENAME: + { + string = F(CPLUGIN_NAME_023); + break; + } + + case CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG: + { + if ((C023_data != nullptr) && C023_data->isInitialized()) { + string = F("Dev addr: "); + string += C023_data->getDevaddr(); + string += C023_data->useOTAA() ? F(" (OTAA)") : F(" (ABP)"); + } else { + string = F("-"); + } + break; + } + + case CPlugin::Function::CPLUGIN_INIT: + { + success = init_c023_delay_queue(event->ControllerIndex); + + if (success) { + C023_init(event); + } + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + if (C023_data != nullptr) { + C023_data->reset(); + delete C023_data; + C023_data = nullptr; + } + exit_c023_delay_queue(); + break; + } + + case CPlugin::Function::CPLUGIN_WEBFORM_LOAD: + { + { + // Script to toggle visibility of OTAA/ABP field, based on the activation method selector. + protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(event->ControllerIndex); + html_add_script(false); + addHtml(F( + "function joinChanged(elem){ var styleOTAA = elem.value == 0 ? '' : 'none'; var styleABP = elem.value == 1 ? '' : 'none';")); + LoRa_Helper::add_joinChanged_script_element_line(getControllerParameterInternalName(ProtocolIndex, + ControllerSettingsStruct::CONTROLLER_USER), + LoRa_Helper::LoRaWAN_JoinMethod::OTAA); + LoRa_Helper::add_joinChanged_script_element_line(getControllerParameterInternalName(ProtocolIndex, + ControllerSettingsStruct::CONTROLLER_PASS), + LoRa_Helper::LoRaWAN_JoinMethod::OTAA); + LoRa_Helper::add_joinChanged_script_element_line(F("deveui"), LoRa_Helper::LoRaWAN_JoinMethod::OTAA); + LoRa_Helper::add_joinChanged_script_element_line(F("deveui_note"), LoRa_Helper::LoRaWAN_JoinMethod::OTAA); + + LoRa_Helper::add_joinChanged_script_element_line(F("devaddr"), LoRa_Helper::LoRaWAN_JoinMethod::ABP); + LoRa_Helper::add_joinChanged_script_element_line(F("nskey"), LoRa_Helper::LoRaWAN_JoinMethod::ABP); + LoRa_Helper::add_joinChanged_script_element_line(F("appskey"), LoRa_Helper::LoRaWAN_JoinMethod::ABP); + addHtml('}'); + html_add_script_end(); + } + + { + // Keep this object in a small scope so we can destruct it as soon as possible again. + + constexpr unsigned size = sizeof(C023_ConfigStruct); + void *ptr = special_calloc(1, size); + + if (ptr == nullptr) { + break; + } + UP_C023_ConfigStruct customConfig(new (ptr) C023_ConfigStruct); + + if (!customConfig) { + break; + } + LoadCustomControllerSettings(event->ControllerIndex, reinterpret_cast(customConfig.get()), sizeof(C023_ConfigStruct)); + customConfig->webform_load(C023_data); + } + + if (C023_data) + { + KeyValueWriter_WebForm writer(true); + + const __FlashStringHelper *separators[] = { + F("Keys, IDs and EUIs"), + F("Access LoRa Network"), + F("LoRa Network Management"), + F("Information") + }; + + const C023_AT_commands::AT_cmd commands[] = { + C023_AT_commands::AT_cmd::UUID, + C023_AT_commands::AT_cmd::CFM, + C023_AT_commands::AT_cmd::ADR, + C023_AT_commands::AT_cmd::RSSI, + + C023_AT_commands::AT_cmd::Unknown + }; + + for (size_t i = 0; i < NR_ELEMENTS(separators); ++i) { + C023_data->writeCachedValues( + writer.createChild(separators[i]).get(), + commands[i], commands[i + 1]); + } + } + + break; + } + case CPlugin::Function::CPLUGIN_WEBFORM_SAVE: + { + constexpr unsigned size = sizeof(C023_ConfigStruct); + void *ptr = special_calloc(1, size); + + if (ptr == nullptr) { + break; + } + UP_C023_ConfigStruct customConfig(new (ptr) C023_ConfigStruct); + + if (customConfig) { + customConfig->webform_save(); + SaveCustomControllerSettings(event->ControllerIndex, reinterpret_cast(customConfig.get()), + sizeof(C023_ConfigStruct)); + } + break; + } + + case CPlugin::Function::CPLUGIN_GET_PROTOCOL_DISPLAY_NAME: + { + success = true; + + switch (event->idx) + { + case ControllerSettingsStruct::CONTROLLER_USER: + string = F("AppEUI"); + break; + case ControllerSettingsStruct::CONTROLLER_PASS: + string = F("AppKey"); + break; + case ControllerSettingsStruct::CONTROLLER_TIMEOUT: + string = F("Module Timeout"); + break; + case ControllerSettingsStruct::CONTROLLER_PORT: + string = F("Port"); + break; + default: + success = false; + break; + } + break; + } + + case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: + { + if (C023_DelayHandler == nullptr) { + break; + } + + if (C023_DelayHandler->queueFull(event->ControllerIndex)) { + break; + } + + if (C023_data != nullptr) { + { + constexpr unsigned size = sizeof(C023_queue_element); + void *ptr = special_calloc(1, size); + + if (ptr == nullptr) { + break; + } + + UP_C023_queue_element element(new (ptr) C023_queue_element(event, C023_data->getSampleSetCount(event->TaskIndex))); + success = C023_DelayHandler->addToQueue(std::move(element)); + Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_C023_DELAY_QUEUE, + C023_DelayHandler->getNextScheduleTime()); + } + + if (!C023_data->isInitialized()) { + // Sometimes the module does need some time after power on to respond. + // So it may not be initialized well at the call of CPLUGIN_INIT + // We try to trigger its init again when sending data. + C023_init(event); + } + } + break; + } + + case CPlugin::Function::CPLUGIN_PROTOCOL_RECV: + { + // FIXME TD-er: WHen should this be scheduled? + // schedule_controller_event_timer(event->ControllerIndex, CPlugin::Function::CPLUGIN_PROTOCOL_RECV, event); + break; + } + + case CPlugin::Function::CPLUGIN_WRITE: + { + if (C023_data != nullptr) { + if (C023_data->isInitialized()) + { + const String command = parseString(string, 1); + + if (equals(command, F("lorawan"))) { + const String subcommand = parseString(string, 2); + + if (equals(subcommand, F("write"))) { + const String loraWriteCommand = parseStringToEnd(string, 3); + const String res = C023_data->sendRawCommand(loraWriteCommand); + String logstr = F("LoRaWAN cmd: "); + logstr += loraWriteCommand; + logstr += F(" -> "); + logstr += res; + addLog(LOG_LEVEL_INFO, logstr); + SendStatus(event, logstr); + success = true; + } + } + } + } + break; + } + + case CPlugin::Function::CPLUGIN_FIFTY_PER_SECOND: + { + if (C023_data != nullptr) { + C023_data->async_loop(); + } + + // FIXME TD-er: Handle reading error state or return values. + break; + } + + case CPlugin::Function::CPLUGIN_FLUSH: + { + process_c023_delay_queue(); + delay(0); + break; + } + + default: + break; + } + return success; +} + +bool C023_init(struct EventStruct *event) { + String AppEUI; + String AppKey; + taskIndex_t SampleSetInitiator = INVALID_TASK_INDEX; + unsigned int Port = 0; + + // Check if the object is already created. + // If so, delete it to make sure the module is initialized according to the full set parameters. + if (C023_data != nullptr) { + C023_data->reset(); + delete C023_data; + C023_data = nullptr; + } + + { + constexpr unsigned size = sizeof(C023_data_struct); + void *ptr = special_calloc(1, size); + + if (ptr == nullptr) { + return false; + } + + C023_data = new (ptr) C023_data_struct; + + if (C023_data == nullptr) { + return false; + } + } + { + // Allocate ControllerSettings object in a scope, so we can destruct it as soon as possible. + MakeControllerSettings(ControllerSettings); // -V522 + + if (!AllocatedControllerSettings()) { + return false; + } + + LoadControllerSettings(event->ControllerIndex, *ControllerSettings); + C023_DelayHandler->cacheControllerSettings(*ControllerSettings); + AppEUI = getControllerUser(event->ControllerIndex, *ControllerSettings); + AppKey = getControllerPass(event->ControllerIndex, *ControllerSettings); + SampleSetInitiator = ControllerSettings->SampleSetInitiator; + Port = ControllerSettings->Port; + } + + constexpr unsigned size = sizeof(C023_ConfigStruct); + void *ptr = special_calloc(1, size); + + if (ptr == nullptr) { + return false; + } + UP_C023_ConfigStruct customConfig(new (ptr) C023_ConfigStruct); + + if (!customConfig) { + return false; + } + LoadCustomControllerSettings(event->ControllerIndex, reinterpret_cast(customConfig.get()), sizeof(C023_ConfigStruct)); + customConfig->validate(); + + if (!C023_data->init(*customConfig, + SampleSetInitiator)) + { + return false; + } + + // C023_data->setFrequencyPlan(static_cast(customConfig->frequencyplan), customConfig->rx2_freq); + + // if (!C023_data->setSF(customConfig->sf)) { + // return false; + // } + + /* + if (!C023_data->setTTNstack(static_cast(customConfig->stackVersion))) { + return false; + } + */ + + if (customConfig->getJoinMethod() == LoRa_Helper::LoRaWAN_JoinMethod::OTAA) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, strformat( + F("OTAA: AppEUI: %s AppKey: %s DevEUI: %s"), + AppEUI.c_str(), + AppKey.c_str(), + customConfig->DeviceEUI)); + } + + if (!C023_data->initOTAA(AppEUI, AppKey, customConfig->DeviceEUI)) { + return false; + } + } + else { + if (!C023_data->initABP(customConfig->DeviceAddr, customConfig->AppSessionKey, customConfig->NetworkSessionKey)) { + return false; + } + } + + + if (!C023_data->join()) { return false; } + + + if (!C023_data->txUncnf(F("ESPeasy (TTN)"), Port)) { + return false; + } + return true; +} + +// Uncrustify may change this into multi line, which will result in failed builds +// *INDENT-OFF* +bool do_process_c023_delay_queue(cpluginID_t cpluginID, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { + const C023_queue_element& element = static_cast(element_base); +// *INDENT-ON* +uint8_t pl = (element.packed.length() / 2); +float airtime_ms = C023_data->getLoRaAirTime(pl); +bool mustSetDelay = false; +bool success = false; + +if (!C023_data->command_finished()) { + mustSetDelay = true; +} else { + success = C023_data->txHexBytes(element.packed, ControllerSettings.Port); + + if (success) { + if (airtime_ms > 0.0f) { + ADD_TIMER_STAT(C023_AIR_TIME, static_cast(airtime_ms * 1000)); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("LoRaWAN : Payload Length: "); + log += pl + 13; // We have a LoRaWAN header of 13 bytes. + log += F(" Air Time: "); + log += toString(airtime_ms, 3); + log += F(" ms"); + addLogMove(LOG_LEVEL_INFO, log); + } + } + } +} +String error = C023_data->getLastError(); // Clear the error string. + +if (error.indexOf(F("no_free_ch")) != -1) { + mustSetDelay = true; +} + +if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("C023 : Sent: "); + log += element.packed; + log += F(" length: "); + log += String(element.packed.length()); + + if (success) { + log += F(" (success) "); + } + log += error; + addLogMove(LOG_LEVEL_INFO, log); +} + +if (mustSetDelay) { + // Module is still sending, delay for 10x expected air time, which is equivalent of 10% air time duty cycle. + // This can be retried a few times, so at most 10 retries like these are needed to get below 1% air time again. + // Very likely only 2 - 3 of these delays are needed, as we have 8 channels to send from and messages are likely sent in bursts. + C023_DelayHandler->setAdditionalDelay(10 * airtime_ms); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("LoRaWAN : Unable to send. Delay for "); + log += 10 * airtime_ms; + log += F(" ms"); + addLogMove(LOG_LEVEL_INFO, log); + } +} + +return success; +} + +#endif // ifdef USES_C023 diff --git a/src/_P003_Pulse.ino b/src/_P003_Pulse.ino index 43b07e20b..ea0af238e 100644 --- a/src/_P003_Pulse.ino +++ b/src/_P003_Pulse.ino @@ -68,6 +68,7 @@ boolean Plugin_003(uint8_t function, struct EventStruct *event, String& string) dev.PluginStats = true; dev.TaskLogsOwnPeaks = true; dev.CustomVTypeVar = true; + dev.MqttStateClass = true; break; } @@ -167,7 +168,7 @@ boolean Plugin_003(uint8_t function, struct EventStruct *event, String& string) { addFormNumericBox(F("Debounce Time"), F("debounce") , PCONFIG(P003_IDX_DEBOUNCETIME)); - addUnit(F("mSec")); + addUnit(F("ms")); { const uint8_t choice = PCONFIG(P003_IDX_COUNTERTYPE); diff --git a/src/_P004_Dallas.ino b/src/_P004_Dallas.ino index 11c512a26..a2c144e25 100644 --- a/src/_P004_Dallas.ino +++ b/src/_P004_Dallas.ino @@ -24,7 +24,7 @@ # define PLUGIN_004 # define PLUGIN_ID_004 4 -# define PLUGIN_NAME_004 "Environment - 1-Wire Temperature" +# define PLUGIN_NAME_004 "Environment - DS18xxx/MAX31xxx/1-Wire Temperature" # define PLUGIN_VALUENAME1_004 "Temperature" # define P004_ERROR_NAN 0 diff --git a/src/_P006_BMP085.ino b/src/_P006_BMP085.ino index c44177a09..f2a0517c4 100644 --- a/src/_P006_BMP085.ino +++ b/src/_P006_BMP085.ino @@ -104,11 +104,12 @@ boolean Plugin_006(uint8_t function, struct EventStruct *event, String& string) pressure = pressureElevation(pressure, elev); } UserVar.setFloat(event->TaskIndex, 1, pressure); - +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, concat(F("BMP : Temperature: "), formatUserVarNoCheck(event, 0))); addLog(LOG_LEVEL_INFO, concat(F("BMP : Barometric Pressure: "), formatUserVarNoCheck(event, 1))); } +#endif success = true; } } diff --git a/src/_P007_PCF8591.ino b/src/_P007_PCF8591.ino index 4db7cfa8e..a929635c8 100644 --- a/src/_P007_PCF8591.ino +++ b/src/_P007_PCF8591.ino @@ -215,7 +215,7 @@ boolean Plugin_007(uint8_t function, struct EventStruct *event, String& string) { Wire.read(); // Read older value first (stored in chip) UserVar.setFloat(event->TaskIndex, var, Wire.read()); // now read actual value and store into Value var - +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, strformat( F("PCF : Analog port: A%d value %d: %s"), @@ -223,6 +223,7 @@ boolean Plugin_007(uint8_t function, struct EventStruct *event, String& string) var + 1, formatUserVarNoCheck(event, var).c_str())); } +#endif success = true; } } else { diff --git a/src/_P008_RFID.ino b/src/_P008_RFID.ino index 35f06e07c..b24f36c15 100644 --- a/src/_P008_RFID.ino +++ b/src/_P008_RFID.ino @@ -157,7 +157,7 @@ boolean Plugin_008(uint8_t function, struct EventStruct *event, String& string) if (P008_REMOVE_TIMEOUT == 0) { P008_REMOVE_TIMEOUT = 500; } // Default 500 mSec (was hardcoded value) addFormNumericBox(F("Automatic Tag removal after"), F("rmvtime"), P008_REMOVE_TIMEOUT, 250, 60000); // 0.25 to 60 seconds - addUnit(F("mSec.")); + addUnit(F("ms")); // Max allowed is int = 0x7FFFFFFF ... addFormNumericBox(F("Value to set on Tag removal"), F("rmvval"), P008_REMOVE_VALUE, 0); diff --git a/src/_P009_MCP.ino b/src/_P009_MCP.ino index 9e46e207f..1d38ef4f8 100644 --- a/src/_P009_MCP.ino +++ b/src/_P009_MCP.ino @@ -182,10 +182,12 @@ boolean Plugin_009(uint8_t function, struct EventStruct *event, String& string) { // We do not actually read the pin state as this is already done 10x/second // Instead we just send the last known state stored in Uservar +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, strformat(F("MCP : Port=%d State=%d"), CONFIG_PORT, UserVar[event->BaseVarIndex])); } +#endif success = true; break; } diff --git a/src/_P010_BH1750.ino b/src/_P010_BH1750.ino index 352be8341..d92bd99d0 100644 --- a/src/_P010_BH1750.ino +++ b/src/_P010_BH1750.ino @@ -142,13 +142,14 @@ boolean Plugin_010(uint8_t function, struct EventStruct *event, String& string) if (lux != -1.0f) { UserVar.setFloat(event->TaskIndex, 0, lux); - +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, strformat(F("BH1750 Address: 0x%02x Mode: 0x%02x : Light intensity: %s"), PCONFIG(0), PCONFIG(1), formatUserVarNoCheck(event, 0).c_str())); } +#endif success = true; } break; diff --git a/src/_P013_HCSR04.ino b/src/_P013_HCSR04.ino index 2fea00afe..8497b7aa6 100644 --- a/src/_P013_HCSR04.ino +++ b/src/_P013_HCSR04.ino @@ -33,7 +33,7 @@ # include "src/PluginStructs/P013_data_struct.h" // map of sensors -std::map > P_013_sensordefs; +std::map > P_013_sensordefs; // Forward declarations float Plugin_013_read(struct EventStruct *event); @@ -251,7 +251,7 @@ boolean Plugin_013(uint8_t function, struct EventStruct *even // create sensor instance and add to std::map P_013_sensordefs.erase(event->TaskIndex); - P_013_sensordefs[event->TaskIndex] = std::shared_ptr(new NewPing(P013_TRIGGER_PIN, + P_013_sensordefs[event->TaskIndex] = std::unique_ptr(new NewPing(P013_TRIGGER_PIN, P013_ECHO_PIN, max_distance_cm, P013_TRIGGER_WIDTH)); diff --git a/src/_P014_SI70xx.ino b/src/_P014_SI70xx.ino index 86fa1c9f8..099c65cda 100644 --- a/src/_P014_SI70xx.ino +++ b/src/_P014_SI70xx.ino @@ -211,7 +211,7 @@ boolean Plugin_014(uint8_t function, struct EventStruct *event, String& string) if (P014_data->chip_id == CHIP_ID_SI7013) { UserVar.setFloat(event->TaskIndex, 2, (P014_data->adc) >> P014_FILTER_POWER); } - +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { String log = strformat(F("P014: Temperature: %.2f Humidity: %.2f"), UserVar[event->BaseVarIndex + 0], @@ -222,7 +222,7 @@ boolean Plugin_014(uint8_t function, struct EventStruct *event, String& string) } addLog(LOG_LEVEL_INFO, log); } - +#endif P014_data->state = P014_state::Ready; // getting ready for another read cycle success = true; } diff --git a/src/_P016_IR.ino b/src/_P016_IR.ino index 7b7efa233..9e48b0791 100644 --- a/src/_P016_IR.ino +++ b/src/_P016_IR.ino @@ -1046,13 +1046,11 @@ boolean displayRawToReadableB32Hex(String& outputStr, decode_results results) out[iOut] = 0; - outputStr.reserve(32 + iOut); - outputStr += F("IRSEND,RAW2,"); - outputStr += out; - outputStr += F(",38,"); - outputStr += uint64ToString(div[0], 10); - outputStr += ','; - outputStr += uint64ToString(div[1], 10); + outputStr = strformat( + F("IRSEND,RAW2,%s,38,%s,%s"), + out, + uint64ToString(div[0], 10).c_str(), + uint64ToString(div[1], 10).c_str()); addLog(LOG_LEVEL_INFO, outputStr); return true; } diff --git a/src/_P017_PN532.ino b/src/_P017_PN532.ino index e5ee30273..9da5e31b0 100644 --- a/src/_P017_PN532.ino +++ b/src/_P017_PN532.ino @@ -164,7 +164,7 @@ boolean Plugin_017(uint8_t function, struct EventStruct *event, String& string) // 0.25 to 60 seconds addFormNumericBox(F("Automatic Tag removal after"), F("removetime"), P017_REMOVAL_TIMEOUT, 250, 60000); - addUnit(F("mSec.")); + addUnit(F("ms")); addFormNumericBox(F("Value to set on Tag removal"), F("removevalue"), P017_NO_TAG_DETECTED_VALUE, 0, 2147483647); @@ -354,13 +354,14 @@ bool P017_handle_timer_in(struct EventStruct *event) } tempcounter++; - +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, strformat(F("PN532: %s Tag: %d %u"), FsP(new_key ? F("New") : F("Old")), key, tempcounter)); } +#endif if (new_key) { sendData(event); } uint32_t resetTimer = P017_REMOVAL_TIMEOUT; @@ -413,6 +414,7 @@ boolean Plugin_017_Init(int8_t resetPin) uint32_t versiondata = getFirmwareVersion(); if (versiondata) { +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, strformat(F("PN532: Found chip PN5%s FW: %s.%s"), @@ -420,6 +422,7 @@ boolean Plugin_017_Init(int8_t resetPin) formatToHex_no_prefix((versiondata >> 16) & 0xFF, 2).c_str(), formatToHex_no_prefix((versiondata >> 8) & 0xFF, 2).c_str())); } +#endif } else { # ifdef P017_DEBUG_LOGIC_ANALYZER_PIN_INIT diff --git a/src/_P019_PCF8574.ino b/src/_P019_PCF8574.ino index 0bd11882a..f046a426c 100644 --- a/src/_P019_PCF8574.ino +++ b/src/_P019_PCF8574.ino @@ -186,10 +186,12 @@ boolean Plugin_019(uint8_t function, struct EventStruct *event, String& string) { // We do not actually read the pin state as this is already done 10x/second // Instead we just send the last known state stored in Uservar +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, strformat(F("PCF : Port=%d State=%d"), CONFIG_PORT, UserVar[event->BaseVarIndex])); } +#endif success = true; break; } diff --git a/src/_P020_Ser2Net.ino b/src/_P020_Ser2Net.ino index eca59c003..a7b73e0f9 100644 --- a/src/_P020_Ser2Net.ino +++ b/src/_P020_Ser2Net.ino @@ -350,7 +350,9 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) // P044 Settings to convert? if (P020_Emulate_P044 && P020_ConvertP044Settings(event)) { +#ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("P1 : Automatic settings conversion, please save settings manually.")); +#endif bitSet(P020_FLAGS, P020_FLAG_P044_MODE_SAVED); // Set to P044 configuration done on next save } # endif // ifdef USES_P044 diff --git a/src/_P021_Level.ino b/src/_P021_Level.ino index 9d6230a3c..755304562 100644 --- a/src/_P021_Level.ino +++ b/src/_P021_Level.ino @@ -8,19 +8,24 @@ // Original intention to control a level by opening a valve or switching a pump // Extended by timer based state control to support pumps with additional requirements (floor heating ciculation pump) -// Changelog: -// 2025-05-21, tonhuisman: Add support for MQTT Discovery and MQTT Device Class user-configuration -// 2024-12-14, tonhuisman: Move most defines to .h file to avoid compiler warnings, as Arduino doesn't support #ifdef in .ino files -// Format source using Uncrustify -// Remove unneeded includes -// 2024-07-07, flashmark: Reworked to support floor heating pump (added state machine control) -// 2023-03-13, tonhuisman: Add setting to invert the Output state -// 2022-08-22, tonhuisman: Add setting to auto-save a changed setting after x minutes, size optimizations, add PCONFIG defines -// 2021-12-29, tonhuisman: Add setting to enable/disable saving the settings when the Set Level value is changed using the config -// command -// 2021-12-28, tonhuisman: Avoid saving settings if no change on config command https://github.com/letscontrolit/ESPEasy/issues/3477, -// cleanup source, prevent crashing when hysteresis is 0.0, run Uncrustify source formatter, -// apply float/double math compare functions instead of regular comparisons +/** Changelog: + * 2025-11-03 tonhuisman: Unify changelog format + * Use enum class instead of regular enum for compile-time validations + * Replace switch by if where possible/useful for code-size reduction + * Minor code cleanup + * 2025-05-21 tonhuisman: Add support for MQTT Discovery and MQTT Device Class user-configuration + * 2024-12-14 tonhuisman: Move most defines to .h file to avoid compiler warnings, as Arduino doesn't support #ifdef in .ino files + * Format source using Uncrustify + * Remove unneeded includes + * 2024-07-07 flashmark: Reworked to support floor heating pump (added state machine control) + * 2023-03-13 tonhuisman: Add setting to invert the Output state + * 2022-08-22 tonhuisman: Add setting to auto-save a changed setting after x minutes, size optimizations, add PCONFIG defines + * 2021-12-29 tonhuisman: Add setting to enable/disable saving the settings when the Set Level value is changed using the config + * command + * 2021-12-28 tonhuisman: Avoid saving settings if no change on config command https://github.com/letscontrolit/ESPEasy/issues/3477, + * cleanup source, prevent crashing when hysteresis is 0.0, run Uncrustify source formatter, + * apply float/double math compare functions instead of regular comparisons + */ // NOTE: Due to lack of flash memory for most ESP8266 builds the extensions are only available on a limited set of builds. // Code size and functionality is controlled by several build flags: @@ -73,26 +78,6 @@ int seconds2hours(int x) { # endif // ifndef P021_MIN_BUILD_SIZE -// Operation modes for the control algorithm -enum P021_opmode -{ - P021_OPMODE_CLASSIC, // Original, stateless control - P021_OPMODE_OFF, // Output is fully shut down, no forced curculation - P021_OPMODE_STANDBY, // Output is only switched on for forced maintenance runs - P021_OPMODE_ON, // Output is always switched on - P021_OPMODE_TEMP, // Control algorithm based on temperature only - P021_OPMODE_REMOTE // Both temperature and remote command can switch on Output -}; - -// Control state for the control algorithm -enum P021_control_state -{ - P021_STATE_IDLE, // Output is inactive - P021_STATE_ACTIVE, // Output is active due to level control (based on input value) - P021_STATE_EXTEND, // Output is active due to minimum duration - P021_STATE_FORCE // Output is forced active due to maximum inactive duration is exceeded -}; - // Static storage for global state info. Track per ESPeasy plugin instance static bool P021_remote[TASKS_MAX]; // Static storage for remote control. // Static storage for last change timestamp. Not required with extended features disabled @@ -149,19 +134,18 @@ boolean Plugin_021(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_SET_DEFAULTS: { - P021_DONT_ALWAYS_SAVE = 1; // Do not save - P021_GPIO_RELAY = -1; // GPIO for relay output not assigned - P021_CHECK_TASK = -1; // No input source assigned - P021_CHECK_VALUE = -1; // No input source assigned - P021_SETPOINT = 25.0f; // Switch output active above 25 [deg C] - P021_HYSTERESIS = 5.0f; // Switch off hysteresis 5 [deg C] below switch on value - P021_MIN_TIME = 30 * 60; // Once switched on output should be active at least 30 [min] - P021_INTERVAL_TIME = 24 * 60 * 60; // Output shall run after 24 [hour] stand still - P021_FORCE_TIME = 5 * 30; // Forced circulation for 5 [min] - P021_OPMODE = P021_OPMODE_OFF; // Don't touch output unless selected by operator - P021_FLAGS = 0; // Reset all flags - // UserVar.setFloat(event->TaskIndex, P021_VALUE_STATE, (float)P021_STATE_IDLE); - // P021_remote[event->TaskIndex] = 0; // Remote control state is "off" + P021_DONT_ALWAYS_SAVE = 1; // Do not save + P021_GPIO_RELAY = -1; // GPIO for relay output not assigned + P021_CHECK_TASK = -1; // No input source assigned + P021_CHECK_VALUE = -1; // No input source assigned + P021_SETPOINT = 25.0f; // Switch output active above 25 [deg C] + P021_HYSTERESIS = 5.0f; // Switch off hysteresis 5 [deg C] below switch on value + P021_MIN_TIME = 30 * 60; // Once switched on output should be active at least 30 [min] + P021_INTERVAL_TIME = 24 * 60 * 60; // Output shall run after 24 [hour] stand still + P021_FORCE_TIME = 5 * 30; // Forced circulation for 5 [min] + // Don't touch output unless selected by operator + P021_OPMODE = static_cast(P021_opmode::P021_OPMODE_OFF); + P021_FLAGS = 0; // Reset all flags break; } @@ -187,19 +171,19 @@ boolean Plugin_021(uint8_t function, struct EventStruct *event, String& string) # endif // if FEATURE_P021_EXTRAS >= 1 // For debugging purposes the webform provides some internal data - # ifdef PLUGIN_021_DEBUG + # if (FEATURE_P021_EXTRAS >= 1) && defined(PLUGIN_021_DEBUG) { const P021_control_state control_state = (P021_control_state)UserVar.getFloat(event->TaskIndex, P021_VALUE_STATE); // Add some debug information const String outpstring = essentiallyZero(UserVar.getFloat(event->TaskIndex, P021_VALUE_OUTPUT)) ? F("off") : F("on"); addFormNote(strformat(F("State= %s, Output= %s, Remote= %d, Timer= %d sec"), - P021_printControlState(control_state), + FsP(P021_printControlState(static_cast(control_state))), outpstring.c_str(), P021_remote[event->TaskIndex], millis2seconds(timePassedSince(P021_timestamp[event->TaskIndex])))); } - # endif // ifdef PLUGIN_021_DEBUG + # endif // if (FEATURE_P021_EXTRAS >= 1) && defined(PLUGIN_021_DEBUG) const taskIndex_t check_task = P021_CHECK_TASK; // Optimze reference addRowLabel(F("Input Task")); @@ -250,10 +234,6 @@ boolean Plugin_021(uint8_t function, struct EventStruct *event, String& string) // FormSelector with all operation mode options const __FlashStringHelper *options[] = { F("Classic"), F("Off"), F("Standby"), F("On"), F("Local"), F("Remote") }; - /* - const int optionValues[] = - { P021_OPMODE_CLASSIC, P021_OPMODE_OFF, P021_OPMODE_STANDBY, P021_OPMODE_ON, P021_OPMODE_TEMP, P021_OPMODE_REMOTE }; - */ constexpr size_t optionCount = NR_ELEMENTS(options); const FormSelectorOptions selector(optionCount, options /*, optionValues*/); selector.addFormSelector(F("Control mode"), F(P021_GUID_OPMODE), P021_OPMODE); @@ -388,12 +368,12 @@ boolean Plugin_021(uint8_t function, struct EventStruct *event, String& string) // Set extended parameters to backwards compatible values when extension is disabled if (!newExtFunct) { - P021_OPMODE = P021_OPMODE_CLASSIC; // Switch to classic control algorithm - bitWrite(flags, P021_INV_INPUT, false); // Standard input direction - bitWrite(flags, P021_SYM_HYSTERESIS, true); // Symetrical hysteresis - bitWrite(flags, P021_SLOW_EVAL, false); // 10Hz evaluation - bitWrite(flags, P021_STATE_OUTP, false); // Don't provide state as value - // Keep al other extra settings, thay should not affect CLASSIC mode + P021_OPMODE = static_cast(P021_opmode::P021_OPMODE_CLASSIC); // Switch to classic control algorithm + bitWrite(flags, P021_INV_INPUT, false); // Standard input direction + bitWrite(flags, P021_SYM_HYSTERESIS, true); // Symetrical hysteresis + bitWrite(flags, P021_SLOW_EVAL, false); // 10Hz evaluation + bitWrite(flags, P021_STATE_OUTP, false); // Don't provide state as value + // Keep al other extra settings, they should not affect CLASSIC mode } bitWrite(flags, P021_EXT_FUNCT, newExtFunct); @@ -421,7 +401,7 @@ boolean Plugin_021(uint8_t function, struct EventStruct *event, String& string) const bool isRemote = equals(command, F("remote")); if (isRemote) { - P021_remote[event->TaskIndex] = !essentiallyZero(result); + P021_remote[event->TaskIndex] = !essentiallyZero(result); // FIXME result is always 0.0 here!? } # endif // ifndef P021_MIN_BUILD_SIZE @@ -455,7 +435,7 @@ boolean Plugin_021(uint8_t function, struct EventStruct *event, String& string) (UserVar.getUint32(event->TaskIndex, P021_VALUE_AUTOSAVE_TIME) > P021_AUTOSAVE_TIMER))) { UserVar.setUint32(event->TaskIndex, P021_VALUE_AUTOSAVE_TIME, P021_AUTOSAVE_TIMER); // Start timer # ifndef LIMIT_BUILD_SIZE - addLogMove(LOG_LEVEL_INFO, F("LEVEL: Auto-save timer started.")); + addLog(LOG_LEVEL_INFO, F("LEVEL: Auto-save timer started.")); # endif // ifndef LIMIT_BUILD_SIZE } } @@ -490,7 +470,7 @@ boolean Plugin_021(uint8_t function, struct EventStruct *event, String& string) // I am not sure we want to reset the state at every init. // UserVar seems to be persistent - UserVar.setFloat(event->TaskIndex, P021_VALUE_STATE, (float)P021_STATE_IDLE); + UserVar.setFloat(event->TaskIndex, P021_VALUE_STATE, (float)P021_control_state::P021_STATE_IDLE); P021_remote[event->TaskIndex] = false; // Remote control state is "off" # if FEATURE_P021_EXTRAS >= 1 P021_timestamp[event->TaskIndex] = millis(); // Initialize last switching time @@ -506,9 +486,9 @@ boolean Plugin_021(uint8_t function, struct EventStruct *event, String& string) // this case defines code to be executed when the plugin executes an action (command). // Commands can be accessed via rules or via http. // As an example, http://192.168.1.12//control?cmd=dothis - // implies that there exists the comamnd "dothis" + // implies that there exists the command "dothis" // Expected commands: - // * levelcontrol,remote, [on|off] + // * levelcontrol,remote, [1|0] (1=on, 0=off) # if FEATURE_P021_EXTRAS >= 1 @@ -571,14 +551,14 @@ boolean Plugin_021(uint8_t function, struct EventStruct *event, String& string) # ifdef PLUGIN_021_DEBUG const __FlashStringHelper* P021_printControlState(int state) { - switch (state) + switch (static_cast(state)) { - case P021_STATE_IDLE: return F("Idle"); - case P021_STATE_ACTIVE: return F("Active"); - case P021_STATE_EXTEND: return F("Extend"); - case P021_STATE_FORCE: return F("Force"); - default: return F("***ERROR***"); + case P021_control_state::P021_STATE_IDLE: return F("Idle"); + case P021_control_state::P021_STATE_ACTIVE: return F("Active"); + case P021_control_state::P021_STATE_EXTEND: return F("Extend"); + case P021_control_state::P021_STATE_FORCE: return F("Force"); } + return F(""); } # endif // ifdef PLUGIN_021_DEBUG @@ -589,16 +569,16 @@ const __FlashStringHelper* P021_printControlState(int state) # ifdef PLUGIN_021_DEBUG const __FlashStringHelper* P021_printControlMode(int mode) { - switch (mode) + switch (static_cast(mode)) { - case P021_OPMODE_CLASSIC: return F("Classic"); - case P021_OPMODE_OFF: return F("Off"); - case P021_OPMODE_STANDBY: return F("Standby"); - case P021_OPMODE_ON: return F("On"); - case P021_OPMODE_TEMP: return F("Local"); - case P021_OPMODE_REMOTE: return F("Remote"); - default: return F("***ERROR***"); + case P021_opmode::P021_OPMODE_CLASSIC: return F("Classic"); + case P021_opmode::P021_OPMODE_OFF: return F("Off"); + case P021_opmode::P021_OPMODE_STANDBY: return F("Standby"); + case P021_opmode::P021_OPMODE_ON: return F("On"); + case P021_opmode::P021_OPMODE_TEMP: return F("Local"); + case P021_opmode::P021_OPMODE_REMOTE: return F("Remote"); } + return F(""); } # endif // ifdef PLUGIN_021_DEBUG @@ -623,7 +603,7 @@ void P021_check_autosave(struct EventStruct *event) if ((UserVar.getUint32(event->TaskIndex, P021_VALUE_AUTOSAVE_FLAG) != 0) && !essentiallyEqual(P021_SETPOINT, P021_SETP_LAST_STORED)) { # ifndef P021_MIN_BUILD_SIZE - addLogMove(LOG_LEVEL_INFO, F("LEVEL: Auto-saving changed 'Set Level'.")); + addLog(LOG_LEVEL_INFO, F("LEVEL: Auto-saving changed 'Set Level'.")); # endif // ifndef P021_MIN_BUILD_SIZE P021_SETP_LAST_STORED = P021_SETPOINT; SaveSettings(); @@ -666,50 +646,50 @@ void P021_evaluate(struct EventStruct *event) value = UserVar.getFloat(TaskIndex, P021_CHECK_VALUE); } - switch ((P021_opmode)(P021_OPMODE)) + switch (static_cast(P021_OPMODE)) { // Classic (original P021) stateless control using hysteresis only - case P021_OPMODE_CLASSIC: + case P021_opmode::P021_OPMODE_CLASSIC: if (P021_check_on(value, setpoint, hysteresis, invert_input, symetric_hyst, remote_state)) { - new_control_state = P021_STATE_ACTIVE; + new_control_state = P021_control_state::P021_STATE_ACTIVE; } else if (P021_check_off(value, setpoint, hysteresis, invert_input, symetric_hyst, remote_state)) { - new_control_state = P021_STATE_IDLE; + new_control_state = P021_control_state::P021_STATE_IDLE; } break; // Control is switched off completely - case P021_OPMODE_OFF: + case P021_opmode::P021_OPMODE_OFF: - if (old_control_state != P021_STATE_IDLE) + if (old_control_state != P021_control_state::P021_STATE_IDLE) { timestamp = millis(); - new_control_state = P021_STATE_IDLE; + new_control_state = P021_control_state::P021_STATE_IDLE; } break; // Control is always on - case P021_OPMODE_ON: + case P021_opmode::P021_OPMODE_ON: - if (old_control_state == P021_STATE_IDLE) + if (old_control_state == P021_control_state::P021_STATE_IDLE) { timestamp = millis(); } - new_control_state = P021_STATE_ACTIVE; + new_control_state = P021_control_state::P021_STATE_ACTIVE; break; // Control is switched off with maintnenance interval enabled - case P021_OPMODE_STANDBY: + case P021_opmode::P021_OPMODE_STANDBY: - if (old_control_state == P021_STATE_IDLE) // Output was idling + if (old_control_state == P021_control_state::P021_STATE_IDLE) // Output was idling { if ((beyond_interval) && (P021_FORCE_TIME > 1)) { timestamp = millis(); - new_control_state = P021_STATE_FORCE; + new_control_state = P021_control_state::P021_STATE_FORCE; } } @@ -717,50 +697,50 @@ void P021_evaluate(struct EventStruct *event) else if (!beyond_force) { // Output was active shorter than the forced on time - new_control_state = P021_STATE_FORCE; // Keep running in state FORCE + new_control_state = P021_control_state::P021_STATE_FORCE; // Keep running in state FORCE } else { timestamp = millis(); - new_control_state = P021_STATE_IDLE; // Switch off + new_control_state = P021_control_state::P021_STATE_IDLE; // Switch off } break; // Control based on temperature and optional remote control - case P021_OPMODE_TEMP: + case P021_opmode::P021_OPMODE_TEMP: remote_state = false; // Don't look at requests from remote systems // Continue with shared handling, break deliberately not used // ---------------------------------------------------------- - case P021_OPMODE_REMOTE: + case P021_opmode::P021_OPMODE_REMOTE: switch (old_control_state) { // Output is inactive - case P021_STATE_IDLE: + case P021_control_state::P021_STATE_IDLE: if (P021_check_on(value, setpoint, hysteresis, invert_input, symetric_hyst, remote_state)) { // Setpoint comparator or remote request to activate the output timestamp = millis(); - new_control_state = P021_STATE_ACTIVE; + new_control_state = P021_control_state::P021_STATE_ACTIVE; } else if (beyond_interval) { // Inactive for a long period, forced maintenance run timestamp = millis(); - new_control_state = P021_STATE_FORCE; + new_control_state = P021_control_state::P021_STATE_FORCE; } break; // Output is active due to setpoint comparator request - case P021_STATE_ACTIVE: + case P021_control_state::P021_STATE_ACTIVE: if (P021_check_off(value, setpoint, hysteresis, invert_input, symetric_hyst, remote_state)) { if ((beyond_min_time) && (!extend_at_end)) { timestamp = millis(); - new_control_state = P021_STATE_IDLE; + new_control_state = P021_control_state::P021_STATE_IDLE; } else { @@ -769,67 +749,47 @@ void P021_evaluate(struct EventStruct *event) { timestamp = millis(); } - new_control_state = P021_STATE_EXTEND; + new_control_state = P021_control_state::P021_STATE_EXTEND; } } break; // Output is active to extend a started cycle untill minimum time exceeded - case P021_STATE_EXTEND: + case P021_control_state::P021_STATE_EXTEND: if (P021_check_on(value, setpoint, hysteresis, invert_input, symetric_hyst, remote_state)) { - new_control_state = P021_STATE_ACTIVE; + new_control_state = P021_control_state::P021_STATE_ACTIVE; } else if (beyond_min_time) { timestamp = millis(); - new_control_state = P021_STATE_IDLE; + new_control_state = P021_control_state::P021_STATE_IDLE; } break; // Output was forced active when operation mode switched to temperature control - case P021_STATE_FORCE: + case P021_control_state::P021_STATE_FORCE: if (P021_check_on(value, setpoint, hysteresis, invert_input, symetric_hyst, remote_state)) { // Keep timestamp from moment pump was swiched on - new_control_state = P021_STATE_ACTIVE; + new_control_state = P021_control_state::P021_STATE_ACTIVE; } else if (beyond_force) { timestamp = millis(); - new_control_state = P021_STATE_IDLE; + new_control_state = P021_control_state::P021_STATE_IDLE; } break; } break; // switch(opmode) case P021_OPMODE_TEMP, P021_OPMODE_REMOTE - - // Unexpected opmode, force to OPMODE_OFF - default: - P021_OPMODE = P021_OPMODE_OFF; - break; } // switch P021_OPMODE // Calculate output state from the newly calculated control state - switch (new_control_state) - { - case P021_STATE_IDLE: - relay_output = false; // Relay output state - break; - case P021_STATE_ACTIVE: - relay_output = true; // Relay output state - break; - case P021_STATE_EXTEND: - relay_output = true; // Relay output state - break; - case P021_STATE_FORCE: - relay_output = true; // Relay output state - break; - default: // unexpected state, switch pump off - relay_output = false; // Relay output state - break; - } + relay_output = (P021_control_state::P021_STATE_ACTIVE == new_control_state || + P021_control_state::P021_STATE_EXTEND == new_control_state || + P021_control_state::P021_STATE_FORCE == new_control_state); relay_output ^= bitRead(P021_FLAGS, P021_INV_OUTPUT); // Invert when selected @@ -845,10 +805,10 @@ void P021_evaluate(struct EventStruct *event) { addLogMove(LOG_LEVEL_DEBUG, strformat(F("P021: Calculated State= %s; GPIO= %d; timer= %d sec; mode= %s; value= %f; remote= %d"), - P021_printControlState(new_control_state), + FsP(P021_printControlState(static_cast(new_control_state))), relay_output, millis2seconds(timePassedSince(timestamp)), - P021_printControlMode((P021_opmode)P021_OPMODE), + P021_printControlMode(P021_OPMODE), value, remote_state)); } @@ -859,7 +819,6 @@ void P021_evaluate(struct EventStruct *event) // - The state of the internal state machine // - Timestamp for last change of the output signal // Note: the actual state of the output is not stored, it can be calculated from the control_state - // UserVar.setFloat(event->TaskIndex, P021_VALUE_OUTPUT, (float)(((P021_control_state)new_control_state == P021_STATE_IDLE) ? 0 : 1)); UserVar.setFloat(event->TaskIndex, P021_VALUE_OUTPUT, relay_output ? 1.0f : 0.0f); UserVar.setFloat(event->TaskIndex, P021_VALUE_STATE, (float)new_control_state); P021_timestamp[event->TaskIndex] = timestamp; @@ -900,29 +859,18 @@ void P021_evaluate(struct EventStruct *event) // All new goodies to default (backwards compatible with previous versions) if (P021_check_on(value, setpoint, hysteresis, false, true, false)) { - new_control_state = P021_STATE_ACTIVE; + new_control_state = P021_control_state::P021_STATE_ACTIVE; } else if (P021_check_off(value, setpoint, hysteresis, false, true, false)) { - new_control_state = P021_STATE_IDLE; + new_control_state = P021_control_state::P021_STATE_IDLE; } // Calculate output state from the newly calculated control state - switch (new_control_state) - { - case P021_STATE_IDLE: - relay_output = false; // Relay output state - break; - case P021_STATE_ACTIVE: - relay_output = true; // Relay output state - break; - default: // unexpected state, switch pump off - relay_output = false; // Relay output state - break; - } + relay_output = P021_control_state::P021_STATE_ACTIVE == new_control_state; // Simplified // Actuate the output pin taking output invert flag into account - relay_output ^= bitRead(P021_FLAGS, P021_INV_OUTPUT); // Invert when selected + relay_output ^= bitRead(P021_FLAGS, P021_INV_OUTPUT); // Invert when selected if (validGpio(P021_GPIO_RELAY)) { @@ -935,7 +883,7 @@ void P021_evaluate(struct EventStruct *event) { addLogMove(LOG_LEVEL_DEBUG, strformat(F("P021: Calculated State= %s; GPIO= %d; value= %f"), - P021_printControlState(new_control_state), + FsP(P021_printControlState(static_cast(new_control_state))), relay_output, value)); } diff --git a/src/_P022_PCA9685.ino b/src/_P022_PCA9685.ino index 206e16e34..c64c29fb7 100644 --- a/src/_P022_PCA9685.ino +++ b/src/_P022_PCA9685.ino @@ -335,10 +335,11 @@ boolean Plugin_022(uint8_t function, struct EventStruct *event, String& string) P022_data->Plugin_022_Frequency(address, freq); } P022_data->Plugin_022_writeRegister(address, PCA9685_MODE2, event->Par1); - +#ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, strformat(F("%s%s"), P022_data_struct::P022_logPrefix(address, F("MODE2 0x")).c_str(), formatToHex(event->Par1, 2).c_str())); +#endif } else { addLog(LOG_LEVEL_ERROR, diff --git a/src/_P024_MLX90614.ino b/src/_P024_MLX90614.ino index 4f098b5f2..2bc11b99b 100644 --- a/src/_P024_MLX90614.ino +++ b/src/_P024_MLX90614.ino @@ -119,10 +119,11 @@ boolean Plugin_024(uint8_t function, struct EventStruct *event, String& string) if (nullptr != P024_data) { UserVar.setFloat(event->TaskIndex, 0, P024_data->readTemperature(PCONFIG(0))); - +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, concat(F("MLX90614 : Temperature: "), formatUserVarNoCheck(event, 0))); } +#endif // send(msgObjTemp024->set(UserVar[event->BaseVarIndex], 1)); // Mysensors success = true; diff --git a/src/_P026_Sysinfo.ino b/src/_P026_Sysinfo.ino index 6c17fbe5e..4bec83fb2 100644 --- a/src/_P026_Sysinfo.ino +++ b/src/_P026_Sysinfo.ino @@ -45,6 +45,7 @@ boolean Plugin_026(uint8_t function, struct EventStruct *event, String& string) dev.PluginStats = true; dev.CustomVTypeVar = true; dev.MqttStateClass = true; + dev.NoDeviceSettings = true; break; } diff --git a/src/_P028_BME280.ino b/src/_P028_BME280.ino index e9de7c543..6767a3ae5 100644 --- a/src/_P028_BME280.ino +++ b/src/_P028_BME280.ino @@ -24,7 +24,6 @@ # define PLUGIN_VALUENAME2_028 "Humidity" # define PLUGIN_VALUENAME3_028 "Pressure" - boolean Plugin_028(uint8_t function, struct EventStruct *event, String& string) { boolean success = false; @@ -67,7 +66,8 @@ boolean Plugin_028(uint8_t function, struct EventStruct *event, String& string) ExtraTaskSettings.setAllowedRange(1, 0.0f, 100.0f); // Humidity min/max ExtraTaskSettings.setAllowedRange(2, 300.0f, 1100.0f); // Barometric Pressure min/max - switch (P028_ERROR_STATE_OUTPUT) { // Only temperature error is configurable + switch (P028_ERROR_STATE_OUTPUT) // Only temperature error is configurable + { case P028_ERROR_IGNORE: ExtraTaskSettings.setIgnoreRangeCheck(0); break; @@ -192,7 +192,7 @@ boolean Plugin_028(uint8_t function, struct EventStruct *event, String& string) static_cast(P028_data_struct::BMx_DetectMode::BMP280), }; const FormSelectorOptions selector(NR_ELEMENTS(detectOptionList), detectOptionList, detectOptions); - selector.addFormSelector(F("Output values mode"), F("det"), P028_DETECTION_MODE); + selector.addFormSelector(F("Output values mode"), F("det"), P028_DETECTION_MODE); success = true; } @@ -210,14 +210,15 @@ boolean Plugin_028(uint8_t function, struct EventStruct *event, String& string) if ((P028_data_struct::BMx_DetectMode::BMP280 != static_cast(P028_DETECTION_MODE)) && P028_data->hasHumidity()) { + addRowColspan(2); P028_data->plot_ChartJS_scatter( 0, 1, F("temphumscatter"), { F("Temp/Humidity Scatter Plot") }, - { F("temp/hum"), F("rgb(255, 99, 132)") }, - 500, - 500); + { F("temp/hum"), F("rgb(255, 99, 132)") } + ); + addHtml(F("")); } } @@ -255,12 +256,12 @@ boolean Plugin_028(uint8_t function, struct EventStruct *event, String& string) # endif // ifndef LIMIT_BUILD_SIZE }; constexpr int P028_ERROR_STATE_COUNT = NR_ELEMENTS(resultsOptions); - const FormSelectorOptions selector( + const FormSelectorOptions selector( P028_ERROR_STATE_COUNT, resultsOptions, resultsOptionValues); selector.addFormSelector(F("Temperature Error Value"), F("err"), - P028_ERROR_STATE_OUTPUT); + P028_ERROR_STATE_OUTPUT); break; } diff --git a/src/_P032_MS5611.ino b/src/_P032_MS5611.ino index b0f289d66..680117f9c 100644 --- a/src/_P032_MS5611.ino +++ b/src/_P032_MS5611.ino @@ -122,13 +122,14 @@ boolean Plugin_032(uint8_t function, struct EventStruct *event, String& string) } else { UserVar.setFloat(event->TaskIndex, 1, P032_data->ms5611_pressure); } - +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, concat(F("MS5611 : Temperature: "), formatUserVarNoCheck(event, 0))); addLog(LOG_LEVEL_INFO, concat(F("MS5611 : Barometric Pressure: "), formatUserVarNoCheck(event, 1))); } +#endif success = true; } } diff --git a/src/_P033_Dummy.ino b/src/_P033_Dummy.ino index 79fc3336b..b9eb77986 100644 --- a/src/_P033_Dummy.ino +++ b/src/_P033_Dummy.ino @@ -6,6 +6,7 @@ // ####################################################################################################### /** Changelog: + * 2025-09-11 tonhuisman: Enable Formula field * 2025-06-14 tonhuisman: Add support for Custom Value Type per task value * 2025-01-12 tonhuisman: Add support for MQTT AutoDiscovery (not supported yet for Dummy Device) */ @@ -14,6 +15,7 @@ # define PLUGIN_ID_033 33 # define PLUGIN_NAME_033 "Generic - Dummy Device" # define PLUGIN_VALUENAME1_033 "Dummy" + boolean Plugin_033(uint8_t function, struct EventStruct *event, String& string) { boolean success = false; @@ -26,7 +28,7 @@ boolean Plugin_033(uint8_t function, struct EventStruct *event, String& string) dev.Number = PLUGIN_ID_033; dev.Type = DEVICE_TYPE_DUMMY; dev.VType = Sensor_VType::SENSOR_TYPE_SINGLE; - dev.DecimalsOnly = true; + dev.FormulaOption = true; dev.ValueCount = 4; dev.SendDataOption = true; dev.TimerOption = true; @@ -35,6 +37,7 @@ boolean Plugin_033(uint8_t function, struct EventStruct *event, String& string) dev.PluginStats = true; dev.CustomVTypeVar = true; dev.MqttStateClass = true; + dev.NoDeviceSettings = true; break; } diff --git a/src/_P036_FrameOLED.ino b/src/_P036_FrameOLED.ino index f1b2f361f..8eaa4dbc1 100644 --- a/src/_P036_FrameOLED.ino +++ b/src/_P036_FrameOLED.ino @@ -223,7 +223,7 @@ # ifdef P036_CHECK_HEAP # include "src/Helpers/Memory.h" # endif // ifdef P036_CHECK_HEAP -# include "src/ESPEasyCore/ESPEasyNetwork.h" +# include "ESPEasy/net/ESPEasyNetwork.h" # define PLUGIN_036 # define PLUGIN_ID_036 36 @@ -968,7 +968,7 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) if (P036_DisplayIsOn) { // Display is on. - if (!P036_data->bRunning && NetworkConnected() && (P036_data->ScrollingPages.Scrolling == 0)) { + if (!P036_data->bRunning && ESPEasy::net::NetworkConnected() && (P036_data->ScrollingPages.Scrolling == 0)) { // start page updates after network has connected P036_data->P036_DisplayPage(event); } diff --git a/src/_P043_ClkOutput.ino b/src/_P043_ClkOutput.ino index 589e57557..c9c60b330 100644 --- a/src/_P043_ClkOutput.ino +++ b/src/_P043_ClkOutput.ino @@ -270,10 +270,11 @@ boolean Plugin_043(uint8_t function, struct EventStruct *event, String& string) UserVar.setFloat(event->TaskIndex, 0, x + 1); UserVar.setFloat(event->TaskIndex, 1, state); } - +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, strformat(F("TCLK : State %d"), state)); } +#endif sendData(event); } } diff --git a/src/_P048_Motorshield_v2.ino b/src/_P048_Motorshield_v2.ino index 666c545d4..749d8649b 100644 --- a/src/_P048_Motorshield_v2.ino +++ b/src/_P048_Motorshield_v2.ino @@ -154,10 +154,11 @@ boolean Plugin_048(uint8_t function, struct EventStruct *event, String& string) speed = p4_int; } AFMS.begin(); - +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, strformat(F("DCMotor%s->Forward Speed: %d"), param2.c_str(), speed)); } +#endif myMotor->setSpeed(speed); myMotor->run(FORWARD); success = true; @@ -171,11 +172,11 @@ boolean Plugin_048(uint8_t function, struct EventStruct *event, String& string) speed = p4_int; } AFMS.begin(); - +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, strformat(F("DCMotor%s->Backward Speed: %d"), param2.c_str(), speed)); } - +#endif myMotor->setSpeed(speed); myMotor->run(BACKWARD); success = true; @@ -184,10 +185,11 @@ boolean Plugin_048(uint8_t function, struct EventStruct *event, String& string) if (equals(param3, F("release"))) { AFMS.begin(); - +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, strformat(F("DCMotor%s->Release"), param2.c_str())); } +#endif myMotor->run(RELEASE); success = true; } @@ -247,10 +249,11 @@ boolean Plugin_048(uint8_t function, struct EventStruct *event, String& string) myStepper->step(steps, FORWARD, MICROSTEP); success = true; } - +#ifndef BUILD_NO_DEBUG if (success && loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, strformat(F("Stepper%s->Forward Steps: %d %s"), param2.c_str(), steps, param5.c_str())); } +#endif } } @@ -287,20 +290,22 @@ boolean Plugin_048(uint8_t function, struct EventStruct *event, String& string) myStepper->step(steps, BACKWARD, MICROSTEP); success = true; } - +#ifndef BUILD_NO_DEBUG if (success && loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, strformat(F("Stepper%s->Backward Steps: %d %s"), param2.c_str(), steps, param5.c_str())); } +#endif } } if (equals(param3, F("release"))) { AFMS.begin(); - +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, strformat(F("Stepper%s->Release."), param2.c_str())); } +#endif myStepper->release(); success = true; } diff --git a/src/_P049_MHZ19.ino b/src/_P049_MHZ19.ino index e3345a84d..ff480c9a5 100644 --- a/src/_P049_MHZ19.ino +++ b/src/_P049_MHZ19.ino @@ -234,23 +234,26 @@ boolean Plugin_049(uint8_t function, struct EventStruct *event, String& string) P049_data->ABC_MustApply = false; } } - +#ifndef BUILD_NO_DEBUG if (mustLog) { // Log values in all cases addLog(LOG_LEVEL_INFO, strformat(F("PPM value: %d Temp/S/U values: %d/%d/%.2f"), ppm, temp, s, u)); } +#endif break; // #ifdef ENABLE_DETECTION_RANGE_COMMANDS // Sensor responds with 0x99 whenever we send it a measurement range adjustment } else if (P049_data->receivedCommandAcknowledgement(expectReset)) { +#ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("MHZ19: Received command acknowledgment! ")); if (expectReset) { addLog(LOG_LEVEL_INFO, F("Expecting sensor reset...")); } +#endif success = false; break; @@ -258,10 +261,12 @@ boolean Plugin_049(uint8_t function, struct EventStruct *event, String& string) // log verbosely anything else that the sensor reports } else { +#ifndef BUILD_NO_DEBUG if (mustLog) { addLog(LOG_LEVEL_INFO, concat(F("MHZ19: Unknown response:"), P049_data->getBufferHexDump())); } +#endif // Check for stable reads and allow unstable reads the first 3 minutes after reset. if ((P049_data->nrUnknownResponses > 10) && P049_data->initTimePassed) { diff --git a/src/_P056_SDS011-Dust.ino b/src/_P056_SDS011-Dust.ino index cbf781540..be226b179 100644 --- a/src/_P056_SDS011-Dust.ino +++ b/src/_P056_SDS011-Dust.ino @@ -220,9 +220,11 @@ void Plugin_056_setWorkingPeriod(EventStruct *event, int minutes) { } P056_data->SetWorkingPeriod(minutes); +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, concat(F("SDS : Working Period set to: "), Plugin_056_WorkingPeriodToString(minutes))); } +#endif } // #endif diff --git a/src/_P059_Encoder.ino b/src/_P059_Encoder.ino index bede9ca18..363030e38 100644 --- a/src/_P059_Encoder.ino +++ b/src/_P059_Encoder.ino @@ -30,7 +30,7 @@ # include -std::map > P_059_sensordefs; +std::map > P_059_sensordefs; boolean Plugin_059(uint8_t function, struct EventStruct *event, String& string) { @@ -127,7 +127,7 @@ boolean Plugin_059(uint8_t function, struct EventStruct *event, String& string) // create sensor instance and add to std::map P_059_sensordefs.erase(event->TaskIndex); - P_059_sensordefs[event->TaskIndex] = std::shared_ptr(new QEIx4); + P_059_sensordefs[event->TaskIndex] = std::unique_ptr(new QEIx4); P_059_sensordefs[event->TaskIndex]->begin(CONFIG_PIN1, CONFIG_PIN2, CONFIG_PIN3, PCONFIG(0)); P_059_sensordefs[event->TaskIndex]->setLimit(PCONFIG_LONG(0), PCONFIG_LONG(1)); @@ -180,11 +180,11 @@ boolean Plugin_059(uint8_t function, struct EventStruct *event, String& string) { const long c = P_059_sensordefs[event->TaskIndex]->read(); UserVar.setFloat(event->TaskIndex, 0, c); - +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, concat(F("QEI : "), c)); } - +#endif sendData(event); } } @@ -212,9 +212,11 @@ boolean Plugin_059(uint8_t function, struct EventStruct *event, String& string) { if (event->Par1 >= 0) { +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, concat(F("QEI : "), string)); } +#endif P_059_sensordefs[event->TaskIndex]->write(event->Par1); Scheduler.schedule_task_device_timer(event->TaskIndex, millis()); } diff --git a/src/_P062_MPR121_KeyPad.ino b/src/_P062_MPR121_KeyPad.ino index 48be1a9d7..e4b1acbc8 100644 --- a/src/_P062_MPR121_KeyPad.ino +++ b/src/_P062_MPR121_KeyPad.ino @@ -359,10 +359,11 @@ boolean Plugin_062(uint8_t function, struct EventStruct *event, String& string) uint16_t min = 0; uint16_t max = 0; P062_data->getCalibrationData(col, ¤t, &min, &max); +#ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, strformat(F("P062 touch #%d current: %d min: %d max: %d"), col, current, min, max)); - +#endif if (!PCONFIG(1)) { break; } diff --git a/src/_P063_TTP229_KeyPad.ino b/src/_P063_TTP229_KeyPad.ino index 8bf292722..39d992135 100644 --- a/src/_P063_TTP229_KeyPad.ino +++ b/src/_P063_TTP229_KeyPad.ino @@ -148,10 +148,11 @@ boolean Plugin_063(uint8_t function, struct EventStruct *event, String& string) int16_t pinSCL = CONFIG_PIN1; int16_t pinSDO = CONFIG_PIN2; - +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, strformat(F("Tkey : GPIO: %d %d"), pinSCL, pinSDO)); } +#endif if (validGpio(pinSCL) && validGpio(pinSDO)) { diff --git a/src/_P069_LM75A.ino b/src/_P069_LM75A.ino index ec5b78801..18f786cd2 100644 --- a/src/_P069_LM75A.ino +++ b/src/_P069_LM75A.ino @@ -119,7 +119,7 @@ boolean Plugin_069(uint8_t function, struct EventStruct *event, String& string) const float tempC = P069_data->getTemperatureInDegrees(); UserVar.setFloat(event->TaskIndex, 0, tempC); success = !isnan(tempC); - +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { if (!success) { addLog(LOG_LEVEL_INFO, F("LM75A: No reading!")); @@ -129,6 +129,7 @@ boolean Plugin_069(uint8_t function, struct EventStruct *event, String& string) addLogMove(LOG_LEVEL_INFO, concat(F("LM75A: Temperature: "), formatUserVarNoCheck(event, 0))); } } +#endif break; } } diff --git a/src/_P075_Nextion.ino b/src/_P075_Nextion.ino index 62bdb6ef8..d3ed47997 100644 --- a/src/_P075_Nextion.ino +++ b/src/_P075_Nextion.ino @@ -3,7 +3,7 @@ # include "src/PluginStructs/P075_data_struct.h" -# include "src/ESPEasyCore/ESPEasyWifi.h" +# include "ESPEasy/net/wifi/ESPEasyWifi.h" // ####################################################################################################### // ####################################################################################################### @@ -215,7 +215,7 @@ boolean Plugin_075(uint8_t function, struct EventStruct *event, String& string) if (RssiIndex >= 0) { newString = concat( P075_data->displayLines[x].substring(0, RssiIndex), - GetRSSI_quality() * 10); + ESPEasy::net::wifi::GetRSSI_quality() * 10); } else { String tmpString(P075_data->displayLines[x]); @@ -296,7 +296,7 @@ boolean Plugin_075(uint8_t function, struct EventStruct *event, String& string) } if (!validGpio(P075_data->rxPin)) { - addLog(LOG_LEVEL_INFO, F("NEXTION075 : Missing RxD Pin, aborted serial receive")); +// addLog(LOG_LEVEL_INFO, F("NEXTION075 : Missing RxD Pin, aborted serial receive")); break; } @@ -469,9 +469,11 @@ void P075_sendCommand(taskIndex_t taskIndex, const char *cmd) P075_data->easySerial->write(0xff); P075_data->easySerial->write(0xff); } +#ifndef BUILD_NO_DEBUG else { addLog(LOG_LEVEL_INFO, F("NEXTION075 : P075_data->easySerial error, aborted sendCommand")); } +#endif } } diff --git a/src/_P078_Eastron.ino b/src/_P078_Eastron.ino index daa37dce1..7bf796310 100644 --- a/src/_P078_Eastron.ino +++ b/src/_P078_Eastron.ino @@ -13,6 +13,7 @@ */ /** Changelog: + * 2025-10-25 tonhuisman: Add custom Unit of Measure group per value to limit selection to useful options * 2025-08-02 repa6: Add partial support for TAC2100 meter * 2025-01-17 tonhuisman: Implement support for MQTT AutoDiscovery (partial) * 2025-01-12 tonhuisman: Add support for MQTT AutoDiscovery (not supported yet for Eastron) @@ -103,6 +104,20 @@ boolean Plugin_078(uint8_t function, struct EventStruct *event, String& string) } # endif // if FEATURE_MQTT_DISCOVER + # if FEATURE_TASKVALUE_UNIT_OF_MEASURE + case PLUGIN_GET_UOM_GROUPS: + { + const SDM_MODEL model = static_cast(P078_MODEL); + + for (uint8_t i = 0; i < P078_NR_OUTPUT_VALUES; ++i) { + const uint8_t choice = PCONFIG(i + P078_QUERY1_CONFIG_POS); + event->Par64N[i] = Plugin_078_QueryUOMGroup(model, choice); + } + success = true; + break; + } + # endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE + case PLUGIN_WEBFORM_SHOW_CONFIG: { string += serialHelper_getSerialTypeLabel(event); diff --git a/src/_P079_Wemos_Motorshield.ino b/src/_P079_Wemos_Motorshield.ino index db6c4f66d..b1cbc27a0 100644 --- a/src/_P079_Wemos_Motorshield.ino +++ b/src/_P079_Wemos_Motorshield.ino @@ -387,14 +387,14 @@ boolean Plugin_079(uint8_t function, struct EventStruct *event, String& string) } } } - +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, strformat(F("%s: Addr=0x%02x: Mtr=%s, Dir=%s, Spd=%s"), ModeStr.c_str(), I2C_ADDR_PCFG_P079, paramMotor.c_str(), paramDirection.c_str(), paramSpeed.c_str())); } - +#endif success = true; } break; diff --git a/src/_P087_SerialProxy.ino b/src/_P087_SerialProxy.ino index 2f93bf47a..34ce938f5 100644 --- a/src/_P087_SerialProxy.ino +++ b/src/_P087_SerialProxy.ino @@ -11,6 +11,8 @@ /** * Changelog: + * 2025-12-22 tonhuisman: Add support for receiving binary data, fixed length receive and sending events with hex data + * Moved most #define variables to P087_data_struct.h * 2025-01-12 tonhuisman: Add support for MQTT AutoDiscovery (not supported for Serial Proxy) * 2024-02-27 tonhuisman: Always process the regular expression like 'Global Match' to enable retrieving the available values * 2024-02-26 tonhuisman: Apply log-string and other code optimizations @@ -38,19 +40,6 @@ # define PLUGIN_NAME_087 "Communication - Serial Proxy" -# define P087_BAUDRATE PCONFIG_LONG(0) -# define P087_BAUDRATE_LABEL PCONFIG_LABEL(0) -# define P087_SERIAL_CONFIG PCONFIG_LONG(1) - -# define P087_QUERY_VALUE 0 // Temp placement holder until we know what selectors are needed. -# define P087_NR_OUTPUT_OPTIONS 1 - -# define P087_NR_OUTPUT_VALUES 1 -# define P087_QUERY1_CONFIG_POS 3 - -# define P087_DEFAULT_BAUDRATE 38400 - - // Plugin settings: // Validate: // - [0..9] @@ -168,6 +157,14 @@ boolean Plugin_087(uint8_t function, struct EventStruct *event, String& string) addFormSubHeader(F("Statistics")); P087_html_show_stats(event); + # ifndef LIMIT_BUILD_SIZE + addFormSubHeader(F("Data options")); + addFormCheckBox(F("Receive binary data"), P087_READ_BIN_LABEL, P087_CONFIG_GET_READ_BIN); + addFormNumericBox(F("Fixed length input data"), P087_FIXED_LENGTH_LABEL, P087_CONFIG_GET_FIXED_LENGTH, 0, 255); + addUnit(F("0 = off, max. 255")); + addFormCheckBox(F("Event with hex. data (no prefix)"), P087_EVENT_HEX_LABEL, P087_CONFIG_GET_EVENT_HEX); + # endif // ifndef LIMIT_BUILD_SIZE + success = true; break; } @@ -175,6 +172,11 @@ boolean Plugin_087(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_SAVE: { P087_BAUDRATE = getFormItemInt(P087_BAUDRATE_LABEL); P087_SERIAL_CONFIG = serialHelper_serialconfig_webformSave(); + # ifndef LIMIT_BUILD_SIZE + P087_CONFIG_SET_READ_BIN(isFormItemChecked(P087_READ_BIN_LABEL)); + P087_CONFIG_SET_FIXED_LENGTH(getFormItemInt(P087_FIXED_LENGTH_LABEL)); + P087_CONFIG_SET_EVENT_HEX(isFormItemChecked(P087_EVENT_HEX_LABEL)); + # endif // ifndef LIMIT_BUILD_SIZE P087_data_struct *P087_data = static_cast(getPluginTaskData(event->TaskIndex)); @@ -207,6 +209,15 @@ boolean Plugin_087(uint8_t function, struct EventStruct *event, String& string) if (P087_data->init(port, serial_rx, serial_tx, P087_BAUDRATE, static_cast(P087_SERIAL_CONFIG))) { LoadCustomTaskSettings(event->TaskIndex, P087_data->_lines, P87_Nlines, 0); P087_data->post_init(); + # ifndef LIMIT_BUILD_SIZE + P087_data->setHandleBinary(P087_CONFIG_GET_READ_BIN); + P087_data->setEventAsHex(P087_CONFIG_GET_EVENT_HEX); + + if (P087_CONFIG_GET_FIXED_LENGTH > 0) { + P087_data->setMaxLength(P087_CONFIG_GET_FIXED_LENGTH); + } + P087_data->setFixedLength(P087_CONFIG_GET_FIXED_LENGTH); + # endif // ifndef LIMIT_BUILD_SIZE success = true; serialHelper_log_GpioDescription(port, serial_rx, serial_tx); } else { @@ -216,17 +227,15 @@ boolean Plugin_087(uint8_t function, struct EventStruct *event, String& string) } case PLUGIN_FIFTY_PER_SECOND: { - if (Settings.TaskDeviceEnabled[event->TaskIndex]) { - P087_data_struct *P087_data = - static_cast(getPluginTaskData(event->TaskIndex)); + P087_data_struct *P087_data = + static_cast(getPluginTaskData(event->TaskIndex)); - if ((nullptr != P087_data) && P087_data->loop()) { - Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + 10); - delay(0); // Processing a full sentence may take a while, run some - // background tasks. - } - success = true; + if ((nullptr != P087_data) && P087_data->loop()) { + Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + 10); + delay(0); // Processing a full sentence may take a while, run some + // background tasks. } + success = true; break; } @@ -237,6 +246,12 @@ boolean Plugin_087(uint8_t function, struct EventStruct *event, String& string) if ((nullptr != P087_data) && P087_data->getSentence(event->String2)) { if (Plugin_087_match_all(event->TaskIndex, event->String2)) { // sendData(event); + # ifndef LIMIT_BUILD_SIZE + + if (P087_data->isEventAsHex() && !event->String2.isEmpty()) { // Convert to hex without prefix + event->String2 = formatToHex_array(reinterpret_cast(&event->String2[0]), event->String2.length()); + } + # endif // ifndef LIMIT_BUILD_SIZE # ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_DEBUG, event->String2); # endif // ifndef BUILD_NO_DEBUG diff --git a/src/_P092_DLbus.ino b/src/_P092_DLbus.ino index 30aa07fb9..35da716ed 100644 --- a/src/_P092_DLbus.ino +++ b/src/_P092_DLbus.ino @@ -71,7 +71,7 @@ # include "src/PluginStructs/P092_data_struct.h" -# include "src/ESPEasyCore/ESPEasyNetwork.h" +# include "ESPEasy/net/ESPEasyNetwork.h" # define PLUGIN_092 # define PLUGIN_ID_092 92 @@ -468,7 +468,7 @@ boolean Plugin_092(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_ONCE_A_SECOND: { - if (!NetworkConnected() + if (!ESPEasy::net::NetworkConnected() || !P092_init || (nullptr == P092_data)) { return false; @@ -533,7 +533,7 @@ boolean Plugin_092(uint8_t function, struct EventStruct *event, String& string) } # endif // ifndef P092_LIMIT_BUILD_SIZE - if (!NetworkConnected()) { + if (!ESPEasy::net::NetworkConnected()) { // too busy for DLbus while wifi connect is running addLog(LOG_LEVEL_ERROR, F("## P092_read: Error DL-Bus: WiFi not connected!")); return false; diff --git a/src/_P094_CULReader.ino b/src/_P094_CULReader.ino index 0d7aa9cdc..e1dadd6d2 100644 --- a/src/_P094_CULReader.ino +++ b/src/_P094_CULReader.ino @@ -14,7 +14,7 @@ * 2025-01-12 tonhuisman: Add support for MQTT AutoDiscovery (not supported for CUL-reader) */ -# include "src/ESPEasyCore/ESPEasyNetwork.h" +# include "ESPEasy/net/ESPEasyNetwork.h" # include "src/Helpers/ESPEasy_Storage.h" # include "src/Helpers/StringConverter.h" @@ -281,7 +281,7 @@ boolean Plugin_094(uint8_t function, struct EventStruct *event, String& string) if (event->String2.length() > 0) { const bool fromCUL = true; - const String source = NetworkGetHostname(); + const String source = ESPEasy::net::NetworkGetHostname(); if (Plugin_094_match_all(event->TaskIndex, event->String2, source, fromCUL)) { if (loglevelActiveFor(LOG_LEVEL_INFO)) { diff --git a/src/_P097_Esp32Touch.ino b/src/_P097_Esp32Touch.ino index 25ac2a4ec..8a271e1d7 100644 --- a/src/_P097_Esp32Touch.ino +++ b/src/_P097_Esp32Touch.ino @@ -173,7 +173,7 @@ boolean Plugin_097(uint8_t function, struct EventStruct *event, String& string) const bool touched_prev = bitRead(p097_pinTouchedPrev, t); # endif // ifdef ESP32_CLASSIC - # if defined(ESP32S2) || defined(ESP32S3) + # if defined(ESP32S2) || defined(ESP32S3) || defined(ESP32P4) if (touched) { bitClear(p097_pinTouched, t); diff --git a/src/_P101_WakeOnLan.ino b/src/_P101_WakeOnLan.ino index 1dece4346..0202df727 100644 --- a/src/_P101_WakeOnLan.ino +++ b/src/_P101_WakeOnLan.ino @@ -182,13 +182,13 @@ boolean Plugin_101(uint8_t function, struct EventStruct *event, String& string) if (strlen(deviceTemplate[0]) == 0) { // IP Address missing, use default value (without webform warning). strcpy_P(deviceTemplate[0], PSTR(IP_STR_DEF_P101)); - addLogMove(LOG_LEVEL_INFO, F(LOG_NAME_P101 "Loaded Default IP = " IP_STR_DEF_P101)); + addLog(LOG_LEVEL_INFO, F(LOG_NAME_P101 "Loaded Default IP = " IP_STR_DEF_P101)); } else if (strlen(deviceTemplate[0]) < IP_MIN_SIZE_P101) { // IP Address too short, load default value. Warn User. strcpy_P(deviceTemplate[0], PSTR(IP_STR_DEF_P101)); errorStr += F("Provided IP Invalid (Using Default). "); - addLogMove(LOG_LEVEL_INFO, F(LOG_NAME_P101 "Provided IP Invalid (Using Default). [" IP_STR_DEF_P101 "]")); + addLog(LOG_LEVEL_INFO, F(LOG_NAME_P101 "Provided IP Invalid (Using Default). [" IP_STR_DEF_P101 "]")); } else if (!validateIp(deviceTemplate[0])) { // Unexpected IP Address value. Leave as-is, but Warn User. errorStr += F("WARNING, Please Review IP Address. "); @@ -207,7 +207,7 @@ boolean Plugin_101(uint8_t function, struct EventStruct *event, String& string) strcpy_P(deviceTemplate[1], PSTR(MAC_STR_DEF_P101)); errorStr += F("MAC Address Not Provided, Populated with Zero Values. "); - addLogMove(LOG_LEVEL_INFO, F(LOG_NAME_P101 "MAC Address Not Provided, Populated with Zero Values. ")); + addLog(LOG_LEVEL_INFO, F(LOG_NAME_P101 "MAC Address Not Provided, Populated with Zero Values. ")); } else if (!validateMac(deviceTemplate[1])) { // Suspicious MAC Address. Leave as-is, but warn User. errorStr += F("ERROR, MAC Address Invalid. "); @@ -311,11 +311,11 @@ boolean Plugin_101(uint8_t function, struct EventStruct *event, String& string) // WOL.setRepeat(1, 0); // One Magic Packet, No Repeats. (Library default) if (!WOL.sendMagicPacket(paramMac, paramPort.toInt())) { - addLogMove(LOG_LEVEL_INFO, F(LOG_NAME_P101 "Error, Magic Packet Failed (check parameters)")); + addLog(LOG_LEVEL_INFO, F(LOG_NAME_P101 "Error, Magic Packet Failed (check parameters)")); } } else { - addLogMove(LOG_LEVEL_INFO, F(LOG_NAME_P101 "Error, WiFi Off-Line")); + addLog(LOG_LEVEL_INFO, F(LOG_NAME_P101 "Error, WiFi Off-Line")); } } } diff --git a/src/_P102_PZEM004Tv3.ino b/src/_P102_PZEM004Tv3.ino index acb36395b..cdccf8c04 100644 --- a/src/_P102_PZEM004Tv3.ino +++ b/src/_P102_PZEM004Tv3.ino @@ -3,11 +3,15 @@ #ifdef USES_P102 // ####################################################################################################### -// #################################### Plugin 102: PZEM004T v30 with modbus mgt########################## +// ######################## Plugin 102: PZEM004Tv30 / PZEM-017v1 with modbus mgt ######################## // ####################################################################################################### // /** Changelog: + * 2025-09-14 tonhuisman: Add extra instruction to submit page once more after 'Reset energy' is used, to save 'Read energy' default Mode. + * 2025-08-06 tonhuisman: Change plugin name to "Energy - PZEM-004T (AC) / PZEM-017 (DC)" + * 2025-08-05 tonhuisman: Add support for PZEM-017v1 from a forum suggestion: + * https://www.letscontrolit.com/forum/viewtopic.php?p=74069#p74064 * 2025-01-17 tonhuisman: Implement support for MQTT AutoDiscovery (partially) * 2025-01-12 tonhuisman: Add support for MQTT AutoDiscovery (not supported yet for PZEM00x) */ @@ -21,7 +25,7 @@ # define PLUGIN_102 # define PLUGIN_ID_102 102 # define PLUGIN_102_DEBUG true // activate extra log info in the debug -# define PLUGIN_NAME_102 "Energy (AC) - PZEM-004Tv30-Multiple" +# define PLUGIN_NAME_102 "Energy - PZEM-004T (AC) / PZEM-017 (DC)" # define P102_PZEM_mode PCONFIG(1) // 0=read value ; 1=reset energy; 2=programm address # define P102_PZEM_ADDR PCONFIG(2) @@ -32,6 +36,7 @@ # define P102_QUERY4 PCONFIG(6) # define P102_PZEM_FIRST PCONFIG(7) # define P102_PZEM_ATTEMPT PCONFIG_LONG(1) +# define P102_PZEM_TYPE PCONFIG_LONG(0) # define P102_PZEM_mode_DFLT 0 // Read value # define P102_QUERY1_DFLT 0 // Voltage (V) @@ -44,6 +49,10 @@ # define P102_PZEM_MAX_ATTEMPT 3 // Number of tentative before declaring NAN value +# define P102_PZEM004_VALUE_COUNT 6 +# define P102_PZEM017_VALUE_COUNT 4 + + PZEM004Tv30 * P102_PZEM_sensor = nullptr; boolean Plugin_102_init = false; @@ -56,7 +65,7 @@ const __FlashStringHelper* p102_getQueryString(uint8_t query); boolean Plugin_102(uint8_t function, struct EventStruct *event, String& string) { - boolean success = false; + bool success = false; switch (function) { @@ -127,6 +136,7 @@ boolean Plugin_102(uint8_t function, struct EventStruct *even case PLUGIN_WEBFORM_SHOW_CONFIG: { string += serialHelper_getSerialTypeLabel(event); + string += strformat(F("
addr: 0x%02x"), P102_PZEM_ADDR); // Show modbus address success = true; break; } @@ -135,14 +145,17 @@ boolean Plugin_102(uint8_t function, struct EventStruct *even { // To select the data in the 4 fields. const __FlashStringHelper *options[P102_NR_OUTPUT_OPTIONS]; + const uint8_t nrOptions = PZEM_model::PZEM004Tv30 == static_cast(P102_PZEM_TYPE) + ? P102_PZEM004_VALUE_COUNT + : P102_PZEM017_VALUE_COUNT; - for (uint8_t i = 0; i < P102_NR_OUTPUT_OPTIONS; ++i) { + for (uint8_t i = 0; i < nrOptions; ++i) { options[i] = p102_getQueryString(i); } for (uint8_t i = 0; i < P102_NR_OUTPUT_VALUES; ++i) { const uint8_t pconfigIndex = i + P102_QUERY1_CONFIG_POS; - sensorTypeHelper_loadOutputSelector(event, pconfigIndex, i, P102_NR_OUTPUT_OPTIONS, options); + sensorTypeHelper_loadOutputSelector(event, pconfigIndex, i, nrOptions, options); } success = true; @@ -150,7 +163,19 @@ boolean Plugin_102(uint8_t function, struct EventStruct *even break; } - case PLUGIN_WEBFORM_LOAD: { + case PLUGIN_WEBFORM_LOAD: + { + { + const __FlashStringHelper*pzemModels[] = { + F("PZEM-004Tv30 (AC)"), + F("PZEM-017v1 (DC)"), + }; + constexpr int pzemCount = NR_ELEMENTS(pzemModels); + FormSelectorOptions selector(pzemCount, pzemModels); + selector.reloadonchange = true; + selector.addFormSelector(F("PZEM Model"), F("pztype"), P102_PZEM_TYPE); + } + if (P102_PZEM_sensor == nullptr) { P102_PZEM_FIRST = event->TaskIndex; // To detect if first PZEM or not } @@ -161,7 +186,7 @@ boolean Plugin_102(uint8_t function, struct EventStruct *even "don't use HW serial (or invert Tx and Rx to configure as SW serial).")); addFormSubHeader(F("PZEM actions")); { - const __FlashStringHelper *options_model[] = { F("Read_value"), F("Reset_Energy"), F("Program_adress") }; + const __FlashStringHelper *options_model[] = { F("Read value"), F("Reset Energy"), F("Program address") }; constexpr size_t optionCount = NR_ELEMENTS(options_model); const FormSelectorOptions selector(optionCount, options_model); selector.addFormSelector(F("PZEM Mode"), F("PZEM_mode"), P102_PZEM_mode); @@ -169,8 +194,8 @@ boolean Plugin_102(uint8_t function, struct EventStruct *even if (P102_PZEM_mode == 2) { - addHtml(F("
When programming an address, only one PZEMv30 must be connected. " - "Otherwise, all connected PZEMv30s will get the same address, which would cause a conflict during reading.
")); + addHtml(F("
When programming an address, only one PZEM must be connected. " + "Otherwise, all connected PZEMs will get the same address, which would cause a conflict during reading.
")); { const __FlashStringHelper *options_confirm[] = { F("NO"), F("YES") }; constexpr size_t optionCount = NR_ELEMENTS(options_confirm); @@ -182,13 +207,13 @@ boolean Plugin_102(uint8_t function, struct EventStruct *even } else { - addFormNumericBox(F("Address of PZEM"), F("PZEM_addr"), P102_PZEM_ADDR, 0, 247); - addHtml(F(" Address 0 allows to communicate with any single PZEMv30 whatever its address")); + addFormNumericBox(F("Address of PZEM"), F("PZEM_addr"), P102_PZEM_ADDR, 0x00, 0xF7); + addHtml(F(" Address 0 allows to communicate with any single PZEM whatever its address")); } if (P102_PZEM_ADDR_SET == 3) // If address programming done { - addHtml(F("
Address programming done !
")); + addHtml(F("
Address programming done !
")); P102_PZEM_ADDR_SET = 0; // Reset programming confirmation } } @@ -196,28 +221,30 @@ boolean Plugin_102(uint8_t function, struct EventStruct *even { addFormSubHeader(F("PZEM actions")); { - const __FlashStringHelper *options_model[] = { F("Read_value"), F("Reset_Energy") }; + const __FlashStringHelper *options_model[] = { F("Read value"), F("Reset Energy") }; constexpr size_t optionCount = NR_ELEMENTS(options_model); const FormSelectorOptions selector(optionCount, options_model); selector.addFormSelector(F("PZEM Mode"), F("PZEM_mode"), P102_PZEM_mode); } - addHtml(F(" Tx/Rx Pins config disabled: Configuration is available in the first PZEM plugin.
")); - addFormNumericBox(F("Address of PZEM"), F("PZEM_addr"), P102_PZEM_ADDR, 1, 247); + addHtml(F(" Tx/Rx Pins config ignored: Configuration is available in the first PZEM plugin.
")); + addFormNumericBox(F("Address of PZEM"), F("PZEM_addr"), P102_PZEM_ADDR, 0x01, 0xF7); } - addHtml(F("

Reset energy can be done also by: http://*espeasyip*/control?cmd=resetenergy,*PZEM address*")); - if (P102_PZEM_ADDR_SET == 4) { addHtml(F("
Energy reset on current PZEM !
")); + addHtml(F("
Click Submit button once more to save 'Read value' PZEM Mode !
")); P102_PZEM_ADDR_SET = 0; // Reset programming confirmation } + addFormNote(F("Reset Energy can also be done by: http://*espeasyip*/control?cmd=resetenergy,*PZEM address*")); + success = true; break; } - case PLUGIN_WEBFORM_SAVE: { + case PLUGIN_WEBFORM_SAVE: + { serialHelper_webformSave(event); // Save output selector parameters. @@ -226,6 +253,7 @@ boolean Plugin_102(uint8_t function, struct EventStruct *even const uint8_t choice = PCONFIG(pconfigIndex); sensorTypeHelper_saveOutputSelector(event, pconfigIndex, i, p102_getQueryString(choice)); } + P102_PZEM_TYPE = getFormItemInt(F("pztype")); P102_PZEM_mode = getFormItemInt(F("PZEM_mode")); P102_PZEM_ADDR = getFormItemInt(F("PZEM_addr")); P102_PZEM_ADDR_SET = getFormItemInt(F("PZEM_addr_set")); @@ -263,6 +291,7 @@ boolean Plugin_102(uint8_t function, struct EventStruct *even if (P102_PZEM_sensor != nullptr) { P102_PZEM_sensor->init(P102_PZEM_ADDR); + P102_PZEM_sensor->setModel(static_cast(P102_PZEM_TYPE)); // Sequence for reseting PZEM energy if (P102_PZEM_mode == 1) @@ -297,6 +326,7 @@ boolean Plugin_102(uint8_t function, struct EventStruct *even { // When new data is available, return true P102_PZEM_sensor->init(P102_PZEM_ADDR); + P102_PZEM_sensor->setModel(static_cast(P102_PZEM_TYPE)); float PZEM[6]; PZEM[0] = P102_PZEM_sensor->voltage(); @@ -306,12 +336,17 @@ boolean Plugin_102(uint8_t function, struct EventStruct *even PZEM[4] = P102_PZEM_sensor->pf(); PZEM[5] = P102_PZEM_sensor->frequency(); - for (uint8_t i = 0; i < 6; i++) // Check each PZEM field + // Skip non-available values + const uint8_t toCheckMax = PZEM_model::PZEM004Tv30 == static_cast(P102_PZEM_TYPE) + ? P102_PZEM004_VALUE_COUNT + : P102_PZEM017_VALUE_COUNT; + + for (uint8_t i = 0; i < toCheckMax; i++) // Check each PZEM field { - if (PZEM[i] != PZEM[i]) // Check if NAN + if (PZEM[i] != PZEM[i]) // Check if NAN { P102_PZEM_ATTEMPT == P102_PZEM_MAX_ATTEMPT ? P102_PZEM_ATTEMPT = 0 : P102_PZEM_ATTEMPT++; - break; // if one is Not A Number, break + break; // if one is Not A Number, break } P102_PZEM_ATTEMPT = 0; } @@ -333,10 +368,14 @@ boolean Plugin_102(uint8_t function, struct EventStruct *even # if FEATURE_PACKED_RAW_DATA case PLUGIN_GET_PACKED_RAW_DATA: { - // Matching JS code: + // Matching JS code for PZEM-004T: // return decode(bytes, [header, int16_1e1, int32_1e3, int32_1e1, int32_1e1, uint16_1e2, uint8_1e1], // ['header', 'voltage', 'current', 'power', 'energy', 'powerfactor', 'frequency']); // + // Matching JS code for PZEM-017: + // return decode(bytes, [header, int16_1e1, int32_1e3, int32_1e1, int32_1e1], + // ['header', 'voltage', 'current', 'power', 'energy']); + // // Resolutions: // Voltage: 0.1V => int16_1e1 (range 80-260V) // Current: 0.001A => int32_1e3 @@ -347,13 +386,18 @@ boolean Plugin_102(uint8_t function, struct EventStruct *even // FIXME TD-er: Calling these functions is probably done within the 200 msec timeout used in the library. // If not, this should be cached in a task data struct. - string += LoRa_addFloat(P102_PZEM_sensor->voltage(), PackedData_int16_1e1); - string += LoRa_addFloat(P102_PZEM_sensor->current(), PackedData_int32_1e3); - string += LoRa_addFloat(P102_PZEM_sensor->power(), PackedData_int32_1e1); - string += LoRa_addFloat(P102_PZEM_sensor->energy(), PackedData_int32_1e1); - string += LoRa_addFloat(P102_PZEM_sensor->pf(), PackedData_uint16_1e2); - string += LoRa_addFloat(P102_PZEM_sensor->frequency() - 40, PackedData_uint8_1e1); - event->Par1 = 6; // valuecount + string += LoRa_addFloat(P102_PZEM_sensor->voltage(), PackedData_int16_1e1); + string += LoRa_addFloat(P102_PZEM_sensor->current(), PackedData_int32_1e3); + string += LoRa_addFloat(P102_PZEM_sensor->power(), PackedData_int32_1e1); + string += LoRa_addFloat(P102_PZEM_sensor->energy(), PackedData_int32_1e1); + + if (PZEM_model::PZEM004Tv30 == static_cast(P102_PZEM_TYPE)) { + string += LoRa_addFloat(P102_PZEM_sensor->pf(), PackedData_uint16_1e2); + string += LoRa_addFloat(P102_PZEM_sensor->frequency() - 40, PackedData_uint8_1e1); + event->Par1 = P102_PZEM004_VALUE_COUNT; // valuecount + } else { + event->Par1 = P102_PZEM017_VALUE_COUNT; // valuecount + } success = true; break; @@ -367,14 +411,10 @@ boolean Plugin_102(uint8_t function, struct EventStruct *even { const String command = parseString(string, 1); - if ((equals(command, F("resetenergy"))) && (P102_PZEM_FIRST == event->TaskIndex)) - { - if ((event->Par1 >= 0) && (event->Par1 <= 247)) - { - P102_PZEM_sensor->init(event->Par1); - P102_PZEM_sensor->resetEnergy(); - success = true; - } + if (equals(command, F("resetenergy")) && (event->Par1 >= 0) && (event->Par1 <= 247)) { + P102_PZEM_sensor->init(event->Par1); + P102_PZEM_sensor->resetEnergy(); + success = true; } } @@ -392,7 +432,7 @@ const __FlashStringHelper* p102_getQueryString(uint8_t query) { case 2: return F("Power_W"); case 3: return F("Energy_kWh"); case 4: return F("Power_Factor_cosphi"); - case 5: return F("Frequency Hz"); + case 5: return F("Frequency_Hz"); } return F(""); } diff --git a/src/_P111_RC522_RFID.ino b/src/_P111_RC522_RFID.ino index 0f89afd59..20ee7c320 100644 --- a/src/_P111_RC522_RFID.ino +++ b/src/_P111_RC522_RFID.ino @@ -6,6 +6,8 @@ // ####################################################################################################### /** Changelog: + * 2025-09-12 TD-er: Add support for 7-byte UID + * 2025-08-20 TD-er: Speed-up reading + send immediate event * 2025-06-14 tonhuisman: Add support for Custom Value Type per task value * 2025-01-12 tonhuisman: Add support for MQTT AutoDiscovery (not supported for RFID) * Update changelog @@ -39,12 +41,17 @@ boolean Plugin_111(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_DEVICE_ADD: { auto& dev = Device[++deviceCount]; - dev.Number = PLUGIN_ID_111; - dev.Type = DEVICE_TYPE_SPI3; - dev.VType = Sensor_VType::SENSOR_TYPE_ULONG; - dev.ValueCount = 1; - dev.SendDataOption = true; - dev.CustomVTypeVar = true; + dev.Number = PLUGIN_ID_111; + dev.Type = DEVICE_TYPE_SPI3; +#if FEATURE_EXTENDED_TASK_VALUE_TYPES + dev.VType = Sensor_VType::SENSOR_TYPE_UINT64_SINGLE; +#else + dev.VType = Sensor_VType::SENSOR_TYPE_ULONG; +#endif + dev.ValueCount = 1; + dev.HasFormatUserVar = true; + dev.SendDataOption = true; + dev.CustomVTypeVar = true; break; } @@ -68,6 +75,21 @@ boolean Plugin_111(uint8_t function, struct EventStruct *event, String& string) break; } + case PLUGIN_FORMAT_USERVAR: + { +#if FEATURE_EXTENDED_TASK_VALUE_TYPES + string = formatULLtoHex( + UserVar.getUint64(event->TaskIndex, event->idx), + 1); +#else + string = formatToHex( + UserVar.getSensorTypeLong(event->TaskIndex, event->idx), + 1); +#endif + success = true; + break; + } + # if FEATURE_MQTT_DISCOVER || FEATURE_CUSTOM_TASKVAR_VTYPE case PLUGIN_GET_DISCOVERY_VTYPES: { diff --git a/src/_P116_ST77xx.ino b/src/_P116_ST77xx.ino index d537bc249..28f192e59 100644 --- a/src/_P116_ST77xx.ino +++ b/src/_P116_ST77xx.ino @@ -152,6 +152,8 @@ boolean Plugin_116(uint8_t function, struct EventStruct *event, String& string) ST77xx_type_toString(ST77xx_type_e::ST7735s_135x240), ST77xx_type_toString(ST77xx_type_e::ST7735s_172x320), ST77xx_type_toString(ST77xx_type_e::ST77xxs_170x320), + ST77xx_type_toString(ST77xx_type_e::ST77xxs_240x320), + ST77xx_type_toString(ST77xx_type_e::ST77xxs_240x280), # endif // if P116_EXTRA_ST7735 ST77xx_type_toString(ST77xx_type_e::ST7789vw_240x320), ST77xx_type_toString(ST77xx_type_e::ST7789vw_240x240), @@ -173,6 +175,8 @@ boolean Plugin_116(uint8_t function, struct EventStruct *event, String& string) static_cast(ST77xx_type_e::ST7735s_135x240), static_cast(ST77xx_type_e::ST7735s_172x320), static_cast(ST77xx_type_e::ST77xxs_170x320), + static_cast(ST77xx_type_e::ST77xxs_240x320), + static_cast(ST77xx_type_e::ST77xxs_240x280), # endif // if P116_EXTRA_ST7735 static_cast(ST77xx_type_e::ST7789vw_240x320), static_cast(ST77xx_type_e::ST7789vw_240x240), @@ -188,8 +192,8 @@ boolean Plugin_116(uint8_t function, struct EventStruct *event, String& string) constexpr int optCount4 = NR_ELEMENTS(optionValues4); const FormSelectorOptions selector(optCount4, options4, optionValues4); selector.addFormSelector(F("TFT display model"), - F("type"), - P116_CONFIG_FLAG_GET_TYPE); + F("type"), + P116_CONFIG_FLAG_GET_TYPE); } addFormSubHeader(F("Layout")); @@ -277,16 +281,18 @@ boolean Plugin_116(uint8_t function, struct EventStruct *event, String& string) # endif // if ADAGFX_FONTS_INCLUDED uint32_t lSettings = 0; + uint16_t _type = getFormItemInt(F("type")); bitWrite(lSettings, P116_CONFIG_FLAG_NO_WAKE, !isFormItemChecked(F("NoDisplay"))); // Bit 0 NoDisplayOnReceivingText, // reverse logic, default=checked! bitWrite(lSettings, P116_CONFIG_FLAG_INVERT_BUTTON, isFormItemChecked(F("buttonInverse"))); // Bit 1 buttonInverse bitWrite(lSettings, P116_CONFIG_FLAG_CLEAR_ON_EXIT, isFormItemChecked(F("clearOnExit"))); // Bit 2 ClearOnExit bitWrite(lSettings, P116_CONFIG_FLAG_USE_COL_ROW, isFormItemChecked(F("colrow"))); // Bit 3 Col/Row addressing - set4BitToUL(lSettings, P116_CONFIG_FLAG_MODE, getFormItemInt(F("mode"))); // Bit 4..7 Text print mode - set4BitToUL(lSettings, P116_CONFIG_FLAG_ROTATION, getFormItemInt(F("rotate"))); // Bit 8..11 Rotation - set4BitToUL(lSettings, P116_CONFIG_FLAG_FONTSCALE, getFormItemInt(F("fontscale"))); // Bit 12..15 Font scale - set4BitToUL(lSettings, P116_CONFIG_FLAG_TYPE, getFormItemInt(F("type"))); // Bit 16..19 Hardwaretype + set4BitToUL(lSettings, P116_CONFIG_FLAG_MODE, getFormItemInt(F("mode"))); // Bit 4..7 Text print mode + set4BitToUL(lSettings, P116_CONFIG_FLAG_ROTATION, getFormItemInt(F("rotate"))); // Bit 8..11 Rotation + set4BitToUL(lSettings, P116_CONFIG_FLAG_FONTSCALE, getFormItemInt(F("fontscale"))); // Bit 12..15 Font scale + set4BitToUL(lSettings, P116_CONFIG_FLAG_TYPE, _type); // Bit 16..19 Hardwaretype + set2BitToUL(lSettings, P116_CONFIG_FLAG_TYPE2, (_type >> 4)); // Bit 29..30 Hardwaretype extension set4BitToUL(lSettings, P116_CONFIG_FLAG_CMD_TRIGGER, getFormItemInt(F("commandtrigger"))); // Bit 20..23 Command trigger bitWrite(lSettings, P116_CONFIG_FLAG_BACK_FILL, !isFormItemChecked(F("backfill"))); // Bit 28 Back fill text (inv) diff --git a/src/_P123_I2CTouch.ino b/src/_P123_I2CTouch.ino index fd8a2eada..7a49e469b 100644 --- a/src/_P123_I2CTouch.ino +++ b/src/_P123_I2CTouch.ino @@ -148,7 +148,7 @@ boolean Plugin_123(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_LOAD: { # ifdef PLUGIN_123_DEBUG - addLogMove(LOG_LEVEL_INFO, F("P123 PLUGIN_WEBFORM_LOAD")); + addLog(LOG_LEVEL_INFO, F("P123 PLUGIN_WEBFORM_LOAD")); # endif // ifdef PLUGIN_123_DEBUG { addRowLabel(F("Display task")); @@ -243,7 +243,7 @@ boolean Plugin_123(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_SAVE: { # ifdef PLUGIN_123_DEBUG - addLogMove(LOG_LEVEL_INFO, F("P123 PLUGIN_WEBFORM_SAVE")); + addLog(LOG_LEVEL_INFO, F("P123 PLUGIN_WEBFORM_SAVE")); # endif // ifdef PLUGIN_123_DEBUG P123_CONFIG_DISPLAY_PREV = P123_CONFIG_DISPLAY_TASK; P123_CONFIG_THRESHOLD = getFormItemInt(F("threshold")); @@ -286,7 +286,7 @@ boolean Plugin_123(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_INIT: { # ifdef PLUGIN_123_DEBUG - addLogMove(LOG_LEVEL_INFO, F("P123 PLUGIN_INIT")); + addLog(LOG_LEVEL_INFO, F("P123 PLUGIN_INIT")); # endif // ifdef PLUGIN_123_DEBUG if (0 == P123_I2C_ADDRESS) { diff --git a/src/_P135_SCD4x.ino b/src/_P135_SCD4x.ino index 1214cde0c..36507c325 100644 --- a/src/_P135_SCD4x.ino +++ b/src/_P135_SCD4x.ino @@ -77,7 +77,8 @@ boolean Plugin_135(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_GET_DISCOVERY_VTYPES: { event->Par1 = static_cast(Sensor_VType::SENSOR_TYPE_CO2_ONLY); - event->Par2 = static_cast(Sensor_VType::SENSOR_TYPE_TEMP_HUM); + event->Par2 = static_cast(Sensor_VType::SENSOR_TYPE_HUM_ONLY); + event->Par3 = static_cast(Sensor_VType::SENSOR_TYPE_TEMP_ONLY); success = true; break; } diff --git a/src/_P137_AXP192.ino b/src/_P137_AXP192.ino index 353fccdcc..7bdad37fb 100644 --- a/src/_P137_AXP192.ino +++ b/src/_P137_AXP192.ino @@ -102,7 +102,7 @@ boolean Plugin_137(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_PRIORITY_INIT: { # ifndef BUILD_NO_DEBUG - addLogMove(LOG_LEVEL_DEBUG, F("P137: PLUGIN_PRIORITY_INIT")); + addLog(LOG_LEVEL_DEBUG, F("P137: PLUGIN_PRIORITY_INIT")); # endif // ifndef BUILD_NO_DEBUG success = Settings.isPowerManagerTask(event->TaskIndex); // Are we the PowerManager task? break; @@ -392,13 +392,13 @@ boolean Plugin_137(uint8_t function, struct EventStruct *event, String& string) if (nullptr != P137_init) { # ifndef BUILD_NO_DEBUG - addLogMove(LOG_LEVEL_INFO, F("P137: Already initialized, skipped.")); + addLog(LOG_LEVEL_INFO, F("P137: Already initialized, skipped.")); # endif // ifndef BUILD_NO_DEBUG // has been initialized so nothing to do here success = true; // Still was successful (to keep plugin enabled!) } else { # ifndef BUILD_NO_DEBUG - addLogMove(LOG_LEVEL_DEBUG, F("P137: PLUGIN_INIT")); + addLog(LOG_LEVEL_DEBUG, F("P137: PLUGIN_INIT")); # endif // ifndef BUILD_NO_DEBUG success = initPluginTaskData(event->TaskIndex, new (std::nothrow) P137_data_struct(event)); } diff --git a/src/_P139_AXP2101.ino b/src/_P139_AXP2101.ino index c63b99848..812b0799a 100644 --- a/src/_P139_AXP2101.ino +++ b/src/_P139_AXP2101.ino @@ -96,7 +96,7 @@ boolean Plugin_139(uint8_t function, struct EventStruct *event, String& string) { const bool isPowerManagerTask = Settings.isPowerManagerTask(event->TaskIndex); # ifndef BUILD_NO_DEBUG - addLogMove(LOG_LEVEL_DEBUG, F("P139: PLUGIN_PRIORITY_INIT")); + addLog(LOG_LEVEL_DEBUG, F("P139: PLUGIN_PRIORITY_INIT")); # endif // ifndef BUILD_NO_DEBUG success = isPowerManagerTask; // Are we the PowerManager task? break; @@ -262,13 +262,13 @@ boolean Plugin_139(uint8_t function, struct EventStruct *event, String& string) if (nullptr != P139_init) { # ifndef BUILD_NO_DEBUG - addLogMove(LOG_LEVEL_INFO, F("P139: Already initialized, skipped.")); + addLog(LOG_LEVEL_INFO, F("P139: Already initialized, skipped.")); # endif // ifndef BUILD_NO_DEBUG // has been initialized so nothing to do here success = true; // Still was successful (to keep plugin enabled!) } else { # ifndef BUILD_NO_DEBUG - addLogMove(LOG_LEVEL_DEBUG, F("P139: PLUGIN_INIT")); + addLog(LOG_LEVEL_DEBUG, F("P139: PLUGIN_INIT")); # endif // ifndef BUILD_NO_DEBUG success = initPluginTaskData(event->TaskIndex, new (std::nothrow) P139_data_struct(event)); } diff --git a/src/_P146_CacheControllerReader.ino b/src/_P146_CacheControllerReader.ino index 602427f4b..c3df5590c 100644 --- a/src/_P146_CacheControllerReader.ino +++ b/src/_P146_CacheControllerReader.ino @@ -45,12 +45,14 @@ boolean Plugin_146(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_DEVICE_ADD: { auto& dev = Device[++deviceCount]; - dev.Number = PLUGIN_ID_146; - dev.Type = DEVICE_TYPE_DUMMY; - dev.VType = Sensor_VType::SENSOR_TYPE_DUAL; - dev.ValueCount = 2; - dev.SendDataOption = true; - dev.OutputDataType = Output_Data_type_t::Default; + dev.Number = PLUGIN_ID_146; + dev.Type = DEVICE_TYPE_DUMMY; + dev.VType = Sensor_VType::SENSOR_TYPE_DUAL; + dev.ValueCount = 2; + dev.SendDataOption = true; + dev.OutputDataType = Output_Data_type_t::Default; + dev.HideDerivedValues = true; + dev.NoDeviceSettings = true; break; } @@ -103,19 +105,36 @@ boolean Plugin_146(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_INIT: { - // Init the controller cache handler, just in case the cache controller may not be enabled - ControllerCache.init(); - // Restore the last position from RTC when rebooting. ControllerCache.setPeekFilePos( P146_TASKVALUE_FILENR, P146_TASKVALUE_FILEPOS); + + // Init the controller cache handler, just in case the cache controller may not be enabled + ControllerCache.init(); + + // Update the position based on actually present cache files. + int peekFileNr{}; + int peekReadPos = ControllerCache.getPeekFilePos(peekFileNr); + + if (peekReadPos >= 0) { + P146_SET_TASKVALUE_FILENR(peekFileNr); + P146_SET_TASKVALUE_FILEPOS(peekReadPos); + } + + success = initPluginTaskData( event->TaskIndex, new (std::nothrow) P146_data_struct(event)); break; } + case PLUGIN_EXIT: + { + P146_data_struct::flush(); + break; + } + case PLUGIN_READ: { if (ControllerCache.peekDataAvailable()) { @@ -132,10 +151,20 @@ boolean Plugin_146(uint8_t function, struct EventStruct *event, String& string) P146_data->prepareCSVInBulk(event->TaskIndex, P146_GET_JOIN_TIMESTAMP, P146_GET_ONLY_SET_TASKS, separator); } } - } else { + } else if (P146_GET_SEND_VIA_ORIG_TASK || P146_GET_SEND_VIA_EVENT) { // Do not set the "success" or else the task values of this Cache reader task will be sent to the same controller too. - if (P146_data_struct::sendViaOriginalTask(event->TaskIndex, P146_GET_SEND_TIMESTAMP)) { + bool processed = false; + + if (P146_GET_SEND_VIA_ORIG_TASK && P146_data_struct::sendViaOriginalTask(event->TaskIndex, P146_GET_SEND_TIMESTAMP)) { + processed = true; + } + + if (P146_GET_SEND_VIA_EVENT && P146_data_struct::sendViaEvent_AllCache(event->TaskIndex, P146_GET_SEND_TIMESTAMP)) { + processed = true; + } + + if (processed) { int readFileNr = 0; const int readPos = ControllerCache.getPeekFilePos(readFileNr); @@ -153,6 +182,10 @@ boolean Plugin_146(uint8_t function, struct EventStruct *event, String& string) } else { // Default to 1 sec Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + 1000); + int readFileNr = 0; + const int readPos = ControllerCache.getPeekFilePos(readFileNr); + P146_SET_TASKVALUE_FILENR(readFileNr); + P146_SET_TASKVALUE_FILEPOS(readPos); } break; @@ -198,12 +231,16 @@ boolean Plugin_146(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_LOAD: { addFormCheckBox(F("Delete Cache Files After Send"), F("deletebin"), P146_GET_ERASE_BINFILES); + + addFormSubHeader(F("Read Rate")); + addFormNumericBox(F("Minimal Send Interval"), F("minsendinterval"), P146_MINIMAL_SEND_INTERVAL, 0, 1000); + addUnit(F("ms")); + addFormSubHeader(F("MQTT Output Options")); addFormCheckBox(F("Send Bulk"), F("sendbulk"), P146_GET_SEND_BULK); addFormCheckBox(F("HEX encoded Binary"), F("binary"), P146_GET_SEND_BINARY); // addFormCheckBox(F("Send ReadPos"), F("sendreadpos"), P146_GET_SEND_READ_POS); - addFormNumericBox(F("Minimal Send Interval"), F("minsendinterval"), P146_MINIMAL_SEND_INTERVAL, 0, 1000); addFormNumericBox(F("Max Message Size"), F("maxmsgsize"), P146_MQTT_MESSAGE_LENGTH, @@ -216,7 +253,10 @@ boolean Plugin_146(uint8_t function, struct EventStruct *event, String& string) addFormTextBox(F("Publish Topic"), getPluginCustomArgName(P146_PublishTopicIndex), strings[P146_PublishTopicIndex], P146_Nchars); - // addFormSubHeader(F("Non MQTT Output Options")); + addFormSubHeader(F("Non MQTT Output Options")); + addFormCheckBox(F("Send via Original Task"), F("origTask"), P146_GET_SEND_VIA_ORIG_TASK); + addFormCheckBox(F("Send as Event"), F("sendEvent"), P146_GET_SEND_VIA_EVENT); + // addFormCheckBox(F("Send Timestamp"), F("sendtimestamp"), P146_GET_SEND_TIMESTAMP); addTableSeparator(F("Export to CSV"), 2, 3); @@ -245,11 +285,15 @@ boolean Plugin_146(uint8_t function, struct EventStruct *event, String& string) html_add_button_prefix(); addHtml(F("dumpcache?separator=")); - switch (static_cast(P146_SEPARATOR_CHARACTER)) { - case '\t': addHtml(F("Tab")); break; - case ',': addHtml(F("Comma")); break; + switch (static_cast(P146_SEPARATOR_CHARACTER)) + { + case '\t': addHtml(F("Tab")); + break; + case ',': addHtml(F("Comma")); + break; case ';': - default: addHtml(F("Semicolon")); break; + default: addHtml(F("Semicolon")); + break; } if (P146_GET_JOIN_TIMESTAMP) { @@ -281,6 +325,9 @@ boolean Plugin_146(uint8_t function, struct EventStruct *event, String& string) P146_SET_ONLY_SET_TASKS(isFormItemChecked(F("onlysettasks"))); P146_SEPARATOR_CHARACTER = getFormItemInt(F("separator")); + P146_SET_SEND_VIA_ORIG_TASK(isFormItemChecked(F("origTask"))); + P146_SET_SEND_VIA_EVENT(isFormItemChecked(F("sendEvent"))); + String strings[P146_Nlines]; for (uint8_t varNr = 0; varNr < P146_Nlines; varNr++) { @@ -305,7 +352,15 @@ boolean Plugin_146(uint8_t function, struct EventStruct *event, String& string) if (equals(command, F("cachereader"))) { if (equals(subcommand, F("setreadpos"))) { - P146_data_struct::setPeekFilePos(event->Par2, event->Par3); + if (P146_data_struct::setPeekFilePos(event->Par2, event->Par3)) { + int peekFileNr{}; + int peekReadPos = ControllerCache.getPeekFilePos(peekFileNr); + + if (peekReadPos >= 0) { + P146_SET_TASKVALUE_FILENR(peekFileNr); + P146_SET_TASKVALUE_FILEPOS(peekReadPos); + } + } success = true; } else if (equals(subcommand, F("sendtaskinfo"))) { P146_data_struct *P146_data = static_cast(getPluginTaskData(event->TaskIndex)); diff --git a/src/_P164_gases_ens160.ino b/src/_P164_gases_ens160.ino index 38d9bd8c0..1cb3fc46e 100644 --- a/src/_P164_gases_ens160.ino +++ b/src/_P164_gases_ens160.ino @@ -121,7 +121,7 @@ boolean Plugin_164(uint8_t function, struct EventStruct *event, String& string) P164_data_struct *P164_data = static_cast(getPluginTaskData(event->TaskIndex)); if (nullptr == P164_data) { - addLogMove(LOG_LEVEL_ERROR, F("P164: plugin_read NULLPTR")); + addLog(LOG_LEVEL_ERROR, F("P164: plugin_read NULLPTR")); break; } diff --git a/src/_P180_I2C_generic.ino b/src/_P180_I2C_generic.ino index b64fbba84..af3e495fc 100644 --- a/src/_P180_I2C_generic.ino +++ b/src/_P180_I2C_generic.ino @@ -6,6 +6,7 @@ // ####################################################################################################### /** Changelog: + * 2025-12-06 tonhuisman: Enable CustomVTypeVar and MQTTStateClass device options, as any type of sensor can be connected * 2025-06-04 tonhuisman: Add [#log] to Get Config to fetch the current Parsing and execution log setting * 2025-06-03 tonhuisman: Restore PLUGIN_GET_CONFIG_VALUE support, to allow I2C operations to be executed and return a value without * defining extra I2C Command definitions. Not available for LIMIT_BUILD_SIZE builds @@ -135,6 +136,8 @@ boolean Plugin_180(uint8_t function, struct EventStruct *event, String& string) dev.TimerOptional = true; dev.FormulaOption = true; dev.PluginStats = true; + dev.CustomVTypeVar = true; + dev.MqttStateClass = true; break; } diff --git a/src/_Plugin_Helper.cpp b/src/_Plugin_Helper.cpp index 5e49b5512..5c720cc64 100644 --- a/src/_Plugin_Helper.cpp +++ b/src/_Plugin_Helper.cpp @@ -63,7 +63,7 @@ bool initPluginTaskData(taskIndex_t taskIndex, PluginTaskData_base *data) { const uint8_t valueCount = getValueCountForTask(taskIndex); for (size_t i = 0; i < valueCount; ++i) { if (Cache.enabledPluginStats(taskIndex, i)) { - Plugin_task_data[taskIndex]->initPluginStats(taskIndex, i); + Plugin_task_data[taskIndex]->initPluginStats(i); } } #endif diff --git a/src/_Plugin_Helper.h b/src/_Plugin_Helper.h index ae93568f1..9848ab0ed 100644 --- a/src/_Plugin_Helper.h +++ b/src/_Plugin_Helper.h @@ -40,6 +40,7 @@ #include "src/Helpers/Misc.h" #include "src/Helpers/Numerical.h" #include "src/Helpers/PortStatus.h" +#include "src/Helpers/PrintToString.h" #include "src/Helpers/StringConverter.h" #include "src/Helpers/StringGenerator_GPIO.h" #include "src/Helpers/StringGenerator_Plugin.h" diff --git a/src/include/ESPEasy_config.h b/src/include/ESPEasy_config.h index 8ccfe1643..c9799c9cc 100644 --- a/src/include/ESPEasy_config.h +++ b/src/include/ESPEasy_config.h @@ -86,6 +86,9 @@ #define NR_ELEMENTS(ARR) (sizeof (ARR) / sizeof *(ARR)) //#define NR_ELEMENTS(ARR) sizeof(ARR) / sizeof(ARR[0]) +// Shortcut to typedef an unique pointer of a given type +#define DEF_UP(T) typedef std::unique_ptr UP_##T + constexpr unsigned FLOOR_LOG2(unsigned x) { @@ -149,7 +152,7 @@ constexpr unsigned CEIL_LOG2(unsigned x) # endif // ifdef ESP8266 # ifdef ESP32 - # if defined(ESP32C2) || defined(ESP32C3) || defined(ESP32C6) + # if defined(ESP32C2) || defined(ESP32C3) || defined(ESP32C5) || defined(ESP32C6) || defined(ESP32C61) || defined(ESP32P4) // (ESP32) FsP: FlashstringHelper to String-Pointer # define FsP diff --git a/src/include/esp32x_fixes.h b/src/include/esp32x_fixes.h index 4c5a5e780..9121bc179 100644 --- a/src/include/esp32x_fixes.h +++ b/src/include/esp32x_fixes.h @@ -32,6 +32,9 @@ * -I$PROJECT_DIR/include * -include "esp32x_fixes.h" */ + +//#include + #ifdef __riscv # undef __INT32_TYPE__ @@ -42,6 +45,10 @@ #endif // __riscv +#ifdef ESP32 +#include +#endif + // alias, deprecated for the chips after esp32s2 #ifdef CONFIG_IDF_TARGET_ESP32 # define SPI_HOST SPI1_HOST @@ -87,10 +94,14 @@ # endif // REG_SPI_BASE # endif // ESP_IDF_VERSION_MAJOR < 5 -#elif CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C6 +#else # define SPI_HOST SPI1_HOST # define HSPI_HOST SPI2_HOST -# define VSPI_HOST SPI2_HOST /* No SPI3_host on C2/C6 */ +#if SOC_SPI_PERIPH_NUM > 2 +# define VSPI_HOST SPI3_HOST +#else +# define VSPI_HOST SPI2_HOST /* No SPI3_host */ +#endif # define VSPI SPI // #if ESP_IDF_VERSION_MAJOR < 5 @@ -105,3 +116,20 @@ #endif // TARGET + + +#ifndef CONFIG_SOC_WIFI_HE_SUPPORT +#if CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32C61 +#define CONFIG_SOC_WIFI_HE_SUPPORT 1 +#else +#define CONFIG_SOC_WIFI_HE_SUPPORT 0 +#endif +#endif + +#ifndef CONFIG_SOC_WIFI_SUPPORT_5G +#if CONFIG_IDF_TARGET_ESP32C5 +#define CONFIG_SOC_WIFI_SUPPORT_5G 1 +#else +#define CONFIG_SOC_WIFI_SUPPORT_5G 0 +#endif +#endif \ No newline at end of file diff --git a/src/src/Commands/Common.cpp b/src/src/Commands/Common.cpp index 0eb96ac9f..7760172c6 100644 --- a/src/src/Commands/Common.cpp +++ b/src/src/Commands/Common.cpp @@ -5,8 +5,7 @@ #include "../../ESPEasy_common.h" - -#include "../ESPEasyCore/ESPEasyWifi.h" +#include "../../ESPEasy/net/wifi/ESPEasyWifi.h" #include "../ESPEasyCore/Serial.h" #include "../Helpers/Networking.h" @@ -15,8 +14,8 @@ // Simple function to return "Ok", to avoid flash string duplication in the firmware. -const __FlashStringHelper * return_command_success_flashstr() { return F("\nOK"); } -const __FlashStringHelper * return_command_failed_flashstr() { return F("\nFailed"); } +const __FlashStringHelper * return_command_success_flashstr() { return F("OK"); } +const __FlashStringHelper * return_command_failed_flashstr() { return F("Failed"); } const __FlashStringHelper * return_command_boolean_result_flashstr(bool success) { @@ -38,6 +37,17 @@ const __FlashStringHelper * return_incorrect_nr_arguments() { return F("Too many const __FlashStringHelper * return_incorrect_source() { return F("Command not allowed from this source!"); } const __FlashStringHelper * return_not_connected() { return F("Not connected to WiFi"); } +String return_result(struct EventStruct *event, + const __FlashStringHelper * result) +{ + serialPrintln(); + serialPrintln(result); + + if (event->Source == EventValueSource::Enum::VALUE_SOURCE_SERIAL) { + return return_command_success(); + } + return result; +} String return_result(struct EventStruct *event, const String& result) { @@ -235,3 +245,33 @@ String Command_GetORSetInt8_t(struct EventStruct *event, } return return_command_success(); } + +String Command_GetORSetFloatMinMax(struct EventStruct *event, + const __FlashStringHelper *targetDescription, + const char *Line, + float *value, + int arg, + float _min, + float _max) +{ + { + // Check if command is valid. Leave in separate scope to delete the TmpStr1 + String TmpStr1; + + if (GetArgv(Line, TmpStr1, arg + 1)) { + TmpStr1.toLowerCase(); + + float tmp_float{}; + + if (validFloatFromString(TmpStr1, tmp_float) && + definitelyGreaterThan(tmp_float, _min) && + definitelyLessThan(tmp_float, _max)) { + *value = tmp_float; + } else { + return return_command_failed(); + } + } + } + + return return_result(event, concat(targetDescription, toString(*value))); +} diff --git a/src/src/Commands/Common.h b/src/src/Commands/Common.h index 6e5d5ec3f..e9691826f 100644 --- a/src/src/Commands/Common.h +++ b/src/src/Commands/Common.h @@ -24,6 +24,9 @@ const __FlashStringHelper * return_incorrect_nr_arguments(); const __FlashStringHelper * return_incorrect_source(); const __FlashStringHelper * return_not_connected(); +String return_result(struct EventStruct *event, + const __FlashStringHelper * result); + String return_result(struct EventStruct *event, const String & result); @@ -66,4 +69,12 @@ String Command_GetORSetInt8_t(struct EventStruct *event, int8_t *value, int arg); +String Command_GetORSetFloatMinMax(struct EventStruct *event, + const __FlashStringHelper * targetDescription, + const char *Line, + float *value, + int arg, + float _min, + float _max); + #endif // COMMAND_COMMON_H diff --git a/src/src/Commands/Diagnostic.cpp b/src/src/Commands/Diagnostic.cpp index a402c6312..8281f47bd 100644 --- a/src/src/Commands/Diagnostic.cpp +++ b/src/src/Commands/Diagnostic.cpp @@ -31,9 +31,9 @@ #include -#ifndef BUILD_MINIMAL_OTA +#ifndef BUILD_NO_DEBUG bool showSettingsFileLayout = false; -#endif // ifndef BUILD_MINIMAL_OTA +#endif #ifndef BUILD_NO_DIAGNOSTIC_COMMANDS String Command_Lowmem(struct EventStruct *event, const char *Line) @@ -91,7 +91,7 @@ const __FlashStringHelper * Command_MemInfo(struct EventStruct *event, const cha const __FlashStringHelper * Command_MemInfo_detail(struct EventStruct *event, const char *Line) { -#ifndef BUILD_MINIMAL_OTA +#ifndef BUILD_NO_DEBUG showSettingsFileLayout = true; Command_MemInfo(event, Line); @@ -124,7 +124,7 @@ const __FlashStringHelper * Command_MemInfo_detail(struct EventStruct *event, co return return_see_serial(event); #else return return_command_failed_flashstr(); - #endif // ifndef BUILD_MINIMAL_OTA + #endif } const __FlashStringHelper * Command_Background(struct EventStruct *event, const char *Line) @@ -139,7 +139,6 @@ const __FlashStringHelper * Command_Background(struct EventStruct *event, const serialPrintln(F("end")); return return_see_serial(event); } -#endif // BUILD_NO_DIAGNOSTIC_COMMANDS const __FlashStringHelper * Command_Debug(struct EventStruct *event, const char *Line) { @@ -152,6 +151,8 @@ const __FlashStringHelper * Command_Debug(struct EventStruct *event, const char } return return_see_serial(event); } +#endif + String Command_logentry(struct EventStruct *event, const char *Line) { @@ -165,7 +166,17 @@ String Command_logentry(struct EventStruct *event, const char *Line) #endif ) { level = event->Par2; } String res = tolerantParseStringKeepCase(Line, 2); +#if FEATURE_COLORIZE_CONSOLE_LOGS + if (!EventValueSource::isExternalSource(event->Source)) { + addLog(LOG_LEVEL_NONE, Line); + } +#endif addLog(level, res); +#if FEATURE_COLORIZE_CONSOLE_LOGS + if (!EventValueSource::isExternalSource(event->Source)) { + addLog(LOG_LEVEL_NONE, res); + } +#endif return res; } diff --git a/src/src/Commands/Diagnostic.h b/src/src/Commands/Diagnostic.h index fa3a84c7a..b1cd8a6c8 100644 --- a/src/src/Commands/Diagnostic.h +++ b/src/src/Commands/Diagnostic.h @@ -17,8 +17,8 @@ const __FlashStringHelper * Command_SerialFloat(struct EventStruct *event, const const __FlashStringHelper * Command_MemInfo(struct EventStruct *event, const char* Line); const __FlashStringHelper * Command_MemInfo_detail(struct EventStruct *event, const char* Line); const __FlashStringHelper * Command_Background(struct EventStruct *event, const char* Line); -#endif const __FlashStringHelper * Command_Debug(struct EventStruct *event, const char* Line); +#endif String Command_logentry(struct EventStruct *event, const char* Line); #ifndef BUILD_NO_DIAGNOSTIC_COMMANDS const __FlashStringHelper * Command_JSONPortStatus(struct EventStruct *event, const char* Line); @@ -28,7 +28,7 @@ const __FlashStringHelper * Command_logPortStatus(struct EventStruct *event, con #endif -#ifndef BUILD_MINIMAL_OTA +#ifndef BUILD_NO_DEBUG // Show settings file layout in charts on sysinfo page. // Will be enabled after running command "meminfodetail" extern bool showSettingsFileLayout; diff --git a/src/src/Commands/ExecuteCommand.cpp b/src/src/Commands/ExecuteCommand.cpp index 045d9e543..61f9c6397 100644 --- a/src/src/Commands/ExecuteCommand.cpp +++ b/src/src/Commands/ExecuteCommand.cpp @@ -12,6 +12,9 @@ #include "../Helpers/StringConverter.h" #include "../Helpers/StringParser.h" +#include "../../ESPEasy/net/Globals/NWPlugins.h" + + ExecuteCommandArgs::ExecuteCommandArgs(EventValueSource::Enum source, const char *Line) : _taskIndex(INVALID_TASK_INDEX), @@ -267,6 +270,13 @@ bool ExecuteCommand(ExecuteCommandArgs&& args, bool addToQueue) // if (handled) addLog(LOG_LEVEL_INFO, F("CPLUGIN_WRITE accepted")); } + if (!handled) { + // Try a controller + handled = ESPEasy::net::NWPluginCall(NWPlugin::Function::NWPLUGIN_WRITE, &TempEvent, tmpAction); + + // if (handled) addLog(LOG_LEVEL_INFO, F("CPLUGIN_WRITE accepted")); + } + if (handled) { SendStatus(&TempEvent, return_command_success()); return true; @@ -284,7 +294,7 @@ bool ExecuteCommand(ExecuteCommandArgs&& args, bool addToQueue) } String errorUnknown = concat(F("Command unknown: "), args._Line); SendStatus(&TempEvent, errorUnknown); - addLogMove(LOG_LEVEL_INFO, errorUnknown); + addLogMove(LOG_LEVEL_ERROR, errorUnknown); delay(0); return false; } diff --git a/src/src/Commands/GPIO.cpp b/src/src/Commands/GPIO.cpp index bb66cd550..ac18d3ee2 100644 --- a/src/src/Commands/GPIO.cpp +++ b/src/src/Commands/GPIO.cpp @@ -100,7 +100,7 @@ bool gpio_monitor_helper(int port, struct EventStruct *event, const char *Line) globalMapPortStatus[key].state = state; if (state == -1) { globalMapPortStatus[key].mode = PIN_MODE_OFFLINE; } - #ifndef BUILD_MINIMAL_OTA + #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, concat( logPrefix, @@ -148,7 +148,7 @@ bool gpio_unmonitor_helper(int port, struct EventStruct *event, const char *Line SendStatusOnlyIfNeeded(event, SEARCH_PIN_STATE, key, dummy, 0); removeMonitorFromPort(key); - #ifndef BUILD_MINIMAL_OTA + #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, concat( logPrefix, @@ -417,7 +417,7 @@ const __FlashStringHelper * Command_GPIO_RTTTL(struct EventStruct *event, const return return_command_success_flashstr(); } #else // if FEATURE_RTTTL - #ifndef BUILD_MINIMAL_OTA + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_ERROR, F("RTTTL: command not included in build")); #endif #endif // if FEATURE_RTTTL diff --git a/src/src/Commands/HTTP.cpp b/src/src/Commands/HTTP.cpp index 5cf27b8c5..0593f6b1b 100644 --- a/src/src/Commands/HTTP.cpp +++ b/src/src/Commands/HTTP.cpp @@ -8,7 +8,7 @@ #include "../DataStructs/SettingsStruct.h" #include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../../ESPEasy/net/ESPEasyNetwork.h" #include "../Globals/Settings.h" @@ -25,9 +25,10 @@ const __FlashStringHelper* httpEmitToHTTP(struct EventStruct *event, const int timeout, const bool waitForAck, const bool useHeader, - const bool useBody) + const bool useBody, + const bool useHttps) { - if (NetworkConnected()) { + if (ESPEasy::net::NetworkConnected()) { String user, pass, host, file, path, header, postBody; uint16_t port; uint8_t idx; @@ -60,12 +61,8 @@ const __FlashStringHelper* httpEmitToHTTP(struct EventStruct *event, port = port_arg; } else { if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - String log = logIdentifier; - log += F(": Invalid port argument: "); - log += port_arg; - log += F(" will use: "); - log += port; - addLogMove(LOG_LEVEL_ERROR, log); + addLogMove(LOG_LEVEL_ERROR, strformat(F("%s: Invalid port argument: %d will use: %d"), + FsP(logIdentifier), port_arg, port)); } } @@ -88,17 +85,19 @@ const __FlashStringHelper* httpEmitToHTTP(struct EventStruct *event, # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = logIdentifier; - log += F(": Host: "); - log += host; - log += F(" port: "); - log += port; - log += F(" path: "); - log += path; - addLogMove(LOG_LEVEL_DEBUG, log); + addLogMove(LOG_LEVEL_DEBUG, strformat(F("%s: Host: %s port: %d path: %s"), + FsP(logIdentifier), host.c_str(), port, path.c_str())); } # endif // ifndef BUILD_NO_DEBUG + #if FEATURE_HTTP_TLS + TLS_types tlsType = TLS_types::NoTLS; + if (useHttps) { + tlsType = TLS_types::TLS_insecure; + // TODO: ?Parse Line for type of TLS to use? + } + #endif // if FEATURE_HTTP_TLS + int httpCode = -1; send_via_http( logIdentifier, @@ -112,7 +111,11 @@ const __FlashStringHelper* httpEmitToHTTP(struct EventStruct *event, header, postBody, httpCode, - waitForAck); + waitForAck + #if FEATURE_HTTP_TLS + , tlsType + #endif // if FEATURE_HTTP_TLS + ); if ((httpCode >= 100) && (httpCode < 300)) { return return_command_success_flashstr(); @@ -138,9 +141,26 @@ const __FlashStringHelper* Command_HTTP_SendToHTTP(struct EventStruct *event, co const int timeout = Settings.SendToHttp_ack() ? CONTROLLER_CLIENTTIMEOUT_MAX : 1000; - return httpEmitToHTTP(event, F("SendToHTTP"), F("GET"), Line, timeout, Settings.SendToHttp_ack(), false, false); + return httpEmitToHTTP(event, F("SendToHTTP"), F("GET"), Line, timeout, Settings.SendToHttp_ack(), false, false, false); } +#if FEATURE_HTTP_TLS + +// syntax 1: SendToHttpS,<[:@],, +// syntax 2: SendToHttpS,http://<[:@][:]/ +const __FlashStringHelper* Command_HTTP_SendToHTTPS(struct EventStruct *event, const char *Line) +{ + // Some servers don't give an ack. + // For these it is adviced to uncheck to wait for an acknowledgement. + // However the default timeout of 4000 msec is then way too long + // FIXME TD-er: Make sendToHttp timeout a setting. + const int timeout = Settings.SendToHttp_ack() + ? CONTROLLER_CLIENTTIMEOUT_MAX : 1000; + + return httpEmitToHTTP(event, F("SendToHTTPS"), F("GET"), Line, timeout, Settings.SendToHttp_ack(), false, false, true); +} +#endif // if FEATURE_HTTP_TLS + #endif // FEATURE_SEND_TO_HTTP #if FEATURE_POST_TO_HTTP @@ -154,9 +174,24 @@ const __FlashStringHelper* Command_HTTP_PostToHTTP(struct EventStruct *event, co // FIXME tonhuisman: make PostToHttp_ack a setting, using SendToHttp_ack for now... - return httpEmitToHTTP(event, F("PostToHTTP"), F("POST"), Line, timeout, Settings.SendToHttp_ack(), true, true); + return httpEmitToHTTP(event, F("PostToHTTP"), F("POST"), Line, timeout, Settings.SendToHttp_ack(), true, true, false); } +#if FEATURE_HTTP_TLS + +// syntax 1: PostToHttpS,<[:@],,,
, +// syntax 2: PostToHttpS,http://<[:@][:]/,
, +const __FlashStringHelper* Command_HTTP_PostToHTTPS(struct EventStruct *event, const char *Line) +{ + // FIXME tonhuisman: Make postToHttp timeout a setting, now using a somewhat sensible default + const int timeout = CONTROLLER_CLIENTTIMEOUT_MAX; + + // FIXME tonhuisman: make PostToHttp_ack a setting, using SendToHttp_ack for now... + + return httpEmitToHTTP(event, F("PostToHTTPS"), F("POST"), Line, timeout, Settings.SendToHttp_ack(), true, true, true); +} +#endif // if FEATURE_HTTP_TLS + #endif // if FEATURE_POST_TO_HTTP #if FEATURE_PUT_TO_HTTP @@ -170,7 +205,22 @@ const __FlashStringHelper* Command_HTTP_PutToHTTP(struct EventStruct *event, con // FIXME tonhuisman: make PutToHttp_ack a setting, using SendToHttp_ack for now... - return httpEmitToHTTP(event, F("PutToHTTP"), F("PUT"), Line, timeout, Settings.SendToHttp_ack(), true, true); + return httpEmitToHTTP(event, F("PutToHTTP"), F("PUT"), Line, timeout, Settings.SendToHttp_ack(), true, true, false); } +#if FEATURE_HTTP_TLS + +// syntax 1: PutToHttpS,<[:@],,,
, +// syntax 2: PutToHttpS,http://<[:@][:]/,
, +const __FlashStringHelper* Command_HTTP_PutToHTTPS(struct EventStruct *event, const char *Line) +{ + // FIXME tonhuisman: Make putToHttp timeout a setting, now using a somewhat sensible default + const int timeout = CONTROLLER_CLIENTTIMEOUT_MAX; + + // FIXME tonhuisman: make PutToHttp_ack a setting, using SendToHttp_ack for now... + + return httpEmitToHTTP(event, F("PutToHTTPS"), F("PUT"), Line, timeout, Settings.SendToHttp_ack(), true, true, true); +} +#endif // if FEATURE_HTTP_TLS + #endif // if FEATURE_PUT_TO_HTTP diff --git a/src/src/Commands/HTTP.h b/src/src/Commands/HTTP.h index ea1dfdc75..124881324 100644 --- a/src/src/Commands/HTTP.h +++ b/src/src/Commands/HTTP.h @@ -11,19 +11,32 @@ const __FlashStringHelper* httpEmitToHTTP(struct EventStruct *event, const int timeout, const bool waitForAck, const bool useHeader, - const bool useBody); + const bool useBody, + const bool useHttps); #endif // if FEATURE_SEND_TO_HTTP || FEATURE_POST_TO_HTTP || FEATURE_PUT_TO_HTTP #if FEATURE_SEND_TO_HTTP const __FlashStringHelper* Command_HTTP_SendToHTTP(struct EventStruct *event, const char *Line); +#if FEATURE_HTTP_TLS +const __FlashStringHelper* Command_HTTP_SendToHTTPS(struct EventStruct *event, + const char *Line); +#endif // if FEATURE_HTTP_TLS #endif // FEATURE_SEND_TO_HTTP #if FEATURE_POST_TO_HTTP const __FlashStringHelper* Command_HTTP_PostToHTTP(struct EventStruct *event, const char *Line); +#if FEATURE_HTTP_TLS +const __FlashStringHelper* Command_HTTP_PostToHTTPS(struct EventStruct *event, + const char *Line); +#endif // if FEATURE_HTTP_TLS #endif // if FEATURE_POST_TO_HTTP #if FEATURE_PUT_TO_HTTP const __FlashStringHelper* Command_HTTP_PutToHTTP(struct EventStruct *event, const char *Line); +#if FEATURE_HTTP_TLS +const __FlashStringHelper* Command_HTTP_PutToHTTPS(struct EventStruct *event, + const char *Line); +#endif // if FEATURE_HTTP_TLS #endif // if FEATURE_PUT_TO_HTTP #endif // COMMAND_HTTP_H diff --git a/src/src/Commands/InternalCommands.cpp b/src/src/Commands/InternalCommands.cpp index dce54f5a3..3d99d2984 100644 --- a/src/src/Commands/InternalCommands.cpp +++ b/src/src/Commands/InternalCommands.cpp @@ -18,6 +18,10 @@ #include "../Commands/InternalCommands_decoder.h" #include "../Commands/i2c.h" +#if FEATURE_LAT_LONG_VAR_CMD +#include "../Commands/LatitudeLongitude.h" +#endif // if FEATURE_LAT_LONG_VAR_CMD + #if FEATURE_MQTT # include "../Commands/MQTT.h" #endif // if FEATURE_MQTT @@ -43,7 +47,9 @@ #include "../Commands/Timer.h" #include "../Commands/UPD.h" #include "../Commands/wd.h" +#if FEATURE_WIFI #include "../Commands/WiFi.h" +#endif #include "../DataStructs/TimingStats.h" @@ -180,6 +186,16 @@ bool do_command_case_check(command_case_data & data, return false; } +#if FEATURE_COLORIZE_CONSOLE_LOGS + if (EventValueSource::isExternalSource(data.event->Source)) { + addLog(LOG_LEVEL_NONE, + strformat( + F("Cmd (%s) : %s"), + FsP(EventValueSource::toString(data.event->Source)), + data.line.c_str())); + } +#endif + // FIXME TD-er: Do not check nr arguments from MQTT source. // See https://github.com/letscontrolit/ESPEasy/issues/3344 // C005 does recreate command partly from topic and published message @@ -279,7 +295,9 @@ bool InternalCommands::executeInternalCommand() case ESPEasy_cmd_e::controllerdisable: COMMAND_CASE_R(Command_Controller_Disable, 1); // Controller.h case ESPEasy_cmd_e::controllerenable: COMMAND_CASE_R(Command_Controller_Enable, 1); // Controller.h case ESPEasy_cmd_e::datetime: COMMAND_CASE_R(Command_DateTime, 2); // Time.h +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS case ESPEasy_cmd_e::debug: COMMAND_CASE_R(Command_Debug, 1); // Diagnostic.h +#endif case ESPEasy_cmd_e::dec: COMMAND_CASE_A(Command_Rules_Dec, -1); // Rules.h case ESPEasy_cmd_e::deepsleep: COMMAND_CASE_R(Command_System_deepSleep, 1); // System.h case ESPEasy_cmd_e::delay: COMMAND_CASE_R(Command_Delay, 1); // Timers.h @@ -289,12 +307,6 @@ bool InternalCommands::executeInternalCommand() case ESPEasy_cmd_e::dns: COMMAND_CASE_R(Command_DNS, 1); // Network Command case ESPEasy_cmd_e::dst: COMMAND_CASE_R(Command_DST, 1); // Time.h #if FEATURE_ETHERNET - case ESPEasy_cmd_e::ethphyadr: COMMAND_CASE_R(Command_ETH_Phy_Addr, 1); // Network Command - case ESPEasy_cmd_e::ethpinmdc: COMMAND_CASE_R(Command_ETH_Pin_mdc, 1); // Network Command - case ESPEasy_cmd_e::ethpinmdio: COMMAND_CASE_R(Command_ETH_Pin_mdio, 1); // Network Command - case ESPEasy_cmd_e::ethpinpower: COMMAND_CASE_R(Command_ETH_Pin_power, 1); // Network Command - case ESPEasy_cmd_e::ethphytype: COMMAND_CASE_R(Command_ETH_Phy_Type, 1); // Network Command - case ESPEasy_cmd_e::ethclockmode: COMMAND_CASE_R(Command_ETH_Clock_Mode, 1); // Network Command case ESPEasy_cmd_e::ethip: COMMAND_CASE_R(Command_ETH_IP, 1); // Network Command case ESPEasy_cmd_e::ethgateway: COMMAND_CASE_R(Command_ETH_Gateway, 1); // Network Command case ESPEasy_cmd_e::ethsubnet: COMMAND_CASE_R(Command_ETH_Subnet, 1); // Network Command @@ -302,14 +314,18 @@ bool InternalCommands::executeInternalCommand() case ESPEasy_cmd_e::ethdisconnect: COMMAND_CASE_A(Command_ETH_Disconnect, 0); // Network Command case ESPEasy_cmd_e::ethwifimode: COMMAND_CASE_R(Command_ETH_Wifi_Mode, 1); // Network Command #endif // FEATURE_ETHERNET +#if FEATURE_WIFI case ESPEasy_cmd_e::erasesdkwifi: COMMAND_CASE_R(Command_WiFi_Erase, 0); // WiFi.h +#endif case ESPEasy_cmd_e::event: COMMAND_CASE_A(Command_Rules_Events, -1); // Rule.h case ESPEasy_cmd_e::executerules: COMMAND_CASE_A(Command_Rules_Execute, -1); // Rule.h case ESPEasy_cmd_e::factoryreset: COMMAND_CASE_R(Command_Settings_FactoryReset, 0); // Settings.h case ESPEasy_cmd_e::gateway: COMMAND_CASE_R(Command_Gateway, 1); // Network Command case ESPEasy_cmd_e::gpio: COMMAND_CASE_A(Command_GPIO, 2); // Gpio.h case ESPEasy_cmd_e::gpiotoggle: COMMAND_CASE_A(Command_GPIO_Toggle, 1); // Gpio.h +#if FEATURE_WIFI case ESPEasy_cmd_e::hiddenssid: COMMAND_CASE_R(Command_Wifi_HiddenSSID, 1); // wifi.h +#endif case ESPEasy_cmd_e::i2cscanner: COMMAND_CASE_R(Command_i2c_Scanner, -1); // i2c.h case ESPEasy_cmd_e::inc: COMMAND_CASE_A(Command_Rules_Inc, -1); // Rules.h case ESPEasy_cmd_e::ip: COMMAND_CASE_R(Command_IP, 1); // Network Command @@ -319,12 +335,18 @@ bool InternalCommands::executeInternalCommand() #ifndef BUILD_NO_DIAGNOSTIC_COMMANDS case ESPEasy_cmd_e::jsonportstatus: COMMAND_CASE_A(Command_JSONPortStatus, -1); // Diagnostic.h #endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + #if FEATURE_LAT_LONG_VAR_CMD + case ESPEasy_cmd_e::latitude: COMMAND_CASE_A(Command_Latitude, -1); // LatitudeLongitude.h + #endif // if FEATURE_LAT_LONG_VAR_CMD case ESPEasy_cmd_e::let: COMMAND_CASE_A(Command_Rules_Let, 2); // Rules.h #if FEATURE_STRING_VARIABLES case ESPEasy_cmd_e::letstr: COMMAND_CASE_A(Command_Rules_LetStr, 2); // Rules.h #endif // if FEATURE_STRING_VARIABLES case ESPEasy_cmd_e::load: COMMAND_CASE_A(Command_Settings_Load, 0); // Settings.h case ESPEasy_cmd_e::logentry: COMMAND_CASE_A(Command_logentry, -1); // Diagnostic.h + #if FEATURE_LAT_LONG_VAR_CMD + case ESPEasy_cmd_e::longitude: COMMAND_CASE_A(Command_Longitude, -1); // LatitudeLongitude.h + #endif // if FEATURE_LAT_LONG_VAR_CMD case ESPEasy_cmd_e::looptimerset: COMMAND_CASE_A(Command_Loop_Timer_Set, 3); // Timers.h case ESPEasy_cmd_e::looptimerset_ms: COMMAND_CASE_A(Command_Loop_Timer_Set_ms, 3); // Timers.h case ESPEasy_cmd_e::looptimersetandrun: COMMAND_CASE_A(Command_Loop_Timer_SetAndRun, 3); // Timers.h @@ -353,7 +375,15 @@ bool InternalCommands::executeInternalCommand() case ESPEasy_cmd_e::meminfo: COMMAND_CASE_A(Command_MemInfo, 0); // Diagnostic.h case ESPEasy_cmd_e::meminfodetail: COMMAND_CASE_A(Command_MemInfo_detail, 0); // Diagnostic.h #endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - case ESPEasy_cmd_e::name: COMMAND_CASE_R(Command_Settings_Name, 1); // Settings.h + case ESPEasy_cmd_e::name: COMMAND_CASE_R(Command_Settings_Name, 1); // Settings.h + case ESPEasy_cmd_e::networkdisable: COMMAND_CASE_R(Command_Network_Disable, 1); // Networks.h + case ESPEasy_cmd_e::networkenable: COMMAND_CASE_R(Command_Network_Enable, 1); // Networks.h +#if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + case ESPEasy_cmd_e::networkexportconfig: COMMAND_CASE_R(Command_Network_ExportConfig, 1); // Networks.h + case ESPEasy_cmd_e::networkimportconfig: COMMAND_CASE_R(Command_Network_ImportConfig, 2); // Networks.h +#endif + + case ESPEasy_cmd_e::nosleep: COMMAND_CASE_R(Command_System_NoSleep, 1); // System.h #if FEATURE_NOTIFIER case ESPEasy_cmd_e::notify: COMMAND_CASE_R(Command_Notifications_Notify, -1); // Notifications.h @@ -376,6 +406,9 @@ bool InternalCommands::executeInternalCommand() case ESPEasy_cmd_e::password: COMMAND_CASE_R(Command_Settings_Password, 1); // Settings.h #if FEATURE_POST_TO_HTTP case ESPEasy_cmd_e::posttohttp: COMMAND_CASE_A(Command_HTTP_PostToHTTP, -1); // HTTP.h +#if FEATURE_HTTP_TLS + case ESPEasy_cmd_e::posttohttps: COMMAND_CASE_A(Command_HTTP_PostToHTTPS, -1); // HTTP.h +#endif // if FEATURE_HTTP_TLS #endif // if FEATURE_POST_TO_HTTP #if FEATURE_CUSTOM_PROVISIONING case ESPEasy_cmd_e::provision: COMMAND_CASE_A(Command_Provisioning_Dispatcher, -1); // Provisioning.h @@ -399,6 +432,9 @@ bool InternalCommands::executeInternalCommand() #endif // if FEATURE_MQTT #if FEATURE_PUT_TO_HTTP case ESPEasy_cmd_e::puttohttp: COMMAND_CASE_A(Command_HTTP_PutToHTTP, -1); // HTTP.h +#if FEATURE_HTTP_TLS + case ESPEasy_cmd_e::puttohttps: COMMAND_CASE_A(Command_HTTP_PutToHTTPS, -1); // HTTP.h +#endif // if FEATURE_HTTP_TLS #endif // if FEATURE_PUT_TO_HTTP case ESPEasy_cmd_e::pwm: COMMAND_CASE_A(Command_GPIO_PWM, 4); // GPIO.h case ESPEasy_cmd_e::reboot: COMMAND_CASE_A(Command_System_Reboot, 0); // System.h @@ -421,6 +457,9 @@ bool InternalCommands::executeInternalCommand() #endif // if FEATURE_ESPEASY_P2P #if FEATURE_SEND_TO_HTTP case ESPEasy_cmd_e::sendtohttp: COMMAND_CASE_A(Command_HTTP_SendToHTTP, 3); // HTTP.h +#if FEATURE_HTTP_TLS + case ESPEasy_cmd_e::sendtohttps: COMMAND_CASE_A(Command_HTTP_SendToHTTPS, 3); // HTTP.h +#endif // if FEATURE_HTTP_TLS #endif // FEATURE_SEND_TO_HTTP case ESPEasy_cmd_e::sendtoudp: COMMAND_CASE_A(Command_UDP_SendToUPD, 3); // UDP.h case ESPEasy_cmd_e::sendtoudpmix: COMMAND_CASE_A(Command_UDP_SendToUPDMix, 3); // UDP.h @@ -471,7 +510,7 @@ bool InternalCommands::executeInternalCommand() case ESPEasy_cmd_e::wdconfig: COMMAND_CASE_R(Command_WD_Config, 3); // WD.h case ESPEasy_cmd_e::wdread: COMMAND_CASE_R(Command_WD_Read, 2); // WD.h #endif // ifndef LIMIT_BUILD_SIZE - +#if FEATURE_WIFI case ESPEasy_cmd_e::wifiallowap: COMMAND_CASE_R(Command_Wifi_AllowAP, 0); // WiFi.h case ESPEasy_cmd_e::wifiapmode: COMMAND_CASE_R(Command_Wifi_APMode, 0); // WiFi.h case ESPEasy_cmd_e::wificonnect: COMMAND_CASE_A(Command_Wifi_Connect, 0); // WiFi.h @@ -479,11 +518,14 @@ bool InternalCommands::executeInternalCommand() case ESPEasy_cmd_e::wifikey: COMMAND_CASE_R(Command_Wifi_Key, 1); // WiFi.h case ESPEasy_cmd_e::wifikey2: COMMAND_CASE_R(Command_Wifi_Key2, 1); // WiFi.h case ESPEasy_cmd_e::wifimode: COMMAND_CASE_R(Command_Wifi_Mode, 1); // WiFi.h +#if FEATURE_OTA_FW_UPDATE_ESP_HOSTED_MCU + case ESPEasy_cmd_e::wifiotahostedmcu: COMMAND_CASE_R(Command_Wifi_OTA_hosted_mcu, 1); // WiFi.h +#endif case ESPEasy_cmd_e::wifiscan: COMMAND_CASE_R(Command_Wifi_Scan, 0); // WiFi.h case ESPEasy_cmd_e::wifissid: COMMAND_CASE_R(Command_Wifi_SSID, 1); // WiFi.h case ESPEasy_cmd_e::wifissid2: COMMAND_CASE_R(Command_Wifi_SSID2, 1); // WiFi.h case ESPEasy_cmd_e::wifistamode: COMMAND_CASE_R(Command_Wifi_STAMode, 0); // WiFi.h - +#endif case ESPEasy_cmd_e::NotMatched: return false; diff --git a/src/src/Commands/InternalCommands_decoder.cpp b/src/src/Commands/InternalCommands_decoder.cpp index 0c6115f6e..ef04f453a 100644 --- a/src/src/Commands/InternalCommands_decoder.cpp +++ b/src/src/Commands/InternalCommands_decoder.cpp @@ -46,7 +46,9 @@ const char Internal_commands_c[] PROGMEM = #define Int_cmd_d_offset ESPEasy_cmd_e::datetime const char Internal_commands_d[] PROGMEM = "datetime|" +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS "debug|" +#endif "dec|" "deepsleep|" "delay|" @@ -57,18 +59,18 @@ const char Internal_commands_d[] PROGMEM = "dst|" ; +#if FEATURE_WIFI #define Int_cmd_e_offset ESPEasy_cmd_e::erasesdkwifi +#else +#define Int_cmd_e_offset ESPEasy_cmd_e::event +#endif const char Internal_commands_e[] PROGMEM = +#if FEATURE_WIFI "erasesdkwifi|" +#endif "event|" "executerules|" #if FEATURE_ETHERNET - "ethphyadr|" - "ethpinmdc|" - "ethpinmdio|" - "ethpinpower|" - "ethphytype|" - "ethclockmode|" "ethip|" "ethgateway|" "ethsubnet|" @@ -84,7 +86,9 @@ const char Internal_commands_fghij[] PROGMEM = "gateway|" "gpio|" "gpiotoggle|" +#if FEATURE_WIFI "hiddenssid|" +#endif "i2cscanner|" "inc|" "ip|" @@ -96,14 +100,24 @@ const char Internal_commands_fghij[] PROGMEM = #endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS ; +#if FEATURE_LAT_LONG_VAR_CMD +#define Int_cmd_l_offset ESPEasy_cmd_e::latitude +#else // if FEATURE_LAT_LONG_VAR_CMD #define Int_cmd_l_offset ESPEasy_cmd_e::let +#endif // if FEATURE_LAT_LONG_VAR_CMD const char Internal_commands_l[] PROGMEM = + #if FEATURE_LAT_LONG_VAR_CMD + "latitude|" + #endif // if FEATURE_LAT_LONG_VAR_CMD "let|" #if FEATURE_STRING_VARIABLES "letstr|" #endif "load|" "logentry|" + #if FEATURE_LAT_LONG_VAR_CMD + "longitude|" + #endif // if FEATURE_LAT_LONG_VAR_CMD "looptimerset|" "looptimerset_ms|" "looptimersetandrun|" @@ -141,6 +155,12 @@ const char Internal_commands_m[] PROGMEM = #define Int_cmd_no_offset ESPEasy_cmd_e::name const char Internal_commands_no[] PROGMEM = "name|" + "networkdisable|" + "networkenable|" +#if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + "networkexportconfig|" + "networkimportconfig|" +#endif "nosleep|" #if FEATURE_NOTIFIER "notify|" @@ -167,6 +187,9 @@ const char Internal_commands_p[] PROGMEM = #endif // #ifdef USES_P019 #if FEATURE_POST_TO_HTTP "posttohttp|" +#if FEATURE_HTTP_TLS + "posttohttps|" +#endif // if FEATURE_HTTP_TLS #endif // #if FEATURE_POST_TO_HTTP #if FEATURE_CUSTOM_PROVISIONING "provision|" @@ -188,6 +211,9 @@ const char Internal_commands_p[] PROGMEM = #endif // #if FEATURE_MQTT #if FEATURE_PUT_TO_HTTP "puttohttp|" +#if FEATURE_HTTP_TLS + "puttohttps|" +#endif // if FEATURE_HTTP_TLS #endif // #if FEATURE_PUT_TO_HTTP "pwm|" ; @@ -214,6 +240,9 @@ const char Internal_commands_s[] PROGMEM = #endif // #if FEATURE_ESPEASY_P2P #if FEATURE_SEND_TO_HTTP "sendtohttp|" +#if FEATURE_HTTP_TLS + "sendtohttps|" +#endif // if FEATURE_HTTP_TLS #endif // FEATURE_SEND_TO_HTTP "sendtoudp|" "sendtoudpmix|" @@ -269,8 +298,14 @@ const char Internal_commands_u[] PROGMEM = "usentp|" ; +#if FEATURE_WIFI || !defined(LIMIT_BUILD_SIZE) +#if FEATURE_WIFI #define Int_cmd_w_offset ESPEasy_cmd_e::wifiallowap +#else +#define Int_cmd_w_offset ESPEasy_cmd_e::wdconfig +#endif const char Internal_commands_w[] PROGMEM = +#if FEATURE_WIFI "wifiallowap|" "wifiapmode|" "wificonnect|" @@ -278,15 +313,20 @@ const char Internal_commands_w[] PROGMEM = "wifikey|" "wifikey2|" "wifimode|" +#if FEATURE_OTA_FW_UPDATE_ESP_HOSTED_MCU + "wifiotahostedmcu|" +#endif "wifiscan|" "wifissid|" "wifissid2|" "wifistamode|" +#endif #ifndef LIMIT_BUILD_SIZE "wdconfig|" "wdread|" #endif // ifndef LIMIT_BUILD_SIZE ; +#endif const char* getInternalCommand_Haystack_Offset(const char firstLetter, int& offset) { @@ -355,10 +395,12 @@ const char* getInternalCommand_Haystack_Offset(const char firstLetter, int& offs offset = static_cast(Int_cmd_u_offset); haystack = Internal_commands_u; break; +#if FEATURE_WIFI || !defined(LIMIT_BUILD_SIZE) case 'w': offset = static_cast(Int_cmd_w_offset); haystack = Internal_commands_w; break; +#endif default: return nullptr; diff --git a/src/src/Commands/InternalCommands_decoder.h b/src/src/Commands/InternalCommands_decoder.h index b5c3f5fa3..8a6d12a8f 100644 --- a/src/src/Commands/InternalCommands_decoder.h +++ b/src/src/Commands/InternalCommands_decoder.h @@ -31,7 +31,9 @@ enum class ESPEasy_cmd_e : uint8_t { controllerenable, datetime, +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS debug, +#endif dec, deepsleep, delay, @@ -40,17 +42,12 @@ enum class ESPEasy_cmd_e : uint8_t { #endif // #if FEATURE_PLUGIN_PRIORITY dns, dst, - +#if FEATURE_WIFI erasesdkwifi, +#endif event, executerules, #if FEATURE_ETHERNET - ethphyadr, - ethpinmdc, - ethpinmdio, - ethpinpower, - ethphytype, - ethclockmode, ethip, ethgateway, ethsubnet, @@ -64,7 +61,9 @@ enum class ESPEasy_cmd_e : uint8_t { gateway, gpio, gpiotoggle, +#if FEATURE_WIFI hiddenssid, +#endif i2cscanner, inc, @@ -76,12 +75,18 @@ enum class ESPEasy_cmd_e : uint8_t { jsonportstatus, #endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + #if FEATURE_LAT_LONG_VAR_CMD + latitude, + #endif // if FEATURE_LAT_LONG_VAR_CMD let, #if FEATURE_STRING_VARIABLES letstr, #endif // if FEATURE_STRING_VARIABLES load, logentry, + #if FEATURE_LAT_LONG_VAR_CMD + longitude, + #endif // if FEATURE_LAT_LONG_VAR_CMD looptimerset, looptimerset_ms, looptimersetandrun, @@ -113,6 +118,12 @@ enum class ESPEasy_cmd_e : uint8_t { #endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS name, + networkdisable, + networkenable, +#if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + networkexportconfig, + networkimportconfig, +#endif nosleep, #if FEATURE_NOTIFIER notify, @@ -137,6 +148,9 @@ enum class ESPEasy_cmd_e : uint8_t { #endif // #ifdef USES_P019 #if FEATURE_POST_TO_HTTP posttohttp, +#if FEATURE_HTTP_TLS + posttohttps, +#endif // if FEATURE_HTTP_TLS #endif // #if FEATURE_POST_TO_HTTP #if FEATURE_CUSTOM_PROVISIONING provision, @@ -158,6 +172,9 @@ enum class ESPEasy_cmd_e : uint8_t { #endif // #if FEATURE_MQTT #if FEATURE_PUT_TO_HTTP puttohttp, +#if FEATURE_HTTP_TLS + puttohttps, +#endif // if FEATURE_HTTP_TLS #endif // #if FEATURE_PUT_TO_HTTP pwm, @@ -178,6 +195,9 @@ enum class ESPEasy_cmd_e : uint8_t { #endif // #if FEATURE_ESPEASY_P2P #if FEATURE_SEND_TO_HTTP sendtohttp, +#if FEATURE_HTTP_TLS + sendtohttps, +#endif // if FEATURE_HTTP_TLS #endif // FEATURE_SEND_TO_HTTP sendtoudp, sendtoudpmix, @@ -226,6 +246,7 @@ enum class ESPEasy_cmd_e : uint8_t { unmonitorrange, usentp, +#if FEATURE_WIFI wifiallowap, wifiapmode, wificonnect, @@ -233,10 +254,14 @@ enum class ESPEasy_cmd_e : uint8_t { wifikey, wifikey2, wifimode, +#if FEATURE_OTA_FW_UPDATE_ESP_HOSTED_MCU + wifiotahostedmcu, +#endif wifiscan, wifissid, wifissid2, wifistamode, +#endif #ifndef LIMIT_BUILD_SIZE wdconfig, wdread, diff --git a/src/src/Commands/LatitudeLongitude.cpp b/src/src/Commands/LatitudeLongitude.cpp new file mode 100644 index 000000000..39edf883b --- /dev/null +++ b/src/src/Commands/LatitudeLongitude.cpp @@ -0,0 +1,13 @@ +#include "../Commands/Common.h" +#include "../Commands/LatitudeLongitude.h" +#include "../Globals/Settings.h" + +String Command_Latitude(struct EventStruct *event, + const char *Line) { + return Command_GetORSetFloatMinMax(event, F("Latitude:"), Line, &Settings.Latitude, 1, -90.0001f, 90.0001f); +} + +String Command_Longitude(struct EventStruct *event, + const char *Line) { + return Command_GetORSetFloatMinMax(event, F("Longitude:"), Line, &Settings.Longitude, 1, -180.0001f, 180.0001f); +} diff --git a/src/src/Commands/LatitudeLongitude.h b/src/src/Commands/LatitudeLongitude.h new file mode 100644 index 000000000..9e546bbc7 --- /dev/null +++ b/src/src/Commands/LatitudeLongitude.h @@ -0,0 +1,8 @@ +#pragma once + +#include "../../ESPEasy_common.h" + +String Command_Latitude(struct EventStruct *event, + const char *Line); +String Command_Longitude(struct EventStruct *event, + const char *Line); diff --git a/src/src/Commands/Networks.cpp b/src/src/Commands/Networks.cpp index 0db3581d0..85f47f699 100644 --- a/src/src/Commands/Networks.cpp +++ b/src/src/Commands/Networks.cpp @@ -1,22 +1,49 @@ #include "../Commands/Networks.h" +#include "../../ESPEasy/net/DataTypes/NetworkIndex.h" +#include "../../ESPEasy/net/ESPEasyNetwork.h" +#include "../../ESPEasy/net/Globals/NetworkState.h" +#include "../../ESPEasy/net/Helpers/NWAccessControl.h" +#include "../../ESPEasy/net/eth/ESPEasyEth.h" #include "../../ESPEasy_common.h" #include "../Commands/Common.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyEth.h" -#include "../Globals/NetworkState.h" #include "../Globals/Settings.h" #include "../Helpers/StringConverter.h" #include "../WebServer/AccessControl.h" - #if FEATURE_ETHERNET #include #endif +#if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS +#include "../Helpers/KeyValueWriter_JSON.h" +#include "../../ESPEasy/net/Helpers/NWPlugin_import_export.h" +#endif + +// networkIndex = (event->Par1 - 1); Par1 is here for 1 ... NETWORK_MAX +bool validNetworkVar(struct EventStruct *event, ESPEasy::net::networkIndex_t& networkIndex) +{ + if (event->Par1 <= 0) { return false; } + networkIndex = static_cast(event->Par1 - 1); + return validNetworkIndex(networkIndex); +} + +const __FlashStringHelper * Command_Network_Disable(struct EventStruct *event, const char *Line) +{ + ESPEasy::net::networkIndex_t networkIndex; + return return_command_boolean_result_flashstr(validNetworkVar(event, networkIndex) && setNetworkEnableStatus(networkIndex, false)); +} + +const __FlashStringHelper * Command_Network_Enable(struct EventStruct *event, const char *Line) +{ + ESPEasy::net::networkIndex_t networkIndex; + return return_command_boolean_result_flashstr(validNetworkVar(event, networkIndex) && setNetworkEnableStatus(networkIndex, true)); +} + + String Command_AccessInfo_Ls(struct EventStruct *event, const char* Line) { - return return_result(event, concat(F("Allowed IP range : "), describeAllowedIPrange())); + return return_result(event, concat(F("Allowed IP range : "), ESPEasy::net::describeAllowedIPrange())); } String Command_AccessInfo_Clear (struct EventStruct *event, const char* Line) @@ -27,24 +54,24 @@ String Command_AccessInfo_Clear (struct EventStruct *event, const char* Line) String Command_DNS (struct EventStruct *event, const char* Line) { - return Command_GetORSetIP(event, F("DNS:"), Line, Settings.DNS, NetworkDnsIP(0), 1); + return Command_GetORSetIP(event, F("DNS:"), Line, Settings.DNS, ESPEasy::net::NetworkDnsIP(0), 1); } String Command_Gateway (struct EventStruct *event, const char* Line) { - return Command_GetORSetIP(event, F("Gateway:"), Line, Settings.Gateway, NetworkGatewayIP(),1); + return Command_GetORSetIP(event, F("Gateway:"), Line, Settings.Gateway, ESPEasy::net::NetworkGatewayIP(),1); } String Command_IP (struct EventStruct *event, const char* Line) { - return Command_GetORSetIP(event, F("IP:"), Line, Settings.IP, NetworkLocalIP(),1); + return Command_GetORSetIP(event, F("IP:"), Line, Settings.IP, ESPEasy::net::NetworkLocalIP(),1); } #if FEATURE_USE_IPV6 String Command_show_all_IP6 (struct EventStruct *event, const char* Line) { // Only get all IPv6 addresses - IP6Addresses_t addresses = NetworkAllIPv6(); + IP6Addresses_t addresses = ESPEasy::net::NetworkAllIPv6(); String res; res += '['; bool first = true; @@ -65,45 +92,10 @@ String Command_show_all_IP6 (struct EventStruct *event, const char* Line) String Command_Subnet (struct EventStruct *event, const char* Line) { - return Command_GetORSetIP(event, F("Subnet:"), Line, Settings.Subnet, NetworkSubnetMask(), 1); + return Command_GetORSetIP(event, F("Subnet:"), Line, Settings.Subnet, ESPEasy::net::NetworkSubnetMask(), 1); } #if FEATURE_ETHERNET -String Command_ETH_Phy_Addr (struct EventStruct *event, const char* Line) -{ - return Command_GetORSetInt8_t(event, F("ETH_Phy_Addr:"), Line, reinterpret_cast(&Settings.ETH_Phy_Addr),1); -} - -String Command_ETH_Pin_mdc (struct EventStruct *event, const char* Line) -{ - return Command_GetORSetInt8_t(event, F("ETH_Pin_mdc_cs:"), Line, reinterpret_cast(&Settings.ETH_Pin_mdc_cs),1); -} - -String Command_ETH_Pin_mdio (struct EventStruct *event, const char* Line) -{ - return Command_GetORSetInt8_t(event, F("ETH_Pin_mdio_irq:"), Line, reinterpret_cast(&Settings.ETH_Pin_mdio_irq),1); -} - -String Command_ETH_Pin_power (struct EventStruct *event, const char* Line) -{ - return Command_GetORSetInt8_t(event, F("ETH_Pin_power_rst:"), Line, reinterpret_cast(&Settings.ETH_Pin_power_rst),1); -} - -String Command_ETH_Phy_Type (struct EventStruct *event, const char* Line) -{ - return Command_GetORSetInt8_t(event, F("ETH_Phy_Type:"), Line, reinterpret_cast(&Settings.ETH_Phy_Type),1); -} - -String Command_ETH_Clock_Mode (struct EventStruct *event, const char* Line) -{ - return Command_GetORSetETH(event, - F("ETH_Clock_Mode:"), - toString(Settings.ETH_Clock_Mode), - Line, - reinterpret_cast(&Settings.ETH_Clock_Mode), - 1); -} - String Command_ETH_IP (struct EventStruct *event, const char* Line) { return Command_GetORSetIP(event, F("ETH_IP:"), Line, Settings.ETH_IP,ETH.localIP(),1); @@ -126,7 +118,7 @@ String Command_ETH_DNS (struct EventStruct *event, const char* Line) String Command_ETH_Wifi_Mode (struct EventStruct *event, const char* Line) { - const NetworkMedium_t orig_medium = Settings.NetworkMedium; + const ESPEasy::net::NetworkMedium_t orig_medium = Settings.NetworkMedium; const String result = Command_GetORSetETH(event, F("NetworkMedium:"), toString(active_network_medium), @@ -138,7 +130,7 @@ String Command_ETH_Wifi_Mode (struct EventStruct *event, const char* Line) Settings.NetworkMedium = orig_medium; return return_command_failed(); } - setNetworkMedium(Settings.NetworkMedium); + ESPEasy::net::setNetworkMedium(Settings.NetworkMedium); } return result; @@ -147,13 +139,51 @@ String Command_ETH_Wifi_Mode (struct EventStruct *event, const char* Line) String Command_ETH_Disconnect (struct EventStruct *event, const char* Line) { - ethPower(0); + // FIXME TD-er: Must implement support for multiple and default eth interface +/* + ESPEasy::net::eth::ethPower(0); delay(400); // ethPower(1); - setNetworkMedium(NetworkMedium_t::Ethernet); - ETHConnectRelaxed(); - + ESPEasy::net::setNetworkMedium(ESPEasy::net::NetworkMedium_t::Ethernet); + ESPEasy::net::eth::ETHConnectRelaxed(); +*/ return return_command_success(); } #endif // if FEATURE_ETHERNET + + +#if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + +String Command_Network_ExportConfig(struct EventStruct *event, const char*Line) +{ + ESPEasy::net::networkIndex_t networkIndex; + + if (!validNetworkVar(event, networkIndex)) { return return_command_failed(); } + + PrintToString p2s; + { + KeyValueWriter_JSON writer(false, &p2s); + + String res = ESPEasy::net::NWPlugin_import_export::exportConfig(networkIndex, &writer); + + if (!res.isEmpty()) { return res; } + } + return p2s.getMove(); +} + +String Command_Network_ImportConfig(struct EventStruct *event, const char*Line) +{ + ESPEasy::net::networkIndex_t networkIndex; + + if (!validNetworkVar(event, networkIndex)) { return return_command_failed(); } + const String json = parseStringToEndKeepCase(Line, 3); + + String res = ESPEasy::net::NWPlugin_import_export::importConfig(networkIndex, json); + + if (!res.isEmpty()) { return res; } + + return return_command_success(); +} + +#endif // if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS diff --git a/src/src/Commands/Networks.h b/src/src/Commands/Networks.h index 3ef0e8302..136c632bc 100644 --- a/src/src/Commands/Networks.h +++ b/src/src/Commands/Networks.h @@ -1,9 +1,16 @@ #ifndef COMMAND_NETWORKS_H #define COMMAND_NETWORKS_H +#include "../../ESPEasy_common.h" + class String; +const __FlashStringHelper* Command_Network_Disable(struct EventStruct *event, + const char *Line); +const __FlashStringHelper* Command_Network_Enable(struct EventStruct *event, + const char *Line); + String Command_AccessInfo_Ls(struct EventStruct *event, const char* Line); String Command_AccessInfo_Clear (struct EventStruct *event, const char* Line); String Command_DNS (struct EventStruct *event, const char* Line); @@ -14,12 +21,6 @@ String Command_show_all_IP6 (struct EventStruct *event, const char* Line); #endif String Command_Subnet (struct EventStruct *event, const char* Line); #if FEATURE_ETHERNET -String Command_ETH_Phy_Addr (struct EventStruct *event, const char* Line); -String Command_ETH_Pin_mdc (struct EventStruct *event, const char* Line); -String Command_ETH_Pin_mdio (struct EventStruct *event, const char* Line); -String Command_ETH_Pin_power (struct EventStruct *event, const char* Line); -String Command_ETH_Phy_Type (struct EventStruct *event, const char* Line); -String Command_ETH_Clock_Mode (struct EventStruct *event, const char* Line); String Command_ETH_IP (struct EventStruct *event, const char* Line); String Command_ETH_Gateway (struct EventStruct *event, const char* Line); String Command_ETH_Subnet (struct EventStruct *event, const char* Line); @@ -27,5 +28,9 @@ String Command_ETH_DNS (struct EventStruct *event, const char* Line); String Command_ETH_Wifi_Mode (struct EventStruct *event, const char* Line); String Command_ETH_Disconnect (struct EventStruct *event, const char* Line); #endif +#if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS +String Command_Network_ExportConfig (struct EventStruct *event, const char* Line); +String Command_Network_ImportConfig (struct EventStruct *event, const char* Line); +#endif #endif // COMMAND_NETWORKS_H diff --git a/src/src/Commands/Provisioning.cpp b/src/src/Commands/Provisioning.cpp index ce7ba429a..1feaec561 100644 --- a/src/src/Commands/Provisioning.cpp +++ b/src/src/Commands/Provisioning.cpp @@ -60,6 +60,14 @@ String Command_Provisioning_Provision() return downloadFileType(FileType::PROVISIONING_DAT); } +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE +String Command_Provisioning_DeviceSecurity() +{ + return downloadFileType(FileType::DEV_SECURITY_DAT); +} +#endif + + String Command_Provisioning_Rules(struct EventStruct *event) { if ((event->Par2 <= 0) || (event->Par2 > 4)) { @@ -102,6 +110,9 @@ String Command_Provisioning_NotificationFallback(struct EventStruct *event, cons return Command_Provisioning_Notification(); } +# endif // if FEATURE_NOTIFIER + + String Command_Provisioning_ProvisionFallback(struct EventStruct *event, const char *Line) { Command_Provisioning_DeprecatedMessage(F("Provision")); @@ -130,7 +141,6 @@ String Command_Provisioning_FirmwareFallback(struct EventStruct *event, const ch return error; } -# endif // if FEATURE_NOTIFIER # endif // ifdef PLUGIN_BUILD_MAX_ESP32 diff --git a/src/src/Commands/Provisioning.h b/src/src/Commands/Provisioning.h index 99b504c1f..c7ebc3fd9 100644 --- a/src/src/Commands/Provisioning.h +++ b/src/src/Commands/Provisioning.h @@ -16,6 +16,10 @@ String Command_Provisioning_Notification(); # endif // if FEATURE_NOTIFIER String Command_Provisioning_Provision(); +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE +String Command_Provisioning_DeviceSecurity(); +#endif + String Command_Provisioning_Rules(struct EventStruct *event); String Command_Provisioning_Firmware(struct EventStruct *event, diff --git a/src/src/Commands/Rules.cpp b/src/src/Commands/Rules.cpp index cbf2471e9..5339986ae 100644 --- a/src/src/Commands/Rules.cpp +++ b/src/src/Commands/Rules.cpp @@ -54,7 +54,7 @@ const __FlashStringHelper * Command_Rules_Events(struct EventStruct *event, cons { if (Settings.UseRules) { const bool executeImmediately = - SourceNeedsStatusUpdate(event->Source) || + EventValueSource::SourceNeedsStatusUpdate(event->Source) || event->Source == EventValueSource::Enum::VALUE_SOURCE_RULES || event->Source == EventValueSource::Enum::VALUE_SOURCE_RULES_RESTRICTED; diff --git a/src/src/Commands/Servo.cpp b/src/src/Commands/Servo.cpp index 1b8f6008a..47adbb826 100644 --- a/src/src/Commands/Servo.cpp +++ b/src/src/Commands/Servo.cpp @@ -59,7 +59,9 @@ const __FlashStringHelper * Command_Servo(struct EventStruct *event, const char tempStatus.monitor = 0; tempStatus.command = 0; savePortStatus(key, tempStatus); +#ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, concat(log, F(" Servo detached"))); +#endif return return_command_success_flashstr(); } diff --git a/src/src/Commands/Settings.cpp b/src/src/Commands/Settings.cpp index cca0dd7f7..c89d9e402 100644 --- a/src/src/Commands/Settings.cpp +++ b/src/src/Commands/Settings.cpp @@ -6,7 +6,7 @@ #include "../CustomBuild/CompiletimeDefines.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../../ESPEasy/net/ESPEasyNetwork.h" #include "../ESPEasyCore/Serial.h" #include "../Globals/SecuritySettings.h" @@ -92,7 +92,7 @@ const __FlashStringHelper * Command_Settings_Print(struct EventStruct *event, co serialPrintln(); serialPrintln(F("System Info")); - serialPrint(F(" IP Address : ")); serialPrintln(formatIP(NetworkLocalIP())); + serialPrint(F(" IP Address : ")); serialPrintln(formatIP(ESPEasy::net::NetworkLocalIP())); serialPrint(F(" Build : ")); serialPrintln(String(get_build_nr()) + '/' + getSystemBuildString()); serialPrint(F(" Name : ")); serialPrintln(Settings.getName()); serialPrint(F(" Unit : ")); serialPrintln(String(static_cast(Settings.Unit))); diff --git a/src/src/Commands/Tasks.cpp b/src/src/Commands/Tasks.cpp index f1ca699d4..192602e89 100644 --- a/src/src/Commands/Tasks.cpp +++ b/src/src/Commands/Tasks.cpp @@ -16,6 +16,7 @@ #include "../Globals/RuntimeData.h" #include "../Globals/Settings.h" +#include "../Helpers/ESPEasy_UnitOfMeasure.h" #include "../Helpers/Misc.h" #include "../Helpers/Numerical.h" #include "../Helpers/StringConverter.h" @@ -128,25 +129,48 @@ const __FlashStringHelper * taskValueSet(struct EventStruct *event, const char * EventStruct tmpEvent(taskIndex); if (GetArgv(Line, TmpStr1, 4)) { const Sensor_VType sensorType = tmpEvent.getSensorType(); +#if FEATURE_EXTENDED_TASK_VALUE_TYPES + bool done = false; + if (isUInt64OutputDataType(sensorType)) + { + // Don't try to do calculations as storing as double will loose accuracy + uint64_t val{}; + if (validUInt64FromString(TmpStr1, val)) { + done = true; + UserVar.setUint64(taskIndex, varNr, val); + } + } else if (isInt64OutputDataType(sensorType)) + { + // Don't try to do calculations as storing as double will loose accuracy + int64_t val{}; + if (validInt64FromString(TmpStr1, val)) { + done = true; + UserVar.setInt64(taskIndex, varNr, val); + } + } + if (!done) { +#endif + // FIXME TD-er: Must check if the value has to be computed and not convert to double when sensor type is 64 bit int. - // FIXME TD-er: Must check if the value has to be computed and not convert to double when sensor type is 64 bit int. + // Perform calculation with float result. + ESPEASY_RULES_FLOAT_TYPE result{}; - // Perform calculation with float result. - ESPEASY_RULES_FLOAT_TYPE result{}; + if (isError(Calculate(TmpStr1, result))) { + success = false; + return F("CALCULATION_ERROR"); + } + #ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, strformat( + F("taskValueSet: %s taskindex: %d varNr: %d result: %f type: %d"), + Line, + taskIndex, + varNr, result, sensorType)); + #endif - if (isError(Calculate(TmpStr1, result))) { - success = false; - return F("CALCULATION_ERROR"); + UserVar.set(taskIndex, varNr, result, sensorType); +#if FEATURE_EXTENDED_TASK_VALUE_TYPES } - #ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_INFO, strformat( - F("taskValueSet: %s taskindex: %d varNr: %d result: %f type: %d"), - Line, - taskIndex, - varNr, result, sensorType)); - #endif - - UserVar.set(taskIndex, varNr, result, sensorType); +#endif } else { // TODO: Get Task description and var name serialPrintln(formatUserVarNoCheck(&tmpEvent, varNr)); diff --git a/src/src/Commands/UDP.cpp b/src/src/Commands/UDP.cpp index ea539c337..3a0ee63fb 100644 --- a/src/src/Commands/UDP.cpp +++ b/src/src/Commands/UDP.cpp @@ -4,11 +4,11 @@ #include "../../ESPEasy_common.h" #include "../Commands/Common.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../Globals/NetworkState.h" +#include "../../ESPEasy/net/ESPEasyNetwork.h" +#include "../../ESPEasy/net/Globals/NetworkState.h" #include "../Globals/Settings.h" #include "../Helpers/Misc.h" -#include "../Helpers/Network.h" +#include "../Helpers/NetworkStatusLED.h" #include "../Helpers/Networking.h" #include "../Helpers/StringConverter.h" #include "../Helpers/StringParser.h" @@ -61,7 +61,7 @@ const __FlashStringHelper* Command_UDP_SendToUPDMix(struct EventStruct *event, c const __FlashStringHelper* Command_UDP_SendToUPD(struct EventStruct *event, const char *Line, const bool handleMix) { - if (NetworkConnected()) { + if (ESPEasy::net::NetworkConnected()) { const String ip = parseString(Line, 2); const int port = parseCommandArgumentInt(Line, 2, -1); @@ -82,20 +82,26 @@ const __FlashStringHelper* Command_UDP_SendToUPD(struct EventStruct *event, cons IPAddress UDP_IP; if (UDP_IP.fromString(ip)) { + WiFiUDP udp; + + if (!beginWiFiUDP_randomPort(udp)) { + return return_command_failed_flashstr(); + } + FeedSW_watchdog(); - portUDP.beginPacket(UDP_IP, port); + udp.beginPacket(UDP_IP, port); if (handleMix) { - portUDP.write(&argument[0], argument.size()); + udp.write(&argument[0], argument.size()); } else { #if defined(ESP8266) - portUDP.write(message.c_str(), message.length()); + udp.write(message.c_str(), message.length()); #endif // if defined(ESP8266) #if defined(ESP32) - portUDP.write(reinterpret_cast(message.c_str()), message.length()); + udp.write(reinterpret_cast(message.c_str()), message.length()); #endif // if defined(ESP32) } - portUDP.endPacket(); + udp.endPacket(); FeedSW_watchdog(); delay(0); } diff --git a/src/src/Commands/WiFi.cpp b/src/src/Commands/WiFi.cpp index 6966d1088..6221bdf08 100644 --- a/src/src/Commands/WiFi.cpp +++ b/src/src/Commands/WiFi.cpp @@ -2,14 +2,15 @@ #include "../../ESPEasy_common.h" +#if FEATURE_WIFI + #include "../Commands/Common.h" #include "../DataStructs/ESPEasy_EventStruct.h" -#include "../ESPEasyCore/ESPEasyWifi.h" #include "../ESPEasyCore/Serial.h" -#include "../Globals/ESPEasyWiFiEvent.h" +#include "../../ESPEasy/net/Globals/ESPEasyWiFiEvent.h" #include "../Globals/RTC.h" #include "../Globals/SecuritySettings.h" #include "../Globals/Settings.h" @@ -17,6 +18,9 @@ #include "../Helpers/ESPEasy_Storage.h" #include "../Helpers/StringConverter.h" +#include "../../ESPEasy/net/wifi/ESPEasyWifi.h" + + #define WIFI_MODE_MAX (WiFiMode_t)4 @@ -74,33 +78,33 @@ String Command_Wifi_HiddenSSID(struct EventStruct *event, const char *Line) const __FlashStringHelper* Command_Wifi_Scan(struct EventStruct *event, const char *Line) { - WiFiScan_log_to_serial(); + ESPEasy::net::wifi::WiFiScan_log_to_serial(); return return_command_success_flashstr(); } const __FlashStringHelper* Command_Wifi_Connect(struct EventStruct *event, const char *Line) { - WiFiEventData.wifiConnectAttemptNeeded = true; +// WiFiEventData.wifiConnectAttemptNeeded = true; return return_command_success_flashstr(); } const __FlashStringHelper* Command_Wifi_Disconnect(struct EventStruct *event, const char *Line) { RTC.clearLastWiFi(); // Force a WiFi scan - WifiDisconnect(); + ESPEasy::net::wifi::WifiDisconnect(); return return_command_success_flashstr(); } const __FlashStringHelper* Command_Wifi_APMode(struct EventStruct *event, const char *Line) { - setAP(true); + ESPEasy::net::wifi::setAPinternal(true); return return_command_success_flashstr(); } const __FlashStringHelper* Command_Wifi_STAMode(struct EventStruct *event, const char *Line) { - setSTA(true); + ESPEasy::net::wifi::setSTA(true); return return_command_success_flashstr(); } @@ -123,19 +127,19 @@ String Command_Wifi_Mode(struct EventStruct *event, const char *Line) } if ((mode >= WIFI_OFF) && (mode < WIFI_MODE_MAX)) { - setWifiMode(mode); + ESPEasy::net::wifi::setWifiMode(mode); } else { return return_result(event, F("Wifi Mode: invalid arguments")); } } else { - return return_result(event, concat(F("WiFi Mode:"), getWifiModeString(WiFi.getMode()))); + return return_result(event, concat(F("WiFi Mode:"), ESPEasy::net::wifi::getWifiModeString(WiFi.getMode()))); } return return_command_success(); } const __FlashStringHelper* Command_Wifi_AllowAP(struct EventStruct *event, const char *Line) { - Settings.DoNotStartAP(false); + Settings.DoNotStartAPfallback_ConnectFail(false); return return_command_success_flashstr(); } @@ -147,3 +151,50 @@ const __FlashStringHelper* Command_WiFi_Erase(struct EventStruct *event, const c ? return_command_success_flashstr() : return_command_failed_flashstr(); } + + +#if FEATURE_OTA_FW_UPDATE_ESP_HOSTED_MCU +# include "esp_hosted.h" + +// Perform OTA update on the esp-hosted-mcu firmware of the external WiFi module (typically ESP32-C6) +// Latest builds are available here: +// https://pioarduino.github.io/esp-hosted-mcu-firmware/ +String Command_Wifi_OTA_hosted_mcu( + struct EventStruct *event, const char *Line) +{ + + String url; + + if (GetArgv(Line, url, 2)) { + if (!url.startsWith(F("http://"))) { + String str(concat(F("OTA update esp-hosted-mcu not HTTP url: "), url)); + addLog(LOG_LEVEL_ERROR, str); + return str; + } + + if (!url.endsWith(F("network_adapter.bin"))) { + String str(concat(F("OTA update esp-hosted-mcu not correct file: "), url)); + addLog(LOG_LEVEL_ERROR, str); + return str; + } + addLog(LOG_LEVEL_INFO, concat(F("OTA update esp-hosted-mcu: "), url)); + +// FIXME TD-er: Must implement new API. See https://github.com/espressif/esp-hosted-mcu/blob/main/examples/host_performs_slave_ota/main/main.c +// Current implementation does not work, so will disable for now... +/* + esp_err_t err = esp_hosted_slave_ota(url.c_str()); + + if (err != ESP_OK) { + String str(strformat(F("Failed to start OTA update: %s"), esp_err_to_name(err))); + addLog(LOG_LEVEL_ERROR, str); + return str; + } +*/ + } + return return_command_success_flashstr(); +} + +# endif // ifdef ESP32P4 + + +#endif \ No newline at end of file diff --git a/src/src/Commands/WiFi.h b/src/src/Commands/WiFi.h index ba81aff0e..41582e0b9 100644 --- a/src/src/Commands/WiFi.h +++ b/src/src/Commands/WiFi.h @@ -1,6 +1,9 @@ #ifndef COMMAND_WIFI_H #define COMMAND_WIFI_H +#include "../../ESPEasy_common.h" + +#if FEATURE_WIFI #include String Command_Wifi_SSID(struct EventStruct *event, @@ -33,4 +36,12 @@ const __FlashStringHelper* Command_Wifi_AllowAP(struct EventStruct *event, const __FlashStringHelper* Command_WiFi_Erase(struct EventStruct *event, const char *Line); +#if FEATURE_OTA_FW_UPDATE_ESP_HOSTED_MCU +// Perform OTA update on the esp-hosted-mcu firmware of the external WiFi module (typically ESP32-C6) +String Command_Wifi_OTA_hosted_mcu(struct EventStruct *event, + const char *Line); + +#endif + +#endif #endif // COMMAND_WIFI_H diff --git a/src/src/ControllerQueue/C023_queue_element.cpp b/src/src/ControllerQueue/C023_queue_element.cpp new file mode 100644 index 000000000..565e61038 --- /dev/null +++ b/src/src/ControllerQueue/C023_queue_element.cpp @@ -0,0 +1,41 @@ +#include "../ControllerQueue/C023_queue_element.h" + +#ifdef USES_C023 + +# include "../DataStructs/ESPEasy_EventStruct.h" +# include "../DataStructs/UnitMessageCount.h" + +# include "../ESPEasyCore/ESPEasy_Log.h" + +# include "../Helpers/_CPlugin_LoRa_TTN_helper.h" +# include "../Helpers/StringConverter.h" + +C023_queue_element::C023_queue_element(struct EventStruct *event, uint8_t sampleSetCount) +{ + _controller_idx = event->ControllerIndex; + _taskIndex = event->TaskIndex; + # if FEATURE_PACKED_RAW_DATA + move_special(packed, getPackedFromPlugin(event, sampleSetCount)); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("C023 queue element: "), packed)); + } + # endif // if FEATURE_PACKED_RAW_DATA +} + +size_t C023_queue_element::getSize() const { + return sizeof(*this) + packed.length(); +} + +bool C023_queue_element::isDuplicate(const Queue_element_base& other) const { + const C023_queue_element& oth = static_cast(other); + + if ((oth._controller_idx != _controller_idx) || + (oth._taskIndex != _taskIndex) || + (oth.packed != packed)) { + return false; + } + return true; +} + +#endif // ifdef USES_C023 diff --git a/src/src/ControllerQueue/C023_queue_element.h b/src/src/ControllerQueue/C023_queue_element.h new file mode 100644 index 000000000..e5e1fc4d5 --- /dev/null +++ b/src/src/ControllerQueue/C023_queue_element.h @@ -0,0 +1,51 @@ +#ifndef CONTROLLERQUEUE_C023_QUEUE_ELEMENT_H +#define CONTROLLERQUEUE_C023_QUEUE_ELEMENT_H + +#include "../../ESPEasy_common.h" + +#ifdef USES_C023 + +# include "../ControllerQueue/Queue_element_base.h" +# include "../CustomBuild/ESPEasyLimits.h" +# include "../Globals/CPlugins.h" + + +struct EventStruct; +struct UnitMessageCount_t; + +/*********************************************************************************************\ +* C023_queue_element for queueing requests for C023: AT-command LoRaWAN +\*********************************************************************************************/ + + +class C023_queue_element : public Queue_element_base { +public: + + C023_queue_element() = default; + + C023_queue_element(const C023_queue_element& other) = delete; + + C023_queue_element(C023_queue_element&& other) = default; + + C023_queue_element(struct EventStruct *event, + uint8_t sampleSetCount); + + size_t getSize() const; + + bool isDuplicate(const Queue_element_base& other) const; + + const UnitMessageCount_t* getUnitMessageCount() const { + return nullptr; + } + + UnitMessageCount_t* getUnitMessageCount() { + return nullptr; + } + + String packed; +}; + +#endif // USES_C023 + + +#endif // CONTROLLERQUEUE_C023_QUEUE_ELEMENT_H diff --git a/src/src/ControllerQueue/ControllerDelayHandlerStruct.cpp b/src/src/ControllerQueue/ControllerDelayHandlerStruct.cpp index 9bdb7ac0f..299d0ee33 100644 --- a/src/src/ControllerQueue/ControllerDelayHandlerStruct.cpp +++ b/src/src/ControllerQueue/ControllerDelayHandlerStruct.cpp @@ -75,21 +75,23 @@ bool ControllerDelayHandlerStruct::queueFull(controllerIndex_t controller_idx) c int freeHeap = FreeMem(); { /* - #ifdef USE_SECOND_HEAP - const int freeHeap2 = FreeMem2ndHeap(); + #ifdef USE_SECOND_HEAP + const int freeHeap2 = FreeMem2ndHeap(); - if (freeHeap2 < freeHeap) { - freeHeap = freeHeap2; - } - #endif // ifdef USE_SECOND_HEAP - */ + if (freeHeap2 < freeHeap) { + freeHeap = freeHeap2; + } + #endif // ifdef USE_SECOND_HEAP + */ } #ifdef ESP32 - if (freeHeap > 50000) -#else - if (freeHeap > 5000) -#endif + + if (freeHeap > 50000) +#else // ifdef ESP32 + + if (freeHeap > 5000) +#endif // ifdef ESP32 { return false; // Memory is not an issue. } @@ -146,10 +148,11 @@ bool ControllerDelayHandlerStruct::isDuplicate(const Queue_element_base& element // Try to add to the queue, if permitted by "delete_oldest" // Return true when item was added, or skipped as it was considered a duplicate -bool ControllerDelayHandlerStruct::addToQueue(std::unique_ptrelement) { - if (!element) { +bool ControllerDelayHandlerStruct::addToQueue(UP_Queue_element_base element) { + if (!element) { return false; } + if (isDuplicate(*element)) { return true; } @@ -165,11 +168,12 @@ bool ControllerDelayHandlerStruct::addToQueue(std::unique_ptr_controller_idx)) { #ifdef USE_SECOND_HEAP + // Do not store in 2nd heap, std::list cannot handle 2nd heap well HeapSelectDram ephemeral; #endif // ifdef USE_SECOND_HEAP - sendQueue.push_back(std::move(element)); + sendQueue.emplace_back(std::move(element)); return true; } @@ -213,7 +217,7 @@ Queue_element_base * ControllerDelayHandlerStruct::getNext() { // Mark as processed and return time to schedule for next process. // Return 0 when nothing to process. // @param remove_from_queue indicates whether the elements should be removed from the queue. -unsigned long ControllerDelayHandlerStruct::markProcessed(bool remove_from_queue) { +uint32_t ControllerDelayHandlerStruct::markProcessed(bool remove_from_queue) { if (sendQueue.empty()) { return 0; } if (remove_from_queue) { @@ -226,9 +230,9 @@ unsigned long ControllerDelayHandlerStruct::markProcessed(bool remove_from_queue return getNextScheduleTime(); } -unsigned long ControllerDelayHandlerStruct::getNextScheduleTime() const { +uint32_t ControllerDelayHandlerStruct::getNextScheduleTime() const { if (sendQueue.empty()) { return 0; } - unsigned long nextTime = lastSend + minTimeBetweenMessages; + uint32_t nextTime = lastSend + minTimeBetweenMessages; if (timePassedSince(nextTime) > 0) { nextTime = millis(); @@ -242,9 +246,7 @@ unsigned long ControllerDelayHandlerStruct::getNextScheduleTime() const { // Set the "lastSend" to "now" + some additional delay. // This will cause the next schedule time to be delayed to // msecFromNow + minTimeBetweenMessages -void ControllerDelayHandlerStruct::setAdditionalDelay(unsigned long msecFromNow) { - lastSend = millis() + msecFromNow; -} +void ControllerDelayHandlerStruct::setAdditionalDelay(uint32_t msecFromNow) { lastSend = millis() + msecFromNow; } size_t ControllerDelayHandlerStruct::getQueueMemorySize() const { size_t totalSize = 0; @@ -258,10 +260,10 @@ size_t ControllerDelayHandlerStruct::getQueueMemorySize() const { } void ControllerDelayHandlerStruct::process( - cpluginID_t cpluginID, - do_process_function func, - TimingStatsElements timerstats_id, - SchedulerIntervalTimer_e timerID) + cpluginID_t cpluginID, + do_process_function func, + TimingStatsElements timerstats_id, + SchedulerIntervalTimer_e timerID) { Queue_element_base *element(static_cast(getNext())); diff --git a/src/src/ControllerQueue/ControllerDelayHandlerStruct.h b/src/src/ControllerQueue/ControllerDelayHandlerStruct.h index f5442cf50..c67901ed5 100644 --- a/src/src/ControllerQueue/ControllerDelayHandlerStruct.h +++ b/src/src/ControllerQueue/ControllerDelayHandlerStruct.h @@ -20,8 +20,8 @@ #include "../Helpers/StringConverter.h" -#include -#include // For std::shared_ptr +#include +#include // For std::unique_ptr #include // std::nothrow #ifndef CONTROLLER_QUEUE_MINIMAL_EXPIRE_TIME @@ -50,7 +50,7 @@ struct ControllerDelayHandlerStruct { // Try to add to the queue, if permitted by "delete_oldest" // Return true when item was added, or skipped as it was considered a duplicate - bool addToQueue(std::unique_ptrelement); + bool addToQueue(UP_Queue_element_base element); // Get the next element. // Remove front element when max_retries is reached. @@ -59,35 +59,36 @@ struct ControllerDelayHandlerStruct { // Mark as processed and return time to schedule for next process. // Return 0 when nothing to process. // @param remove_from_queue indicates whether the elements should be removed from the queue. - unsigned long markProcessed(bool remove_from_queue); + uint32_t markProcessed(bool remove_from_queue); - unsigned long getNextScheduleTime() const; + uint32_t getNextScheduleTime() const; // Set the "lastSend" to "now" + some additional delay. // This will cause the next schedule time to be delayed to // msecFromNow + minTimeBetweenMessages - void setAdditionalDelay(unsigned long msecFromNow); + void setAdditionalDelay(uint32_t msecFromNow); size_t getQueueMemorySize() const; void process( - cpluginID_t cpluginID, - do_process_function func, - TimingStatsElements timerstats_id, + cpluginID_t cpluginID, + do_process_function func, + TimingStatsElements timerstats_id, SchedulerIntervalTimer_e timerID); - std::list >sendQueue; - mutable UnitLastMessageCount_map unitLastMessageCount; - unsigned long lastSend = 0; - unsigned int minTimeBetweenMessages = CONTROLLER_DELAY_QUEUE_DELAY_DFLT; - unsigned long expire_timeout = 0; - uint8_t max_queue_depth = CONTROLLER_DELAY_QUEUE_DEPTH_DFLT; - uint8_t attempt = 0; - uint8_t max_retries = CONTROLLER_DELAY_QUEUE_RETRY_DFLT; - bool delete_oldest = false; - bool must_check_reply = false; - bool deduplicate = false; - bool useLocalSystemTime = false; + std::dequesendQueue; + mutable UnitLastMessageCount_map unitLastMessageCount; + uint32_t lastSend = 0; + uint32_t minTimeBetweenMessages = CONTROLLER_DELAY_QUEUE_DELAY_DFLT; + uint32_t expire_timeout = 0; + uint8_t max_queue_depth = CONTROLLER_DELAY_QUEUE_DEPTH_DFLT; + uint8_t attempt = 0; + uint8_t max_retries = CONTROLLER_DELAY_QUEUE_RETRY_DFLT; + bool delete_oldest = false; + bool must_check_reply = false; + bool deduplicate = false; + bool useLocalSystemTime = false; + }; diff --git a/src/src/ControllerQueue/DelayQueueElements.cpp b/src/src/ControllerQueue/DelayQueueElements.cpp index 24bad6b34..4d432aeb3 100644 --- a/src/src/ControllerQueue/DelayQueueElements.cpp +++ b/src/src/ControllerQueue/DelayQueueElements.cpp @@ -167,11 +167,11 @@ DEFINE_Cxxx_DELAY_QUEUE_MACRO_CPP(0, 18) // -V522 #endif */ -/* - #ifdef USES_C023 - DEFINE_Cxxx_DELAY_QUEUE_MACRO_CPP(0, 23) // -V522 - #endif - */ + +#ifdef USES_C023 + DEFINE_Cxxx_DELAY_QUEUE_MACRO_CPP(0, 23) // -V522 +#endif + /* #ifdef USES_C024 diff --git a/src/src/ControllerQueue/DelayQueueElements.h b/src/src/ControllerQueue/DelayQueueElements.h index 3c44e89ed..71c91c672 100644 --- a/src/src/ControllerQueue/DelayQueueElements.h +++ b/src/src/ControllerQueue/DelayQueueElements.h @@ -9,6 +9,9 @@ #include "../ControllerQueue/Queue_element_base.h" #include "../DataStructs/ControllerSettingsStruct.h" +#include // For std::unique_ptr +#include // for std::nothrow + // The most logical place to have these queue element handlers defined would be in their // respective _Cxxx.ino file. @@ -56,6 +59,7 @@ void process_c##NNN####M##_delay_queue(); \ bool init_c##NNN####M##_delay_queue(controllerIndex_t ControllerIndex); \ void exit_c##NNN####M##_delay_queue(); \ + typedef std::unique_ptr UP_C##NNN####M##_queue_element; # ifdef USE_SECOND_HEAP @@ -268,11 +272,12 @@ DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 18) #endif */ -/* + #ifdef USES_C023 + # include "../ControllerQueue/C023_queue_element.h" DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 23) #endif - */ + /* #ifdef USES_C024 diff --git a/src/src/ControllerQueue/MQTT_queue_element.h b/src/src/ControllerQueue/MQTT_queue_element.h index 4e0436cb5..fef4383de 100644 --- a/src/src/ControllerQueue/MQTT_queue_element.h +++ b/src/src/ControllerQueue/MQTT_queue_element.h @@ -55,6 +55,8 @@ public: bool _retained = false; }; +DEF_UP(MQTT_queue_element); + #endif // if FEATURE_MQTT #endif // CONTROLLERQUEUE_MQTT_QUEUE_ELEMENT_H diff --git a/src/src/ControllerQueue/Queue_element_base.h b/src/src/ControllerQueue/Queue_element_base.h index 68e5f6a43..8c0181228 100644 --- a/src/src/ControllerQueue/Queue_element_base.h +++ b/src/src/ControllerQueue/Queue_element_base.h @@ -36,4 +36,6 @@ public: bool _processByController; }; +DEF_UP(Queue_element_base); + #endif // ifndef CONTROLLERQUEUE_QUEUE_ELEMENT_BASE_H diff --git a/src/src/Controller_config/C018_config.h b/src/src/Controller_config/C018_config.h index 0bc9f4fde..a27b50700 100644 --- a/src/src/Controller_config/C018_config.h +++ b/src/src/Controller_config/C018_config.h @@ -48,6 +48,7 @@ struct C018_ConfigStruct uint32_t rx2_freq = 0; }; +DEF_UP(C018_ConfigStruct); #endif // ifdef USES_C018 diff --git a/src/src/Controller_config/C023_AT_commands.cpp b/src/src/Controller_config/C023_AT_commands.cpp new file mode 100644 index 000000000..92ae87533 --- /dev/null +++ b/src/src/Controller_config/C023_AT_commands.cpp @@ -0,0 +1,382 @@ +#include "../Controller_config/C023_AT_commands.h" + +#include "../Helpers/ESPEasy_time_calc.h" +#include "../Helpers/StringConverter.h" + +bool C023_timestamped_value::expired() const +{ + // TODO TD-er: Implement based on type of value + // return false; + return timePassedSince(lastCheck) > 60000; +} + +void C023_timestamped_value::set(const String& val) { + lastCheck = millis(); + + if (!value.equals(val)) { + lastChange = millis(); + value = val; + } +} + +const __FlashStringHelper * C023_AT_commands::toString(LoRaModule_e module) +{ + switch (module) + { + case LoRaModule_e::Dragino_LA66: return F("Dragino LA66"); + case LoRaModule_e::RAK_3172: return F("RAK 3172"); + case LoRaModule_e::MAX_TYPE: break; + } + return F(""); +} + +bool C023_AT_commands::isVolatileValue(C023_AT_commands::AT_cmd at_cmd) +{ + switch (at_cmd) + { + case C023_AT_commands::AT_cmd::NJS: + case C023_AT_commands::AT_cmd::DR: + case C023_AT_commands::AT_cmd::RX2DR: + // case C023_AT_commands::AT_cmd::FCD: + // case C023_AT_commands::AT_cmd::FCU: + // case C023_AT_commands::AT_cmd::RSSI: + // case C023_AT_commands::AT_cmd::SNR: + case C023_AT_commands::AT_cmd::BAT: + case C023_AT_commands::AT_cmd::TIMESTAMP: + case C023_AT_commands::AT_cmd::LTIME: + return true; + default: + return false; + } +} + +bool C023_AT_commands::supported(AT_cmd at_cmd, LoRaModule_e module) +{ + // Not supported commands + + if (module == LoRaModule_e::Dragino_LA66) { + switch (at_cmd) + { + case C023_AT_commands::AT_cmd::DEVADDR: + case C023_AT_commands::AT_cmd::DEVEUI: + case C023_AT_commands::AT_cmd::NETID: + case C023_AT_commands::AT_cmd::LTIME: + case C023_AT_commands::AT_cmd::CLIVER: + case C023_AT_commands::AT_cmd::HWMODEL: + case C023_AT_commands::AT_cmd::SN: + return false; + + default: return true; + } + } + + if (module == LoRaModule_e::RAK_3172) { + switch (at_cmd) + { + case C023_AT_commands::AT_cmd::DADDR: + case C023_AT_commands::AT_cmd::DEUI: + case C023_AT_commands::AT_cmd::NWKID: + case C023_AT_commands::AT_cmd::RECVB: + case C023_AT_commands::AT_cmd::UUID: + case C023_AT_commands::AT_cmd::FCD: + case C023_AT_commands::AT_cmd::FCU: + case C023_AT_commands::AT_cmd::PORT: + case C023_AT_commands::AT_cmd::RJTDC: + case C023_AT_commands::AT_cmd::RPL: + case C023_AT_commands::AT_cmd::SLEEP: + case C023_AT_commands::AT_cmd::CHS: + case C023_AT_commands::AT_cmd::TIMESTAMP: + case C023_AT_commands::AT_cmd::LEAPSEC: + case C023_AT_commands::AT_cmd::SYNCMOD: + case C023_AT_commands::AT_cmd::SYNCTDC: + case C023_AT_commands::AT_cmd::DDETECT: + case C023_AT_commands::AT_cmd::SETMAXNBTRANS: + return false; + + default: return true; + } + } + return false; +} + +const __FlashStringHelper * C023_AT_commands::toFlashString( + C023_AT_commands::AT_cmd at_cmd, LoRaModule_e module) +{ + // Uncrustify must not be used on macros, so turn it off + // *INDENT-OFF* + #define C023_AT_STR(S) case C023_AT_commands::AT_cmd::S: return F(#S); + // Uncrustify must not be used on macros, but we're now done, so turn Uncrustify on again + // *INDENT-ON* + + if (supported(at_cmd, module)) { + switch (at_cmd) + { + C023_AT_STR(UUID); + C023_AT_STR(VER); + C023_AT_STR(CLIVER); + C023_AT_STR(HWMODEL); + C023_AT_STR(SN); + C023_AT_STR(APPEUI); + C023_AT_STR(APPKEY); + C023_AT_STR(APPSKEY); + C023_AT_STR(DADDR); + C023_AT_STR(DEVADDR); + C023_AT_STR(DEUI); + C023_AT_STR(DEVEUI); + C023_AT_STR(NWKID); + C023_AT_STR(NETID); + C023_AT_STR(NWKSKEY); + C023_AT_STR(CFM); + C023_AT_STR(NJM); + C023_AT_STR(NJS); + C023_AT_STR(RECV); + C023_AT_STR(RECVB); + C023_AT_STR(ADR); + C023_AT_STR(CLASS); + C023_AT_STR(DCS); + C023_AT_STR(DR); + C023_AT_STR(FCD); + C023_AT_STR(FCU); + C023_AT_STR(JN1DL); + C023_AT_STR(JN2DL); + C023_AT_STR(PNM); + C023_AT_STR(RX1DL); + C023_AT_STR(RX2DL); + C023_AT_STR(RX2DR); + C023_AT_STR(RX2FQ); + C023_AT_STR(TXP); + C023_AT_STR(RSSI); + C023_AT_STR(SNR); + C023_AT_STR(PORT); + C023_AT_STR(CHS); + C023_AT_STR(SLEEP); + C023_AT_STR(BAT); + C023_AT_STR(RJTDC); + C023_AT_STR(RPL); + C023_AT_STR(TIMESTAMP); + C023_AT_STR(LTIME); + C023_AT_STR(LEAPSEC); + C023_AT_STR(SYNCMOD); + C023_AT_STR(SYNCTDC); + C023_AT_STR(DDETECT); + C023_AT_STR(SETMAXNBTRANS); + case C023_AT_commands::AT_cmd::Unknown: break; + } + } + return F("Unknown"); +#undef C023_AT_STR +} + +String C023_AT_commands::toString( + C023_AT_commands::AT_cmd at_cmd, LoRaModule_e module) { return concat(F("AT+"), toFlashString(at_cmd, module)); } + +const __FlashStringHelper * C023_AT_commands::toDisplayString(C023_AT_commands::AT_cmd at_cmd) +{ + switch (at_cmd) + { + case C023_AT_commands::AT_cmd::Unknown: break; + + case C023_AT_commands::AT_cmd::UUID: return F("UUID"); + case C023_AT_commands::AT_cmd::VER: return F("Firmware Version"); // 2.4 AT+VER + case C023_AT_commands::AT_cmd::CLIVER: return F("CLI Version"); + case C023_AT_commands::AT_cmd::HWMODEL: return F("Hardware Model"); + case C023_AT_commands::AT_cmd::SN: return F("Serial Number"); + case C023_AT_commands::AT_cmd::APPEUI: return F("Application EUI"); // 3.1 AT+APPEUI + case C023_AT_commands::AT_cmd::APPKEY: return F("Application Key"); // 3.2 AT+APPKEY + case C023_AT_commands::AT_cmd::APPSKEY: return F("Application Session Key"); // 3.3 AT+APPSKEY + case C023_AT_commands::AT_cmd::DADDR: + case C023_AT_commands::AT_cmd::DEVADDR: return F("Device Address"); // 3.4 AT+DADDR + case C023_AT_commands::AT_cmd::DEUI: + case C023_AT_commands::AT_cmd::DEVEUI: return F("Device EUI"); // 3.5 AT+DEUI + case C023_AT_commands::AT_cmd::NWKID: + case C023_AT_commands::AT_cmd::NETID: return F("Network ID"); // 3.6 AT+NWKID + case C023_AT_commands::AT_cmd::NWKSKEY: return F("Network Session Key"); // 3.7 AT+NWKSKEY + case C023_AT_commands::AT_cmd::CFM: return F("Confirm Mode"); // 4.1 AT+CFM + case C023_AT_commands::AT_cmd::NJM: return F("LoRa Network Join Mode"); // 4.3 AT+NJM + case C023_AT_commands::AT_cmd::NJS: return F("LoRa Network Join Status"); // 4.4 AT+NJS + case C023_AT_commands::AT_cmd::RECV: return F("Print Last Received Data in Raw Format"); // 4.5 AT+RECV + case C023_AT_commands::AT_cmd::RECVB: return F("Print Last Received Data in Binary Format"); // 4.6 AT+RECVB + case C023_AT_commands::AT_cmd::ADR: return F("Adaptive Rate"); // 5.1 AT+ADR + case C023_AT_commands::AT_cmd::CLASS: return F("LoRa Class"); // 5.2 AT+CLASS + case C023_AT_commands::AT_cmd::DCS: return F("Duty Cycle Setting"); // 5.3 AT+DCS + case C023_AT_commands::AT_cmd::DR: return F("Data Rate"); // 5.4 AT+DR + case C023_AT_commands::AT_cmd::FCD: return F("Frame Counter Downlink"); // 5.5 AT+FCD + case C023_AT_commands::AT_cmd::FCU: return F("Frame Counter Uplink"); // 5.6 AT+FCU + case C023_AT_commands::AT_cmd::JN1DL: return F("Join Accept Delay1"); // 5.7 AT+JN1DL + case C023_AT_commands::AT_cmd::JN2DL: return F("Join Accept Delay2"); // 5.8 AT+JN2DL + case C023_AT_commands::AT_cmd::PNM: return F("Public Network Mode"); // 5.9 AT+PNM + case C023_AT_commands::AT_cmd::RX1DL: return F("Receive Delay1"); // 5.10 AT+RX1DL + case C023_AT_commands::AT_cmd::RX2DL: return F("Receive Delay2"); // 5.11 AT+RX2DL + case C023_AT_commands::AT_cmd::RX2DR: return F("Rx2 Window Data Rate"); // 5.12 AT+RX2DR + case C023_AT_commands::AT_cmd::RX2FQ: return F("Rx2 Window Frequency"); // 5.13 AT+RX2FQ + case C023_AT_commands::AT_cmd::TXP: return F("Transmit Power"); // 5.14 AT+TXP + case C023_AT_commands::AT_cmd::RSSI: return F("RSSI of the Last Received Packet"); // 5.15 AT+RSSI + case C023_AT_commands::AT_cmd::SNR: return F("SNR of the Last Received Packet"); // 5.16 AT+SNR + case C023_AT_commands::AT_cmd::PORT: return F("Application Port"); // 5.17 AT+PORT + case C023_AT_commands::AT_cmd::CHS: return F("Single Channel Mode"); // 5.18 AT+ CHS + case C023_AT_commands::AT_cmd::SLEEP: return F("Sleep mode"); // 5.20 AT+SLEEP + case C023_AT_commands::AT_cmd::BAT: return F("Current battery voltage [mV]"); // 5.22 AT+BAT + case C023_AT_commands::AT_cmd::RJTDC: return F("ReJoin data transmission interval [min]"); // 5.23 AT+RJTDC + case C023_AT_commands::AT_cmd::RPL: return F("Response level"); // 5.24 AT+RPL + case C023_AT_commands::AT_cmd::TIMESTAMP: return F("UNIX timestamp [s]"); // 5.25 AT+TIMESTAMP + case C023_AT_commands::AT_cmd::LTIME: return F("Local Time"); // RAK: AT+TIMESTAMP + case C023_AT_commands::AT_cmd::LEAPSEC: return F("Leap Second"); // 5.26 AT+LEAPSEC + case C023_AT_commands::AT_cmd::SYNCMOD: return F("Time synchronization method"); // 5.27 AT+SYNCMOD + case C023_AT_commands::AT_cmd::SYNCTDC: return F("Time synchronization interval [days]"); // 5.28 AT+SYNCTDC + case C023_AT_commands::AT_cmd::DDETECT: return F("Downlink detection"); // 5.29 AT+DDETECT + case C023_AT_commands::AT_cmd::SETMAXNBTRANS: return F("Max nbtrans in LinkADR"); // 5.30 AT+SETMAXNBTRANS + } + return F(""); +} + +C023_AT_commands::AT_cmd C023_AT_commands::determineReceivedDataType(const String& receivedData) +{ + // Uncrustify must not be used on macros, so turn it off + // *INDENT-OFF* + #define MATCH_STRING(S) if (searchStr.startsWith(F(#S))) return C023_AT_commands::AT_cmd::S; + // Uncrustify must not be used on macros, but we're now done, so turn Uncrustify on again + // *INDENT-ON* + + + if ((receivedData.length() > 4) && receivedData.startsWith(F("AT+"))) { + String searchStr = receivedData.substring(3); + searchStr.trim(); + + switch (searchStr[0]) + { + case 'A': + MATCH_STRING(ADR); + MATCH_STRING(APPEUI); + MATCH_STRING(APPKEY); + MATCH_STRING(APPSKEY); + break; + case 'B': + MATCH_STRING(BAT); + break; + case 'C': + MATCH_STRING(CFM); + MATCH_STRING(CHS); + MATCH_STRING(CLASS); + MATCH_STRING(CLIVER); + break; + case 'D': + MATCH_STRING(DADDR); + MATCH_STRING(DCS); + MATCH_STRING(DDETECT); + MATCH_STRING(DEUI); + MATCH_STRING(DEVADDR); + MATCH_STRING(DEVEUI); + MATCH_STRING(DR); + break; + case 'F': + MATCH_STRING(FCD); + MATCH_STRING(FCU); + break; + case 'H': + MATCH_STRING(HWMODEL); + break; + case 'J': + MATCH_STRING(JN1DL); + MATCH_STRING(JN2DL); + break; + case 'L': + MATCH_STRING(LEAPSEC); + MATCH_STRING(LTIME); + break; + case 'N': + MATCH_STRING(NJM); + MATCH_STRING(NJS); + MATCH_STRING(NETID); + MATCH_STRING(NWKID); + MATCH_STRING(NWKSKEY); + break; + case 'P': + MATCH_STRING(PNM); + MATCH_STRING(PORT); + break; + case 'R': + MATCH_STRING(RECV); + MATCH_STRING(RECVB); + MATCH_STRING(RJTDC); + MATCH_STRING(RPL); + MATCH_STRING(RSSI); + MATCH_STRING(RX1DL); + MATCH_STRING(RX2DL); + MATCH_STRING(RX2DR); + MATCH_STRING(RX2FQ); + break; + case 'S': + MATCH_STRING(SETMAXNBTRANS); + MATCH_STRING(SLEEP); + MATCH_STRING(SN); + MATCH_STRING(SNR); + MATCH_STRING(SYNCMOD); + MATCH_STRING(SYNCTDC); + break; + case 'T': + MATCH_STRING(TIMESTAMP); + MATCH_STRING(TXP); + break; + case 'U': + MATCH_STRING(UUID); + break; + case 'V': + MATCH_STRING(VER); + break; + } + } + #undef MATCH_STRING + return C023_AT_commands::AT_cmd::Unknown; +} + +C023_AT_commands::AT_cmd C023_AT_commands::decode(const String& receivedData, String& value) +{ + const C023_AT_commands::AT_cmd res = determineReceivedDataType(receivedData); + + if (res != C023_AT_commands::AT_cmd::Unknown) { + const int pos = receivedData.indexOf('='); + + if (pos == -1) { + value.clear(); + } else { + value = receivedData.substring(pos + 1); + } + } + return res; +} + +KeyValueStruct C023_AT_commands::getKeyValue( + C023_AT_commands::AT_cmd at_cmd, + LoRaModule_e module, + const String & value, + bool extendedValue) +{ + if ((at_cmd != C023_AT_commands::AT_cmd::Unknown) && !value.isEmpty()) { + if ((at_cmd == C023_AT_commands::AT_cmd::NJS) || + (at_cmd == C023_AT_commands::AT_cmd::ADR) || + (at_cmd == C023_AT_commands::AT_cmd::DCS) || + (at_cmd == C023_AT_commands::AT_cmd::PNM) || + (at_cmd == C023_AT_commands::AT_cmd::CHS) || + + // (at_cmd == C023_AT_commands::AT_cmd::CFM) || + (at_cmd == C023_AT_commands::AT_cmd::SLEEP) || + (at_cmd == C023_AT_commands::AT_cmd::SYNCMOD)) + { + const bool boolValue = value.toInt() != 0; + + return KeyValueStruct( + extendedValue + ? toDisplayString(at_cmd) + : toFlashString(at_cmd, module), + boolValue); + } + + return KeyValueStruct( + extendedValue + ? toDisplayString(at_cmd) + : toFlashString(at_cmd, module), + value, + KeyValueStruct::Format::PreFormatted); + } + return KeyValueStruct(); +} diff --git a/src/src/Controller_config/C023_AT_commands.h b/src/src/Controller_config/C023_AT_commands.h new file mode 100644 index 000000000..a59999d99 --- /dev/null +++ b/src/src/Controller_config/C023_AT_commands.h @@ -0,0 +1,163 @@ +#pragma once + +#include + +#include "../DataStructs/KeyValueStruct.h" + +struct C023_timestamped_value { + C023_timestamped_value(String&& val) : + lastCheck(millis()), + value(std::move(val)) {} + + void set(const String& val); + + bool expired() const; + + uint32_t lastChange{}; + uint32_t lastCheck{}; + String value; + +}; + +class C023_AT_commands +{ +public: + + // Value is stored, so do not change numerical values + enum class LoRaModule_e : uint8_t { + Dragino_LA66 = 0, + RAK_3172 = 1, + + + MAX_TYPE // Leave as last, used to iterate over all enum values + + }; + + static const __FlashStringHelper* toString(LoRaModule_e module); + + + // Received types. + // These commands can be used to get and/or set a value. + enum class AT_cmd : size_t { + UUID, // + VER, // 2.4 AT+VER: Image Version and Frequency Band + CLIVER, // RAK: CLI Version, AT Command Version + HWMODEL, // RAK: HW Model + SN, // RAK: Serial Number + APPEUI, // 3.1 AT+APPEUI: Application EUI + APPKEY, // 3.2 AT+APPKEY: Application Key + DEUI, // 3.5 AT+DEUI: Device EUI + DEVEUI, + APPSKEY, // 3.3 AT+APPSKEY: Application Session Key + DADDR, // 3.4 AT+DADDR: Device Address + DEVADDR, + NWKSKEY, // 3.7 AT+NWKSKEY: Network Session Key + NWKID, // 3.6 AT+NWKID: Network ID(You can enter this command change only after successful network connection) + NETID, + CFM, // 4.1 AT+CFM: Confirm Mode + NJM, // 4.3 AT+NJM: LoRa® Network Join Mode + // 0 = ABP + // 1 = OTAA + // 2 = P2P + NJS, // 4.4 AT+NJS: LoRa® Network Join Status + RECV, // 4.5 AT+RECV: Print Last Received Data in Raw Format + RECVB, // 4.6 AT+RECVB: Print Last Received Data in Binary Format + ADR, // 5.1 AT+ADR: Adaptive Rate + CLASS, // 5.2 AT+CLASS: LoRa® Class(Currently only support class A, class C) + DCS, // 5.3 AT+DCS: Duty Cycle Setting + DR, // 5.4 AT+DR: Data Rate (Can Only be Modified after ADR=0) + FCD, // 5.5 AT+FCD: Frame Counter Downlink + FCU, // 5.6 AT+FCU: Frame Counter Uplink + JN1DL, // 5.7 AT+JN1DL: Join Accept Delay1 + JN2DL, // 5.8 AT+JN2DL: Join Accept Delay2 + PNM, // 5.9 AT+PNM: Public Network Mode + RX1DL, // 5.10 AT+RX1DL: Receive Delay1 + RX2DL, // 5.11 AT+RX2DL: Receive Delay2 + RX2DR, // 5.12 AT+RX2DR: Rx2 Window Data Rate + RX2FQ, // 5.13 AT+RX2FQ: Rx2 Window Frequency + TXP, // 5.14 AT+TXP: Transmit Power + // TxPower(dBM) offset from Max. ERP. + // Region | 0 1 2 3 4 5 6 7 8 9 10 + // ------------|------------------------------------------------ + // AS923 | 0 -2 -4 -6 -8 -10 -12 -14 + // AU915 | 0 -2 -4 -6 -8 -10 -14 -14 -16 -18 -20 + // CN470 | 0 -2 -4 -6 -8 -10 -14 -14 + // CN779 | 0 -2 -4 -6 -8 -10 + // EU433 | 0 -2 -4 -6 -8 -10 + // EU868 | 0 -2 -4 -6 -8 -10 -14 -14 + // IN865 | 0 -2 -4 -6 -8 -10 -14 -14 -16 -18 -20 + // KR920 | 0 -2 -4 -6 -8 -10 -14 -14 + // US915 | 0 -2 -4 -6 -8 -10 -16 -16 -16 -16 -10 + // US915_HYBRID| 0 -2 -4 -6 -8 -10 -16 -16 -16 -16 -10 + RSSI, // 5.15 AT+RSSI: RSSI of the Last Received Packet + SNR, // 5.16 AT+SNR: SNR of the Last Received Packet + PORT, // 5.17 AT+PORT: Application Port + CHS, // 5.18 AT+CHS: Single Channel Mode + SLEEP, // 5.20 AT+SLEEP: Set sleep mode + BAT, // 5.22 AT+BAT: Get the current battery voltage in Mv + RJTDC, // 5.23 AT+RJTDC: Get or set the ReJoin data transmission interval in min + RPL, // 5.24 AT+RPL: Get or set response level + // This feature is used to set compatible with different LoRaWAN servers. If RPL doesn;t match , user will see strange + // message in the + // server portal. + // RPL value: + // AT+RPL=0: Device won't immediately reply any downlink commands from platform. + // AT+RPL=1: Device will immediately reply message to Unconfirmed Data Down. Payload is 0x00. + // AT+RPL=2: Device will immediately reply message to Confirmed Data Down. Payload is 0x00 and requied response header + // for this command. + // AT+RPL=3: Device will immediately reply message to MAC Command. Payload is 0x00 and requied response header for this + // command. + // AT+RPL=4: Device will immediately reply message to Confirmed Data Down & MAC Command. Payload is 0x00 and requied + // response header for these two commands. + // Case Analyes: + // For Class A devices, AT+RPL=0 is ok. that is defaut settings in software. + // For Class C devices used in ChirpStack, need to set AT+RPL=4 because Chirpstack require immedietely reply message to + // MAC Command. + // For Class C devices used in TTI, need to set AT+RPL=4 because TTI require immediately reply message to Confirmed Data + // Down & MAC Command. + TIMESTAMP, // 5.25 AT+TIMESTAMP: Get or Set UNIX timestamp in second + LTIME, // RAK: Get Local time + LEAPSEC, // 5.26 AT+LEAPSEC: Get or Set Leap Second + SYNCMOD, // 5.27 AT+SYNCMOD: Get or Set time synchronization method + SYNCTDC, // 5.28 AT+SYNCTDC: Get or Set time synchronization interval in day + DDETECT, // 5.29 AT+DDETECT: Get or set the downlink detection + // Device offline rejoining. ,, (timeout in min) + // Enable Online Detect, if end node doesn't receive any downlink within ACK_Timout_1( 1440 minutes or 24 hours). + // End node will use confirmed uplink to send packets during ACK_Timout_1 (the 24th hour) to ACK_Timout_2 ( the 48th + // hour). If from the 24th to 48th hour, end node got an downlink from server, it will switch back to unconfirmed uplink. + // end node will restart ACK_Timout_1. If from the 24th to 48th hour, end node still not got any downlink, means device + // doesn't get ACK from server within last 48 hours. Device will process rejoin, rejoin request interval is AT+RJTDC + // period. For AU915/ US915, device will use the sub-band used for last join. + + SETMAXNBTRANS, // 5.30 AT+SETMAXNBTRANS: Get or set the max nbtrans in LinkADR. + // Value1: set the maximum NBTrans. + // value2: 0: uplink fcnt doesn't change for each NBTrans; + // 1: uplink fcnt increase by 1 for each NBTrans. + Unknown + + }; + + static bool isVolatileValue(AT_cmd at_cmd); + + static bool supported(AT_cmd at_cmd, + LoRaModule_e module); + + static const __FlashStringHelper* toFlashString(AT_cmd at_cmd, + LoRaModule_e module); + + static String toString(AT_cmd at_cmd, + LoRaModule_e module); + static const __FlashStringHelper* toDisplayString(AT_cmd at_cmd); + + static AT_cmd determineReceivedDataType(const String& receivedData); + + static AT_cmd decode(const String& receivedData, + String & value); + + static KeyValueStruct getKeyValue(AT_cmd at_cmd, + LoRaModule_e module, + const String& value, + bool extendedValue); + + +}; // class C023_AT_commands diff --git a/src/src/Controller_config/C023_config.cpp b/src/src/Controller_config/C023_config.cpp new file mode 100644 index 000000000..9c04994dd --- /dev/null +++ b/src/src/Controller_config/C023_config.cpp @@ -0,0 +1,197 @@ +#include "../Controller_config/C023_config.h" + +#ifdef USES_C023 + +# include "../Controller_struct/C023_data_struct.h" + +# define C023_BAUDRATE_LABEL "baudrate" + +void C023_ConfigStruct::validate() { + ZERO_TERMINATE(DeviceEUI); + ZERO_TERMINATE(DeviceAddr); + ZERO_TERMINATE(NetworkSessionKey); + ZERO_TERMINATE(AppSessionKey); + + if ((baudrate < 2400) || (baudrate > 115200)) { + reset(); + } + + if (LoRaWAN_Class > static_cast(LoRa_Helper::LoRaWANclass_e::C)) { + // Set to default class A + LoRaWAN_Class = static_cast(LoRa_Helper::LoRaWANclass_e::A); + } + + /* + + switch (frequencyplan) { + case RN2xx3_datatypes::Freq_plan::SINGLE_CHANNEL_EU: + case RN2xx3_datatypes::Freq_plan::TTN_EU: + case RN2xx3_datatypes::Freq_plan::DEFAULT_EU: + + if ((rx2_freq < 867000000) || (rx2_freq > 870000000)) { + rx2_freq = 0; + } + break; + case RN2xx3_datatypes::Freq_plan::TTN_US: + // FIXME TD-er: Need to find the ranges for US (and other regions) + break; + default: + rx2_freq = 0; + break; + } + */ +} + +void C023_ConfigStruct::reset() { + ZERO_FILL(DeviceEUI); + ZERO_FILL(DeviceAddr); + ZERO_FILL(NetworkSessionKey); + ZERO_FILL(AppSessionKey); + baudrate = 57600; + rxpin = -1; + txpin = -1; + resetpin = -1; + dr = 5; + + // frequencyplan = RN2xx3_datatypes::Freq_plan::TTN_EU; + rx2_freq = 0; + + // stackVersion = RN2xx3_datatypes::TTN_stack_version::TTN_v3; + joinmethod = C023_USE_OTAA; +} + +void C023_ConfigStruct::webform_load(C023_data_struct *C023_data) { + validate(); + ESPEasySerialPort port = static_cast(serialPort); + + { + addFormTextBox(F("Device EUI"), F("deveui"), DeviceEUI, C023_DEVICE_EUI_LEN - 1); + String deveui_note = F("Leave empty to use HW DevEUI: "); + + if (C023_data != nullptr) { + deveui_note += C023_data->hweui(); + } + addFormNote(deveui_note, F("deveui_note")); + } + + addFormTextBox(F("Device Addr"), F("devaddr"), DeviceAddr, C023_DEVICE_ADDR_LEN - 1); + addFormTextBox(F("Network Session Key"), F("nskey"), NetworkSessionKey, C023_NETWORK_SESSION_KEY_LEN - 1); + addFormTextBox(F("App Session Key"), F("appskey"), AppSessionKey, C023_APP_SESSION_KEY_LEN - 1); + + LoRa_Helper::addLoRaWAN_JoinMethod_FormSelector(F("Activation Method"), F("joinmethod"), getJoinMethod()); + + addTableSeparator(F("Connection Configuration"), 2, 3); + + /* + { + const __FlashStringHelper *options[] = { F("SINGLE_CHANNEL_EU"), F("TTN_EU"), F("TTN_US"), F("DEFAULT_EU") }; + int values[] = + { + RN2xx3_datatypes::Freq_plan::SINGLE_CHANNEL_EU, + RN2xx3_datatypes::Freq_plan::TTN_EU, + RN2xx3_datatypes::Freq_plan::TTN_US, + RN2xx3_datatypes::Freq_plan::DEFAULT_EU + }; + const FormSelectorOptions selector( NR_ELEMENTS(options), options, values); + selector.addFormSelector(F("Frequency Plan"), F("frequencyplan"), frequencyplan); + addFormNumericBox(F("RX2 Frequency"), F("rx2freq"), rx2_freq, 0); + addUnit(F("Hz")); + addFormNote(F("0 = default, or else override default")); + } + */ + + LoRa_Helper::addLoRaWAN_DR_FormSelector(F("Data Rate"), F("dr"), getDR()); + LoRa_Helper::addLoRaWANclass_FormSelector( + F("LoRaWAN Class"), F("loraclass"), getClass()); + + addTableSeparator(F("LoRaWAN module"), 2, 3); + { + const __FlashStringHelper *options[] = { + C023_AT_commands::toString(C023_AT_commands::LoRaModule_e::Dragino_LA66), + C023_AT_commands::toString(C023_AT_commands::LoRaModule_e::RAK_3172) + }; + int values[] = + { + static_cast(C023_AT_commands::LoRaModule_e::Dragino_LA66), + static_cast(C023_AT_commands::LoRaModule_e::RAK_3172) + }; + const FormSelectorOptions selector(NR_ELEMENTS(options), options, values); + selector.addFormSelector(F("Module"), F("module"), LoRa_module); + } + + addTableSeparator(F("Serial Port Configuration"), 2, 3); + + serialHelper_webformLoad(port, rxpin, txpin, true); + + // Show serial port selection + addFormPinSelect(PinSelectPurpose::Generic_input, formatGpioName_serialRX(false), F("taskdevicepin1"), rxpin); + addFormPinSelect(PinSelectPurpose::Generic_output, formatGpioName_serialTX(false), F("taskdevicepin2"), txpin); + + html_add_script(F("document.getElementById('serPort').onchange();"), false); + + addFormNumericBox(F("Baudrate"), F(C023_BAUDRATE_LABEL), baudrate, 2400, 115200); + addUnit(F("baud")); + addFormNote(F("Module default baudrate: 9600 bps")); + + // Optional reset pin RN2xx3 + addFormPinSelect(PinSelectPurpose::Generic_output, formatGpioName_output_optional(F("Reset")), F("taskdevicepin3"), resetpin); + + addTableSeparator(F("Downlink Messages"), 2, 3); + + LoRa_Helper::addEventFormatStructure_FormSelector( + F("Event Format"), F("eventformat"), getEventFormat()); + + + if (C023_data != nullptr) { + addTableSeparator(F("Statistics"), 2, 3); + + // Some information on detected device + addRowLabel(F("Voltage")); + addHtmlFloat(static_cast(C023_data->getVbat()) / 1000.0f, 3); + + addRowLabel(F("Device Addr")); + addHtml(C023_data->getDevaddr()); + + uint32_t dnctr, upctr; + + if (C023_data->getFrameCounters(dnctr, upctr)) { + addRowLabel(F("Frame Counters (down/up)")); + addHtml(strformat(F("%u / %u"), dnctr, upctr)); + } + + addRowLabel(F("Last Command Error")); + addHtml(C023_data->getLastError()); + + addRowLabel(F("Sample Set Counter")); + addHtmlInt(static_cast(C023_data->getSampleSetCount())); + + addRowLabel(F("Data Rate")); + addHtml(C023_data->getDataRate_str()); + } +} + +void C023_ConfigStruct::webform_save() { + reset(); + String deveui = webArg(F("deveui")); + String devaddr = webArg(F("devaddr")); + String nskey = webArg(F("nskey")); + String appskey = webArg(F("appskey")); + + strlcpy(DeviceEUI, deveui.c_str(), sizeof(DeviceEUI)); + strlcpy(DeviceAddr, devaddr.c_str(), sizeof(DeviceAddr)); + strlcpy(NetworkSessionKey, nskey.c_str(), sizeof(NetworkSessionKey)); + strlcpy(AppSessionKey, appskey.c_str(), sizeof(AppSessionKey)); + baudrate = getFormItemInt(F(C023_BAUDRATE_LABEL), baudrate); + rxpin = getFormItemInt(F("taskdevicepin1"), rxpin); + txpin = getFormItemInt(F("taskdevicepin2"), txpin); + resetpin = getFormItemInt(F("taskdevicepin3"), resetpin); + dr = getFormItemInt(F("dr"), dr); + eventFormat = getFormItemInt(F("eventformat"), eventFormat); + rx2_freq = getFormItemInt(F("rx2freq"), rx2_freq); + joinmethod = getFormItemInt(F("joinmethod"), joinmethod); + LoRaWAN_Class = getFormItemInt(F("loraclass"), LoRaWAN_Class); + LoRa_module = getFormItemInt(F("module"), LoRa_module); + serialHelper_webformSave(serialPort, rxpin, txpin); +} + +#endif // ifdef USES_C023 diff --git a/src/src/Controller_config/C023_config.h b/src/src/Controller_config/C023_config.h new file mode 100644 index 000000000..f85135bc3 --- /dev/null +++ b/src/src/Controller_config/C023_config.h @@ -0,0 +1,72 @@ +#ifndef CONTROLLER_CONFIG_C023_CONFIG_H +#define CONTROLLER_CONFIG_C023_CONFIG_H + +#include "../Helpers/_CPlugin_Helper.h" + +#ifdef USES_C023 + +# include "../Helpers/_CPlugin_Helper_LoRa.h" + +// Forward declaration +struct C023_data_struct; + + +# define C023_DEVICE_EUI_LEN 17 +# define C023_DEVICE_ADDR_LEN 33 +# define C023_NETWORK_SESSION_KEY_LEN 33 +# define C023_APP_SESSION_KEY_LEN 33 + +// N.B. numerical value for OTAA/ABP differs from the enum value +# define C023_USE_ABP 0 +# define C023_USE_OTAA 1 + +struct C023_ConfigStruct +{ + + + C023_ConfigStruct() = default; + + void validate(); + + void reset(); + + // Send all to the web interface + void webform_load(C023_data_struct*C023_data); + + // Collect all data from the web interface + void webform_save(); + + LoRa_Helper::LoRaWANclass_e getClass() const { return static_cast(LoRaWAN_Class); } + + LoRa_Helper::DownlinkEventFormat_e getEventFormat() const { return static_cast(eventFormat); } + + LoRa_Helper::LoRaWAN_DR getDR() const { return static_cast(dr); } + + LoRa_Helper::LoRaWAN_JoinMethod getJoinMethod() const { + return static_cast(joinmethod); + } + + char DeviceEUI[C023_DEVICE_EUI_LEN] = { 0 }; + char DeviceAddr[C023_DEVICE_ADDR_LEN] = { 0 }; + char NetworkSessionKey[C023_NETWORK_SESSION_KEY_LEN] = { 0 }; + char AppSessionKey[C023_APP_SESSION_KEY_LEN] = { 0 }; + unsigned long baudrate = 9600; + int8_t rxpin = -1; + int8_t txpin = -1; + int8_t resetpin = -1; + uint8_t dr = static_cast(LoRa_Helper::LoRaWAN_DR::ADR); + uint8_t eventFormat = + static_cast(LoRa_Helper::DownlinkEventFormat_e::PortNr_in_eventPar); + uint8_t joinmethod = 0; // LoRa_Helper::LoRaWAN_JoinMethod::OTAA + uint8_t serialPort = 0; + uint8_t LoRaWAN_Class = static_cast(LoRa_Helper::LoRaWANclass_e::A); + uint8_t LoRa_module = 0; // static_cast(C023_AT_commands::LoRaModule_e::Dragino_LA66); + uint32_t rx2_freq = 0; + +}; + +DEF_UP(C023_ConfigStruct); + +#endif // ifdef USES_C023 + +#endif // ifndef CONTROLLER_CONFIG_C023_CONFIG_H diff --git a/src/src/Controller_struct/C023_data_struct.cpp b/src/src/Controller_struct/C023_data_struct.cpp new file mode 100644 index 000000000..8e4c14860 --- /dev/null +++ b/src/src/Controller_struct/C023_data_struct.cpp @@ -0,0 +1,866 @@ +#include "../Controller_struct/C023_data_struct.h" + +#ifdef USES_C023 + + +# include + + +C023_data_struct::C023_data_struct() : + _easySerial(nullptr) {} + +C023_data_struct::~C023_data_struct() { + if (_easySerial != nullptr) { + _easySerial->end(); + delete _easySerial; + _easySerial = nullptr; + } +} + +void C023_data_struct::reset() { + if (_easySerial != nullptr) { + _easySerial->end(); + delete _easySerial; + _easySerial = nullptr; + } + _cachedValues.clear(); + _queuedQueries.clear(); + clearQueryPending(); +} + +bool C023_data_struct::init( + const C023_ConfigStruct& config, + taskIndex_t sampleSet_Initiator) +{ + const uint8_t port = config.serialPort; + const int8_t serial_rx = config.rxpin; + const int8_t serial_tx = config.txpin; + unsigned long baudrate = config.baudrate; + bool joinIsOTAA = (config.getJoinMethod() == LoRa_Helper::LoRaWAN_JoinMethod::OTAA); + int8_t reset_pin = config.resetpin; + + _eventFormatStructure = config.getEventFormat(); + + if ((serial_rx < 0) || (serial_tx < 0)) { + // Both pins are needed, or else no serial possible + return false; + } + + // FIXME TD-er: Prevent unneeded OTAA joins. + // See: https://www.thethingsnetwork.org/forum/t/how-often-should-a-node-do-an-otaa-join-and-is-otaa-better-than-abp/11192/47?u=td-er + + + sampleSetInitiator = sampleSet_Initiator; + + if (isInitialized()) { + // Check to see if serial parameters have changed. + bool notChanged = true; + notChanged &= _easySerial->getRxPin() == serial_rx; + notChanged &= _easySerial->getTxPin() == serial_tx; + notChanged &= _easySerial->getBaudRate() == static_cast(baudrate); + + if (notChanged) { return true; } + } + reset(); + _resetPin = reset_pin; + _baudrate = baudrate; + _isClassA = config.getClass() == LoRa_Helper::LoRaWANclass_e::A; + _dr = config.getDR(); + _loraModule = static_cast(config.LoRa_module); + + // FIXME TD-er: Make force SW serial a proper setting. + if (_easySerial != nullptr) { + delete _easySerial; + } + + // When calling "AT+CFG", we may get quite a lot of data at once at a relatively low baud rate. + // This requires quite a lot of calls to read it, so it is much more likely to have some other call inbetween taking way longer than 20 + // msec and thus we will miss some data + + _easySerial = new (std::nothrow) ESPeasySerial(static_cast(port), serial_rx, serial_tx, false, 1024); + + if (_easySerial != nullptr) { + _easySerial->begin(baudrate); + + sendSetValue(F("ATZ")); // Reset LoRa + + if (_loraModule == C023_AT_commands::LoRaModule_e::RAK_3172) { + sendSetValue(F("AT+NWM=1")); // Set to LoRaWAN mode (M5-LoRaWAN-RAK) + } + setDR(_dr); // TODO TD-er: Must this be called after join? + } + return isInitialized(); +} + +bool C023_data_struct::hasJoined() { return getInt(C023_AT_commands::AT_cmd::NJS, 0) != 0; } + +bool C023_data_struct::useOTAA() { return getInt(C023_AT_commands::AT_cmd::NJM, 1) == 1; } + +bool C023_data_struct::command_finished() const { + return true; // myLora->command_finished(); +} + +bool C023_data_struct::txUncnfBytes(const uint8_t *data, uint8_t size, uint8_t port) { + bool res = true; // myLora->txBytes(data, size, port) != RN2xx3_datatypes::TX_return_type::TX_FAIL; + + return res; +} + +bool C023_data_struct::txHexBytes(const String& data, uint8_t port) { + if (!isInitialized()) { return false; } + bool res = true; + String sendData = data; + sendData.replace(F(" "), F("")); + sendData.trim(); + + if (_loraModule == C023_AT_commands::LoRaModule_e::Dragino_LA66) { + // "AT+SENDB=0,2,4,11223344" + // confirm status,Fport,payload length,payload(HEX) + sendSetValue( + strformat( + F("AT+SENDB=%d,%d,%d,%s"), + 0, // confirm status + port, // Fport + sendData.length() / 2, // payload length + sendData.c_str())); // payload(HEX) + } else { + // RUI3 + // "AT+SEND=12:112233" + // Fport:payload(HEX) + sendSetValue( + strformat( + F("AT+SEND=%d:%s"), + port, // Fport + sendData.c_str())); // payload(HEX) + + + } + + // TODO TD-er: Must wait for either "OK", "AT_NO_NETWORK_JOINED" or "AT_BUSY_ERROR" + // This 'busy error' may occur in case the previous send is + // not completed, because of the duty cycle restriction, + // or because RX windows are not completed + + return res; +} + +bool C023_data_struct::txUncnf(const String& data, uint8_t port) { + bool res = true; // myLora->tx(data, port) != RN2xx3_datatypes::TX_return_type::TX_FAIL; + + return res; +} + +bool C023_data_struct::setDR(LoRa_Helper::LoRaWAN_DR dr) +{ + if (!isInitialized()) { return false; } + + if (dr == LoRa_Helper::LoRaWAN_DR::ADR) { + sendSetValue(C023_AT_commands::AT_cmd::ADR, 1); + } else { + sendSetValue(C023_AT_commands::AT_cmd::ADR, 0); + sendSetValue(C023_AT_commands::AT_cmd::DR, static_cast(dr)); + } + get(C023_AT_commands::AT_cmd::ADR); + get(C023_AT_commands::AT_cmd::DR); + return true; +} + +bool C023_data_struct::initOTAA(const String& AppEUI, const String& AppKey, const String& DevEUI) { + const C023_AT_commands::AT_cmd deveui_cmd = + C023_AT_commands::supported(C023_AT_commands::AT_cmd::DEUI, _loraModule) + ? C023_AT_commands::AT_cmd::DEUI + : C023_AT_commands::AT_cmd::DEVEUI; + + if (sendSetValue(C023_AT_commands::AT_cmd::NJM, 1) && + sendSetValue(C023_AT_commands::AT_cmd::APPEUI, AppEUI) && + sendSetValue(C023_AT_commands::AT_cmd::APPKEY, AppKey) && + sendSetValue(deveui_cmd, DevEUI)) + { + return true; + } + return false; +} + +bool C023_data_struct::initABP(const String& addr, const String& AppSKey, const String& NwkSKey) { + const C023_AT_commands::AT_cmd devaddr_cmd = + C023_AT_commands::supported(C023_AT_commands::AT_cmd::DADDR, _loraModule) + ? C023_AT_commands::AT_cmd::DADDR + : C023_AT_commands::AT_cmd::DEVADDR; + + return sendSetValue(C023_AT_commands::AT_cmd::NJM, 0) && + sendSetValue(C023_AT_commands::AT_cmd::NWKSKEY, NwkSKey) && + sendSetValue(C023_AT_commands::AT_cmd::APPSKEY, AppSKey) && + sendSetValue(devaddr_cmd, addr); +} + +bool C023_data_struct::join(bool enable, + bool autoJoin, + uint8_t reattemptInterval, + uint8_t nrJoinAttempts) +{ + if (!isInitialized()) { return false; } + + if (reattemptInterval < 7) { reattemptInterval = 7; } + sendSetValue( + strformat( + F("AT+JOIN=%d:%d:%d:%d"), + enable ? 1 : 0, + autoJoin ? 1 : 0, + reattemptInterval, + nrJoinAttempts)); + + if (_loraModule == C023_AT_commands::LoRaModule_e::Dragino_LA66) { + sendSetValue(F("AT+CFG")); // AT+CFG: Print all configurations + } else { + for (size_t i = 0; i != static_cast(C023_AT_commands::AT_cmd::Unknown); ++i) { + const C023_AT_commands::AT_cmd cmd = static_cast(i); + sendQuery(cmd); + } + } + + return true; +} + +String C023_data_struct::sendRawCommand(const String& command) { + if (!isInitialized()) { return EMPTY_STRING; } + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("sendRawCommand: "); + log += command; + addLogMove(LOG_LEVEL_INFO, log); + } + String res; // = myLora->sendRawCommand(command); + return res; +} + +int C023_data_struct::getVbat() { return getInt(C023_AT_commands::AT_cmd::BAT, -1); } + +String C023_data_struct::peekLastError() { + if (!isInitialized()) { return EMPTY_STRING; } + return EMPTY_STRING; // myLora->peekLastError(); +} + +String C023_data_struct::getLastError() { + if (!isInitialized()) { return EMPTY_STRING; } + return EMPTY_STRING; // myLora->getLastError(); +} + +LoRa_Helper::LoRaWAN_DR C023_data_struct::getDataRate() +{ + const int dr_int = getInt(C023_AT_commands::AT_cmd::DR, -1); + + if (dr_int == -1) { + return LoRa_Helper::LoRaWAN_DR::ADR; + } + return static_cast(dr_int); +} + +String C023_data_struct::getDataRate_str() { + const int dr_int = getInt(C023_AT_commands::AT_cmd::DR, -1); + + if (dr_int == -1) { return F("-"); } + LoRa_Helper::LoRaWAN_DR dr = + static_cast(dr_int); + return strformat(F("%d: %s"), dr_int, FsP(LoRa_Helper::toString(dr))); +} + +int C023_data_struct::getRSSI() { return getInt(C023_AT_commands::AT_cmd::RSSI, 0); } + +uint32_t C023_data_struct::getRawStatus() { + if (!isInitialized()) { return 0; } + return 0; // myLora->getStatus().getRawStatus(); +} + +bool C023_data_struct::getFrameCounters(uint32_t& dnctr, uint32_t& upctr) { + if (!isInitialized()) { return false; } + dnctr = getInt(C023_AT_commands::AT_cmd::FCD, 0); + upctr = getInt(C023_AT_commands::AT_cmd::FCU, 0); + return true; +} + +bool C023_data_struct::setFrameCounters(uint32_t dnctr, uint32_t upctr) { + if (!isInitialized()) { return false; } + bool res = true; // myLora->setFrameCounters(dnctr, upctr); + + return res; +} + +// Cached data, only changing occasionally. + +String C023_data_struct::getDevaddr() { return get(C023_AT_commands::AT_cmd::DADDR); } + +String C023_data_struct::hweui() { return get(C023_AT_commands::AT_cmd::DEUI); } + +String C023_data_struct::sysver() { return get(C023_AT_commands::AT_cmd::VER); } + +uint8_t C023_data_struct::getSampleSetCount() const { + return sampleSetCounter; +} + +uint8_t C023_data_struct::getSampleSetCount(taskIndex_t taskIndex) { + if (sampleSetInitiator == taskIndex) + { + ++sampleSetCounter; + } + return sampleSetCounter; +} + +float C023_data_struct::getLoRaAirTime(uint8_t pl) { + if (isInitialized()) { + return LoRa_Helper::getLoRaAirTime(pl, getDataRate()); + } + return -1.0f; +} + +bool C023_data_struct::async_loop(bool processingSetCommand) { + if (!isInitialized()) { return false; } + bool res = false; + + if (!_in_async_loop) { + _in_async_loop = true; + + sendNextQueuedQuery(); + + while (_easySerial->available()) { + const int ret = _easySerial->read(); + + if (ret >= 0) { + const char c = static_cast(ret); + + switch (c) + { + case '\n': + case '\r': + { + // End of line + if (!_fromLA66.isEmpty()) { + addLog(LOG_LEVEL_INFO, concat(F("LoRa recv: "), _fromLA66)); + + // TODO TD-er: Process received data + if ((_fromLA66.indexOf(F("OK")) != -1) || + (_fromLA66.indexOf(F("AT_COMMAND_NOT_FOUND")) != -1)) { + res = true; + } + + if (processReceived(_fromLA66, processingSetCommand) && !processingSetCommand) { + sendNextQueuedQuery(); + } + } + + _fromLA66.clear(); + break; + } + default: + _fromLA66 += c; + break; + } + } + } + _in_async_loop = false; + } + + return res; +} + +bool C023_data_struct::writeCachedValues(KeyValueWriter*writer, C023_AT_commands::AT_cmd start, C023_AT_commands::AT_cmd end) +{ + if (writer == nullptr) { return false; } + + for (size_t i = static_cast(start); i < static_cast(end); ++i) { + const C023_AT_commands::AT_cmd cmd = static_cast(i); + + const String value = get(cmd); + + if (!value.isEmpty()) { + auto kv = C023_AT_commands::getKeyValue(cmd, _loraModule, value, true /*!writer->dataOnlyOutput()*/); + writer->write(kv); + } + } + return true; +} + +bool C023_data_struct::sendSetValue( + C023_AT_commands::AT_cmd at_cmd, + const String & value) +{ + if (!isInitialized() || + !C023_AT_commands::supported(at_cmd, _loraModule) || + (at_cmd == C023_AT_commands::AT_cmd::Unknown) || + value.isEmpty()) { return false; } + + const String str = strformat(F("AT+%s=%s"), + FsP(C023_AT_commands::toFlashString(at_cmd, _loraModule)), + value.c_str()); + + if (!sendSetValue(str)) { return false; } + sendQuery(at_cmd); + return true; +} + +bool C023_data_struct::sendSetValue( + C023_AT_commands::AT_cmd at_cmd, + int value) { return sendSetValue(at_cmd, String(value)); } + +bool C023_data_struct::sendSetValue(const String& str) +{ + if (!isInitialized() || + str.isEmpty()) { return false; } + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, concat(F("LoRa Send: "), str)); + } + async_loop(true); + _easySerial->println(str); + + uint32_t start = millis(); + bool res = false; + + while (timePassedSince(start) < 2000 && !res) { + if (async_loop(true)) { res = true; } + } + return res; +} + +String C023_data_struct::get(C023_AT_commands::AT_cmd at_cmd, uint32_t& lastChange) +{ + lastChange = 0; + + if (isInitialized() && (at_cmd != C023_AT_commands::AT_cmd::Unknown)) { + auto it = _cachedValues.find(static_cast(at_cmd)); + + if (it != _cachedValues.end()) { + if (C023_AT_commands::isVolatileValue(at_cmd) && it->second.expired()) { + sendQuery(at_cmd, true); + } + lastChange = it->second.lastChange; + return it->second.value; + } + sendQuery(at_cmd); + } + return EMPTY_STRING; + +} + +int C023_data_struct::getInt(C023_AT_commands::AT_cmd at_cmd, + int errorvalue, + uint32_t & lastChange) +{ + String value = get(at_cmd, lastChange); + + if (value.isEmpty()) { + return errorvalue; + } + return value.toInt(); +} + +float C023_data_struct::getFloat(C023_AT_commands::AT_cmd at_cmd, + float errorvalue, + uint32_t & lastChange) +{ + String value = get(at_cmd, lastChange); + + if (value.isEmpty()) { + return errorvalue; + } + float res{}; + + if (validFloatFromString(value, res)) { return res; } + return errorvalue; +} + +String C023_data_struct::get(C023_AT_commands::AT_cmd at_cmd) +{ + uint32_t lastChange{}; + + return get(at_cmd, lastChange); +} + +int C023_data_struct::getInt(C023_AT_commands::AT_cmd at_cmd, int errorvalue) +{ + uint32_t lastChange{}; + + return getInt(at_cmd, errorvalue, lastChange); +} + +float C023_data_struct::getFloat(C023_AT_commands::AT_cmd at_cmd, + float errorvalue) +{ + uint32_t lastChange{}; + + return getFloat(at_cmd, errorvalue, lastChange); +} + +bool C023_data_struct::processReceived(const String& receivedData, bool processingSetCommand) +{ + String value; + const C023_AT_commands::AT_cmd at_cmd = C023_AT_commands::decode(receivedData, value); + + if (at_cmd == C023_AT_commands::AT_cmd::Unknown) { + switch (_loraModule) + { + case C023_AT_commands::LoRaModule_e::Dragino_LA66: + processReceived_Dragino_LA66(receivedData, at_cmd, processingSetCommand); + break; + case C023_AT_commands::LoRaModule_e::RAK_3172: + processReceived_RAK_3172(receivedData, at_cmd, processingSetCommand); + break; + + case C023_AT_commands::LoRaModule_e::MAX_TYPE: + break; + } + return false; + } + cacheValue(at_cmd, std::move(value)); + + if (_queryPending == at_cmd) { + clearQueryPending(); + + // sendNextQueuedQuery(); + } + + return true; +} + +bool C023_data_struct::processReceived_Dragino_LA66(const String& receivedData, C023_AT_commands::AT_cmd at_cmd, bool processingSetCommand) +{ + if (receivedData.equals(F("txDone"))) { + sendQuery(C023_AT_commands::AT_cmd::FCU); + } else if (receivedData.equals(F("rxDone"))) { + sendQuery(C023_AT_commands::AT_cmd::FCD); + sendQuery(C023_AT_commands::AT_cmd::SNR); + } else if (receivedData.indexOf(F("ADR Message")) != -1) { + sendQuery(C023_AT_commands::AT_cmd::ADR); + sendQuery(C023_AT_commands::AT_cmd::DR); + } else if (receivedData.equals(F("JOINED"))) { + sendSetValue(C023_AT_commands::AT_cmd::CLASS, String(_isClassA ? 'A' : 'C')); + + // Enable Sync system time via LoRaWAN MAC Command (DeviceTimeReq), LoRaWAN server must support v1.0.3 protocol to reply this command. + sendSetValue(F("AT+SYNCMOD=1")); + + sendSetValue(F("AT+CFG")); // AT+CFG: Print all configurations + // sendQuery(C023_AT_commands::AT_cmd::NJM); + // sendQuery(C023_AT_commands::AT_cmd::NJS); + eventQueue.add(F("LoRa#joined")); + } else if (receivedData.equals(F("rxTimeout"))) { + // Just skip this one, no data received + } else if (receivedData.startsWith(F("Rssi"))) { + cacheValue(C023_AT_commands::AT_cmd::RSSI, getValueFromReceivedData(receivedData)); + } else if (receivedData.indexOf(F("DevEui")) != -1) { + cacheValue(C023_AT_commands::AT_cmd::DEUI, getValueFromReceivedData(receivedData)); + } else if (receivedData.indexOf(F("AT+RECVB=?")) != -1) { + sendQuery(C023_AT_commands::AT_cmd::RECVB, true); + } else if (receivedData.indexOf(F("AT+RECV=?")) != -1) { + sendQuery(C023_AT_commands::AT_cmd::RECV, true); + } else if (receivedData.startsWith(F("*****")) || + receivedData.startsWith(F("TX on")) || + receivedData.startsWith(F("RX on"))) + { + // Ignore these lines for now. + // Maybe those "***** UpLinkCounter= 51 *****" could be parsed + // also: + // TX on freq 867.100 MHz at DR 5 + // RX on freq 869.525 MHz at DR 3 + } else if (receivedData.indexOf(F("Tx events")) != -1) { + // Ignore replies like these: + // Stop Tx events,Please wait for the erase to complete + // Start Tx events + // TODO TD-er: Maybe use these to ignore/pause parsing possible pending queries? + } + + else if ( + receivedData.equals(F("AT_ERROR")) || // Generic error + receivedData.equals(F("AT_PARAM_ERROR")) || // A parameter of the command is wrong + receivedData.equals(F("AT_BUSY_ERROR")) || // the LoRa® network is busy, so the command could not completed + receivedData.equals(F("AT_TEST_PARAM_OVERFLOW")) || // the parameter is too long + receivedData.equals(F("AT_NO_NETWORK_JOINED")) || // the LoRa® network has not been joined yet + receivedData.equals(F("AT_RX_ERROR"))) // error detection during the reception of the command + { + if (!queryPending()) { + addLog(LOG_LEVEL_ERROR, strformat( + F("LoRa : %s"), + receivedData.c_str())); + } else { + addLog(LOG_LEVEL_ERROR, strformat( + F("LoRa : %s while processing %s"), + receivedData.c_str(), + C023_AT_commands::toString(_queryPending, _loraModule).c_str())); + clearQueryPending(); + } + } + + else if (receivedData.equals(F("OK"))) { + // Just ignore + } else { + processPendingQuery(receivedData); + } + + return true; +} + +bool C023_data_struct::processReceived_RAK_3172(const String& receivedData, C023_AT_commands::AT_cmd at_cmd, bool processingSetCommand) +{ + // See: https://docs.rakwireless.com/product-categories/software-apis-and-libraries/rui3/at-command-manual/#asynchronous-events + if (receivedData.indexOf(F("+EVT:TX_DONE")) != -1) { + sendQuery(C023_AT_commands::AT_cmd::FCU); + } else if (receivedData.indexOf(F("+EVT:RX_")) != -1) { + processDownlinkMessage(receivedData, true); + } else if (receivedData.indexOf(F("ADR Message")) != -1) { + sendQuery(C023_AT_commands::AT_cmd::ADR); + sendQuery(C023_AT_commands::AT_cmd::DR); + } else if (receivedData.indexOf(F("+EVT:JOINED")) != -1) { + sendSetValue(C023_AT_commands::AT_cmd::CLASS, String(_isClassA ? 'A' : 'C')); + sendSetValue(F("AT+TIMEREQ=1")); // Request time from network + sendQuery(C023_AT_commands::AT_cmd::NJM); + sendQuery(C023_AT_commands::AT_cmd::NJS); + eventQueue.add(F("LoRa#joined")); + } else if (receivedData.indexOf(F("+EVT:TIMEREQ")) != -1) { + sendQuery(C023_AT_commands::AT_cmd::LTIME); + } else if (receivedData.equals(F("rxTimeout"))) { + // Just skip this one, no data received + } else if (receivedData.startsWith(F("Rssi"))) { + cacheValue(C023_AT_commands::AT_cmd::RSSI, getValueFromReceivedData(receivedData)); + } else if (receivedData.indexOf(F("DevEui")) != -1) { + cacheValue(C023_AT_commands::AT_cmd::DEUI, getValueFromReceivedData(receivedData)); + } else if (receivedData.indexOf(F("AT+BAT=")) != -1) { + const float vbat = getValueFromReceivedData(receivedData).toFloat(); + const int vbat_mv = vbat * 1000.0f; + cacheValue(C023_AT_commands::AT_cmd::DEUI, String(vbat_mv)); + } else if (receivedData.indexOf(F("AT+RECVB=?")) != -1) { + sendQuery(C023_AT_commands::AT_cmd::RECVB, true); + } else if (receivedData.indexOf(F("AT+RECV=?")) != -1) { + sendQuery(C023_AT_commands::AT_cmd::RECV, true); + } else if (receivedData.startsWith(F("*****")) || + receivedData.startsWith(F("TX on")) || + receivedData.startsWith(F("RX on"))) + { + // Ignore these lines for now. + // Maybe those "***** UpLinkCounter= 51 *****" could be parsed + // also: + // TX on freq 867.100 MHz at DR 5 + // RX on freq 869.525 MHz at DR 3 + } else if (receivedData.indexOf(F("Tx events")) != -1) { + // Ignore replies like these: + // Stop Tx events,Please wait for the erase to complete + // Start Tx events + // TODO TD-er: Maybe use these to ignore/pause parsing possible pending queries? + } + + else if ( + receivedData.equals(F("AT_ERROR")) || // Generic error + receivedData.equals(F("AT_COMMAND_NOT_FOUND")) || // Unknown AT command + receivedData.equals(F("AT_PARAM_ERROR")) || // A parameter of the command is wrong + receivedData.equals(F("AT_BUSY_ERROR")) || // the LoRa® network is busy, so the command could not completed + receivedData.equals(F("AT_TEST_PARAM_OVERFLOW")) || // the parameter is too long + receivedData.equals(F("AT_NO_NETWORK_JOINED")) || // the LoRa® network has not been joined yet + receivedData.equals(F("AT_RX_ERROR"))) // error detection during the reception of the command + { + if (!queryPending()) { + addLog(LOG_LEVEL_ERROR, strformat( + F("LoRa : %s"), + receivedData.c_str())); + } else { + addLog(LOG_LEVEL_ERROR, strformat( + F("LoRa : %s while processing %s"), + receivedData.c_str(), + C023_AT_commands::toString(_queryPending, _loraModule).c_str())); + clearQueryPending(); + } + } + + else if (receivedData.equals(F("OK"))) { + // Just ignore + } else { + processPendingQuery(receivedData); + } + + return true; +} + +bool C023_data_struct::processPendingQuery(const String& receivedData) +{ + if (!queryPending()) { + // sendNextQueuedQuery(); + return false; + } + + if ((_queryPending == C023_AT_commands::AT_cmd::RECVB) || + (_queryPending == C023_AT_commands::AT_cmd::RECV)) { + const bool hexEncoded = _queryPending == C023_AT_commands::AT_cmd::RECVB; + processDownlinkMessage(receivedData, hexEncoded); + } else { + cacheValue(_queryPending, receivedData); + } + addLog(LOG_LEVEL_INFO, strformat( + F("LoRa : Process Query: %s -> %s"), + C023_AT_commands::toString(_queryPending, _loraModule).c_str(), + receivedData.c_str())); + + clearQueryPending(); + + // sendNextQueuedQuery(); + return true; +} + +bool C023_data_struct::processDownlinkMessage(const String& receivedData, bool hexEncoded) +{ + int port{}; + const String value = getValueFromReceivedBinaryData( + port, receivedData, hexEncoded); + + if ((port > 0) && (value.length() != 0)) { + switch (_eventFormatStructure) + { + case LoRa_Helper::DownlinkEventFormat_e::PortNr_in_eventPar: + eventQueue.addMove(strformat(F("LoRa#received%d=%s"), port, value.c_str())); + break; + case LoRa_Helper::DownlinkEventFormat_e::PortNr_as_first_eventvalue: + eventQueue.addMove(strformat(F("LoRa#received=%d,%s"), port, value.c_str())); + break; + case LoRa_Helper::DownlinkEventFormat_e::PortNr_both_eventPar_eventvalue: + eventQueue.addMove(strformat(F("LoRa#received%d=%d,%s"), port, port, value.c_str())); + break; + } + } + + cacheValue( + hexEncoded ? C023_AT_commands::AT_cmd::RECVB : C023_AT_commands::AT_cmd::RECV, + strformat(F("%s -> %d : %s"), receivedData.c_str(), port, value.c_str())); + return true; +} + +void C023_data_struct::sendQuery(C023_AT_commands::AT_cmd at_cmd, bool prioritize) +{ + if (_easySerial) { + if (!C023_AT_commands::supported(at_cmd, _loraModule)) { + return; + } + + if (prioritize) { + if (_queuedQueries.front() != static_cast(at_cmd)) { + _queuedQueries.push_front(static_cast(at_cmd)); + } + async_loop(); + } else { + auto it = _queuedQueries.remove(static_cast(at_cmd)); + _queuedQueries.push_back(static_cast(at_cmd)); + } + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + const String query = concat(C023_AT_commands::toString(at_cmd, _loraModule), F("=?")); + addLog(LOG_LEVEL_INFO, concat(F("LoRa : Add to queue: "), query)); + } + + if (prioritize) { + delay(10); + async_loop(); + } + } +} + +void C023_data_struct::sendQuery(C023_AT_commands::AT_cmd at_cmd[], size_t count) +{ + for (size_t i = 0; i < count; ++i) { + sendQuery(at_cmd[i]); + } +} + +void C023_data_struct::sendNextQueuedQuery() +{ + if (!queryPending() && !_queuedQueries.empty()) { + _queryPending = static_cast(_queuedQueries.front()); + _queuedQueries.pop_front(); + const String query = concat(C023_AT_commands::toString(_queryPending, _loraModule), F("=?")); + + addLog(LOG_LEVEL_INFO, concat(F("LoRa : Queried "), query)); + + _easySerial->println(query); + _querySent = millis(); + } +} + +bool sendSetValue(C023_AT_commands::AT_cmd at_cmd, + const String & value); + +void C023_data_struct::cacheValue(C023_AT_commands::AT_cmd at_cmd, const String& value) +{ + String tmp(value); + + cacheValue(at_cmd, std::move(tmp)); +} + +void C023_data_struct::cacheValue(C023_AT_commands::AT_cmd at_cmd, String&& value) +{ + if (value.isEmpty()) { return; } + const size_t key = static_cast(at_cmd); + auto it = _cachedValues.find(key); + + if (it != _cachedValues.end()) { + it->second.set(value); + } else { + _cachedValues.emplace(key, std::move(value)); + } +} + +String C023_data_struct::getValueFromReceivedData(const String& receivedData) +{ + const int pos = receivedData.indexOf('='); + + if (pos == -1) { return EMPTY_STRING; } + String res = receivedData.substring(pos + 1); + res.trim(); + return res; +} + +String C023_data_struct::getValueFromReceivedBinaryData(int& port, const String& receivedData, bool hexEncoded) +{ + port = -1; + String payload; + + switch (_loraModule) + { + case C023_AT_commands::LoRaModule_e::Dragino_LA66: + { + int pos = receivedData.indexOf(':'); + + if (pos == -1) { return EMPTY_STRING; } + port = receivedData.substring(0, pos).toInt(); + payload = receivedData.substring(pos + 1); + break; + } + case C023_AT_commands::LoRaModule_e::RAK_3172: + { + // Receiving Class A downlink + // Example: + // +EVT:RX_1:-51:15:UNICAST:1:312c322c332e343536 + // +EVT:RX_1:-70:8:UNICAST:1:1234 + // -70 is RSSI, 8 is SNR, 1 is Fport, 1234 is payload. + // + // Class B downlink: + // +EVT:RX_B:-47:3:UNICAST:2:4321 + // -47 is RSSI, 3 is SNR, Unicast for B / Multicast for C, 2 is Fport, 4321 is payload. + // + // Class C downlink: + // +EVT:RX_C:-39:12:UNICAST:6:312c322c332e343536 + + + cacheValue(C023_AT_commands::AT_cmd::RSSI, parseString(receivedData, 3, ':', true)); + cacheValue(C023_AT_commands::AT_cmd::SNR, parseString(receivedData, 4, ':', true)); + + port = parseString(receivedData, 6, ':', true).toInt(); + payload = parseString(receivedData, 7, ':', true); + break; + } + default: + break; + } + + if (!payload.isEmpty()) { + if (hexEncoded) { + payload = stringFromHexArray(payload); + } + addLog(LOG_LEVEL_INFO, concat(F("LoRa fromHex: "), payload)); + } + return payload; +} + +#endif // ifdef USES_C023 diff --git a/src/src/Controller_struct/C023_data_struct.h b/src/src/Controller_struct/C023_data_struct.h new file mode 100644 index 000000000..38314fd93 --- /dev/null +++ b/src/src/Controller_struct/C023_data_struct.h @@ -0,0 +1,199 @@ +#ifndef CONTROLLER_STRUCT_C023_DATA_STRUCT_H +#define CONTROLLER_STRUCT_C023_DATA_STRUCT_H + +#include "../Helpers/_CPlugin_Helper.h" + +#ifdef USES_C023 + +# include "../Controller_config/C023_AT_commands.h" +# include "../Controller_config/C023_config.h" + + +class ESPeasySerial; + + +struct C023_data_struct { +public: + + C023_data_struct(); + + ~C023_data_struct(); + + void reset(); + + bool init( + const C023_ConfigStruct& config, + taskIndex_t sampleSet_Initiator); + + bool isInitialized() const { + return _easySerial != nullptr; + } + + bool hasJoined(); + + bool useOTAA(); + + bool command_finished() const; + + bool txUncnfBytes(const uint8_t *data, + uint8_t size, + uint8_t port); + + bool txHexBytes(const String& data, + uint8_t port); + + bool txUncnf(const String& data, + uint8_t port); + + bool setDR(LoRa_Helper::LoRaWAN_DR dr); + + + bool initOTAA(const String& AppEUI, + const String& AppKey, + const String& DevEUI); + + bool initABP(const String& addr, + const String& AppSKey, + const String& NwkSKey); + + bool join(bool enable = true, // Set to false for stop joining + bool autoJoin = 0, // for Auto-join on power up + uint8_t reattemptInterval = 8, // 7 - 255 seconds + uint8_t nrJoinAttempts = 1); // No. of join attempts: 0 - 255 + + String sendRawCommand(const String& command); + + int getVbat(); + + String peekLastError(); + + String getLastError(); + + LoRa_Helper::LoRaWAN_DR getDataRate(); + String getDataRate_str(); + + int getRSSI(); + + uint32_t getRawStatus(); + + + bool getFrameCounters(uint32_t& dnctr, + uint32_t& upctr); + + bool setFrameCounters(uint32_t dnctr, + uint32_t upctr); + + // Cached data, only changing occasionally. + + String getDevaddr(); + + String hweui(); + + String sysver(); + + uint8_t getSampleSetCount() const; + + uint8_t getSampleSetCount(taskIndex_t taskIndex); + + float getLoRaAirTime(uint8_t pl); + + bool async_loop(bool processingSetCommand = false); + + bool writeCachedValues(KeyValueWriter *writer, + C023_AT_commands::AT_cmd start, + C023_AT_commands::AT_cmd end); + +private: + + bool sendSetValue(C023_AT_commands::AT_cmd at_cmd, + const String & value); + bool sendSetValue(C023_AT_commands::AT_cmd at_cmd, + int value); + bool sendSetValue(const String& value); + + + String get(C023_AT_commands::AT_cmd at_cmd, + uint32_t & lastChange); + int getInt(C023_AT_commands::AT_cmd at_cmd, + int errorvalue, + uint32_t & lastChange); + float getFloat(C023_AT_commands::AT_cmd at_cmd, + float errorvalue, + uint32_t & lastChange); + + String get(C023_AT_commands::AT_cmd at_cmd); + int getInt(C023_AT_commands::AT_cmd at_cmd, + int errorvalue); + float getFloat(C023_AT_commands::AT_cmd at_cmd, + float errorvalue); + + bool processReceived(const String& receivedData, + bool processingSetCommand); + bool processReceived_Dragino_LA66(const String & receivedData, + C023_AT_commands::AT_cmd at_cmd, + bool processingSetCommand); + bool processReceived_RAK_3172(const String & receivedData, + C023_AT_commands::AT_cmd at_cmd, + bool processingSetCommand); + + bool processPendingQuery(const String& receivedData); + + bool processDownlinkMessage(const String& receivedData, + bool hexEncoded); + + void sendQuery(C023_AT_commands::AT_cmd at_cmd, + bool prioritize = false); + + void sendQuery(C023_AT_commands::AT_cmd at_cmd[], + size_t count); + + void sendNextQueuedQuery(); + + bool queryPending() const { + return _queryPending != C023_AT_commands::AT_cmd::Unknown && + _querySent != 0 && timePassedSince(_querySent) < 2000; + } + + void clearQueryPending() { + _queryPending = C023_AT_commands::AT_cmd::Unknown; + _querySent = 0; + } + + void cacheValue(C023_AT_commands::AT_cmd at_cmd, + const String & value); + void cacheValue(C023_AT_commands::AT_cmd at_cmd, + String && value); + + static String getValueFromReceivedData(const String& receivedData); + + // Decode HEX stream + String getValueFromReceivedBinaryData(int & port, + const String& receivedData, + bool hexEncoded); + + std::map_cachedValues; + std::list _queuedQueries; + C023_AT_commands::AT_cmd _queryPending = C023_AT_commands::AT_cmd::Unknown; + uint32_t _querySent{}; + + + ESPeasySerial *_easySerial = nullptr; + unsigned long _baudrate = 9600; + uint8_t sampleSetCounter = 0; + taskIndex_t sampleSetInitiator = INVALID_TASK_INDEX; + int8_t _resetPin = -1; + bool _isClassA{}; + bool _in_async_loop{}; // Mutex to prevent recursive calls to async_loop() + LoRa_Helper::LoRaWAN_DR _dr = LoRa_Helper::LoRaWAN_DR::ADR; + + LoRa_Helper::DownlinkEventFormat_e _eventFormatStructure = LoRa_Helper::DownlinkEventFormat_e::PortNr_in_eventPar; + C023_AT_commands::LoRaModule_e _loraModule = C023_AT_commands::LoRaModule_e::Dragino_LA66; + + + String _fromLA66; + +}; + +#endif // ifdef USES_C023 + +#endif // ifndef CONTROLLER_STRUCT_C023_DATA_STRUCT_H diff --git a/src/src/CustomBuild/ESPEasyDefaults.h b/src/src/CustomBuild/ESPEasyDefaults.h index d94e69546..3fdecee7d 100644 --- a/src/src/CustomBuild/ESPEasyDefaults.h +++ b/src/src/CustomBuild/ESPEasyDefaults.h @@ -4,10 +4,15 @@ // Needed to make sure Custom.h is used. #include "../../ESPEasy_common.h" -#include "../DataTypes/NetworkMedium.h" +#include "../../ESPEasy/net/DataTypes/NetworkMedium.h" + #include "../Helpers/Hardware_defines.h" +#ifdef ESP32P4 +#include +#endif + // ******************************************************************************** // User specific configuration // ******************************************************************************** @@ -33,10 +38,23 @@ #ifndef DEFAULT_AP_SUBNET #define DEFAULT_AP_SUBNET 255,255,255,0 // Enter IP address (comma separated) for AP (config) mode #endif +#ifndef DEFAULT_AP_DNS +#define DEFAULT_AP_DNS 1,1,1,1 // Cloudflare DNS: 1.1.1.1 and 1.0.0.1 + // Alternatives + // Cleanbrowsing: 185.228.168.9 and 185.228.169.9 + // Google Public DNS: 8.8.8.8 and 8.8.4.4 + // Quad9: 9.9.9.9 and 149.112.112.112 + // OpenDNS: 208.67.222.222 and 208.67.220.220 + // Comodo Secure DNS: 8.26.56.26 and 8.20.247.20 +#endif #ifndef DEFAULT_AP_KEY #define DEFAULT_AP_KEY "configesp" // Enter network WPA key for AP (config) mode #endif +#ifndef DEFAULT_AP_FALLBACK_MINIMAL_ON_TIME_SEC +#define DEFAULT_AP_FALLBACK_MINIMAL_ON_TIME_SEC 60 // Minimal time to leave the AP on to allow a user to connect to it for entering setup +#endif + // --- Wifi Client Mode ----------------------------------------------------------------------------- #ifndef DEFAULT_SSID #define DEFAULT_SSID "ssid" // Enter your Wifi network SSID @@ -119,8 +137,8 @@ #define DEFAULT_SEND_TO_HTTP_ACK false // Wait for ack with SendToHttp command. #endif -#ifndef DEFAULT_AP_DONT_FORCE_SETUP -#define DEFAULT_AP_DONT_FORCE_SETUP false // Allow optional usage of Sensor without WIFI avaiable // When set you can use the Sensor in AP-Mode without beeing forced to /setup +#ifndef DEFAULT_AP_FORCE_SETUP +#define DEFAULT_AP_FORCE_SETUP true // When set, start Captive Portal to redirect user to web interface when connecting to AP #endif #ifndef DEFAULT_DONT_ALLOW_START_AP @@ -143,7 +161,7 @@ #define DEFAULT_CONTROLLER_PASS "" // Default controller Password #endif #ifndef DEFAULT_CONTROLLER_TIMEOUT -#define DEFAULT_CONTROLLER_TIMEOUT 100 +#define DEFAULT_CONTROLLER_TIMEOUT 500 #endif // using a default template, you also need to set a DEFAULT PROTOCOL to a suitable MQTT protocol ! @@ -261,25 +279,61 @@ #define DEFAULT_PIN_RESET_BUTTON (-1) #endif #ifndef DEFAULT_ETH_PHY_ADDR +#ifdef ESP32P4 +#define DEFAULT_ETH_PHY_ADDR ETH_PHY_ADDR +#else #define DEFAULT_ETH_PHY_ADDR 0 #endif +#endif #ifndef DEFAULT_ETH_PHY_TYPE -#define DEFAULT_ETH_PHY_TYPE EthPhyType_t::notSet +#if FEATURE_ETHERNET +#ifdef ESP32P4 +#define DEFAULT_ETH_PHY_TYPE ESPEasy::net::EthPhyType_t::TLK110 +#else +#define DEFAULT_ETH_PHY_TYPE ESPEasy::net::EthPhyType_t::notSet +#endif +#else +#define DEFAULT_ETH_PHY_TYPE 0 +#endif #endif #ifndef DEFAULT_ETH_PIN_MDC +#ifdef ESP32P4 +#define DEFAULT_ETH_PIN_MDC ETH_PHY_MDC +#else #define DEFAULT_ETH_PIN_MDC -1 #endif +#endif #ifndef DEFAULT_ETH_PIN_MDIO +#ifdef ESP32P4 +#define DEFAULT_ETH_PIN_MDIO ETH_PHY_MDIO +#else #define DEFAULT_ETH_PIN_MDIO -1 #endif +#endif #ifndef DEFAULT_ETH_PIN_POWER +#ifdef ESP32P4 +#define DEFAULT_ETH_PIN_POWER ETH_PHY_POWER +#else #define DEFAULT_ETH_PIN_POWER -1 #endif +#endif #ifndef DEFAULT_ETH_CLOCK_MODE -#define DEFAULT_ETH_CLOCK_MODE EthClockMode_t::Ext_crystal_osc +# if CONFIG_ETH_USE_ESP32_EMAC && FEATURE_ETHERNET +#ifdef ESP32P4 +#define DEFAULT_ETH_CLOCK_MODE ESPEasy::net::EthClockMode_t::Ext_crystal +#else +#define DEFAULT_ETH_CLOCK_MODE static_cast(0) +#endif +#else +#define DEFAULT_ETH_CLOCK_MODE (0) +#endif #endif #ifndef DEFAULT_NETWORK_MEDIUM - #define DEFAULT_NETWORK_MEDIUM NetworkMedium_t::WIFI +#ifdef ESP32P4 + #define DEFAULT_NETWORK_MEDIUM ESPEasy::net::NetworkMedium_t::Ethernet +#else + #define DEFAULT_NETWORK_MEDIUM ESPEasy::net::NetworkMedium_t::WIFI +#endif #endif #ifndef DEFAULT_JSON_BOOL_WITHOUT_QUOTES #define DEFAULT_JSON_BOOL_WITHOUT_QUOTES false diff --git a/src/src/CustomBuild/ESPEasyLimits.h b/src/src/CustomBuild/ESPEasyLimits.h index 73b5ecdeb..b1b0ca2e8 100644 --- a/src/src/CustomBuild/ESPEasyLimits.h +++ b/src/src/CustomBuild/ESPEasyLimits.h @@ -34,16 +34,15 @@ #endif #ifndef MAX_GPIO - #if ESP_IDF_VERSION_MAJOR > 3 // IDF 4+ #include #define MAX_GPIO (GPIO_NUM_MAX - 1) - #else // ESP32 Before IDF 4.0 - #define MAX_GPIO 39 - #endif #endif #endif +#ifndef NETWORK_MAX + #define NETWORK_MAX 8 +#endif #ifndef CONTROLLER_MAX #define CONTROLLER_MAX 3 // max 4! #endif @@ -96,6 +95,12 @@ #ifndef CPLUGIN_MAX #define CPLUGIN_MAX 255 #endif +#ifndef NETWORKDRIVER_INDEX_MAX + #define NETWORKDRIVER_INDEX_MAX 255 +#endif +#ifndef NWPLUGIN_MAX + #define NWPLUGIN_MAX 255 +#endif #ifndef NPLUGIN_MAX #define NPLUGIN_MAX 4 #endif diff --git a/src/src/CustomBuild/ESPEasy_buildinfo.h b/src/src/CustomBuild/ESPEasy_buildinfo.h index 3b203c12f..f6bd045f8 100644 --- a/src/src/CustomBuild/ESPEasy_buildinfo.h +++ b/src/src/CustomBuild/ESPEasy_buildinfo.h @@ -34,12 +34,26 @@ # define BUILD_NOTES " - Mega32-s2" #elif defined(ESP32S3) # define BUILD_NOTES " - Mega32-s3" - #elif defined(ESP32C6) - # define BUILD_NOTES " - Mega32-c6" - #elif defined(ESP32C3) - # define BUILD_NOTES " - Mega32-c3" #elif defined(ESP32C2) # define BUILD_NOTES " - Mega32-c2" + #elif defined(ESP32C3) + # define BUILD_NOTES " - Mega32-c3" + #elif defined(ESP32C5) + # define BUILD_NOTES " - Mega32-c5" + #elif defined(ESP32C6) + # define BUILD_NOTES " - Mega32-c6" + #elif defined(ESP32C61) + # define BUILD_NOTES " - Mega32-c61" + #elif defined(ESP32H2) + # define BUILD_NOTES " - Mega32-h2" + #elif defined(ESP32H21) + # define BUILD_NOTES " - Mega32-h21" + #elif defined(ESP32P4) + #ifdef CONFIG_ESP32P4_SELECTS_REV_LESS_V3 + # define BUILD_NOTES " - Mega32-p4" + #else + # define BUILD_NOTES " - Mega32-p4r3" + #endif # elif defined(ESP32_CLASSIC) # define BUILD_NOTES " - Mega32" # else @@ -61,21 +75,33 @@ #ifdef ESP32 /* - #if CONFIG_IDF_TARGET_ESP32C6 || // support USB via HWCDC using JTAG interface + #if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32C61 || // support USB via HWCDC using JTAG interface CONFIG_IDF_TARGET_ESP32C3 || // support USB via HWCDC using JTAG interface CONFIG_IDF_TARGET_ESP32S2 || // support USB via USBCDC CONFIG_IDF_TARGET_ESP32S3 // support USB via HWCDC using JTAG interface or USBCDC */ -# if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 +# if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C2) // #if CONFIG_TINYUSB_CDC_ENABLED // This define is not recognized here so use USE_USB_CDC_CONSOLE # ifdef USE_USB_CDC_CONSOLE # if ARDUINO_USB_MODE // ESP32C3/S3 embedded USB using JTAG interface -# define USES_HWCDC 1 +# ifndef USES_HWCDC +# if CONFIG_IDF_TARGET_ESP32S2 +# define USES_HWCDC 0 +# ifndef USES_USBCDC +# define USES_USBCDC 1 +# endif // ifndef USES_USBCDC +# else +# define USES_HWCDC 1 +# endif +# endif // ifndef USES_HWCDC + # else // No ARDUINO_USB_MODE -# define USES_USBCDC 1 +# ifndef USES_USBCDC +# define USES_USBCDC 1 +# endif // ifndef USES_USBCDC # endif // if ARDUINO_USB_MODE # endif // ifdef USE_USB_CDC_CONSOLE # endif // if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 diff --git a/src/src/CustomBuild/StorageLayout.h b/src/src/CustomBuild/StorageLayout.h index 5b78641a0..b9958d6d9 100644 --- a/src/src/CustomBuild/StorageLayout.h +++ b/src/src/CustomBuild/StorageLayout.h @@ -130,6 +130,11 @@ #endif +#ifndef DAT_NETWORK_INTERFACE_SIZE +# define DAT_NETWORK_INTERFACE_SIZE 1024 +#endif + + /* @@ -218,6 +223,22 @@ # ifndef CONFIG_FILE_SIZE # define CONFIG_FILE_SIZE (DAT_OFFSET_TASKS + ((DAT_TASKS_SIZE + DAT_TASKS_CUSTOM_SIZE) * TASKS_MAX)) # endif // ifndef CONFIG_FILE_SIZE + + // On ESP8266 we will not store Network Interface settings + // The only 2 interfaces supported on ESP8266 are: + // - WiFi STA + // - WiFi AP + // Both do not need extra settings for basic functionality + // All other network interface plugins will be ESP32-only. + # ifndef DAT_NETWORK_INTERFACES_OFFSET + # define DAT_NETWORK_INTERFACES_OFFSET 16384 + # endif // ifndef DAT_OFFSET_CUSTOM_CONTROLLER + # ifndef DAT_OFFSET_DEV_CREDENTIALS + # define DAT_OFFSET_DEV_CREDENTIALS 0 + # endif + # ifndef DAT_DEV_CREDENTIALS_SIZE + # define DAT_DEV_CREDENTIALS_SIZE 4096 + # endif #endif // if defined(ESP32) diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index cd0dc505a..14983ba0a 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -57,8 +57,11 @@ To create/register a plugin, you have to : #ifndef WEBSERVER_LOG #define WEBSERVER_LOG #endif + #ifndef WEBSERVER_JSON + #define WEBSERVER_JSON + #endif #ifndef WEBSERVER_GITHUB_COPY - #ifndef USE_SECOND_HEAP + #if !(defined(LIMIT_BUILD_SIZE) || defined(ESP8266)) #define WEBSERVER_GITHUB_COPY #endif #endif @@ -71,6 +74,11 @@ To create/register a plugin, you have to : #ifndef WEBSERVER_CONFIG #define WEBSERVER_CONFIG #endif + #ifndef WEBSERVER_NETWORK +// #ifdef ESP32 + #define WEBSERVER_NETWORK +// #endif + #endif #ifndef WEBSERVER_CONTROL #define WEBSERVER_CONTROL #endif @@ -154,6 +162,18 @@ To create/register a plugin, you have to : #endif #endif +#if defined(PLUGIN_DISPLAY_A_COLLECTION) || defined(PLUGIN_DISPLAY_B_COLLECTION) + // Display builds are getting too large, so for now disable ETH for display builds + // Update: By splitting Display into A and B, there is enough space available to enable ETH for now + // #if FEATURE_ETHERNET + // #undef FEATURE_ETHERNET + // #endif + #ifndef BUILD_NO_DEBUG + #define BUILD_NO_DEBUG + #endif +#endif + + #ifdef MEMORY_ANALYSIS #ifdef MQTT_ONLY #define USES_C002 // Domoticz MQTT @@ -256,6 +276,8 @@ To create/register a plugin, you have to : #define PLUGIN_DESCR "Collection_F, IR with AC" #elif defined(PLUGIN_SET_COLLECTION_G_ESP32) #define PLUGIN_DESCR "Collection_G, IR with AC" + #elif defined(PLUGIN_SET_COLLECTION_H_ESP32) + #define PLUGIN_DESCR "Collection_H, IR with AC" #else #define PLUGIN_DESCR "Normal, IR with AC" #endif @@ -270,7 +292,7 @@ To create/register a plugin, you have to : #endif #ifdef PLUGIN_BUILD_COLLECTION - #if !defined(PLUGIN_BUILD_COLLECTION_B) && !defined(PLUGIN_BUILD_COLLECTION_C) && !defined(PLUGIN_BUILD_COLLECTION_D) && !defined(PLUGIN_BUILD_COLLECTION_E) && !defined(PLUGIN_BUILD_COLLECTION_F) && !defined(PLUGIN_BUILD_COLLECTION_G) + #if !defined(PLUGIN_BUILD_COLLECTION_B) && !defined(PLUGIN_BUILD_COLLECTION_C) && !defined(PLUGIN_BUILD_COLLECTION_D) && !defined(PLUGIN_BUILD_COLLECTION_E) && !defined(PLUGIN_BUILD_COLLECTION_F) && !defined(PLUGIN_BUILD_COLLECTION_G) && !defined(PLUGIN_BUILD_COLLECTION_H) #define PLUGIN_DESCR "Collection_A" #define PLUGIN_SET_COLLECTION_A #endif @@ -334,13 +356,22 @@ To create/register a plugin, you have to : #define PLUGIN_BUILD_NORMAL // add stable #endif +#ifdef PLUGIN_BUILD_COLLECTION_H + #define PLUGIN_DESCR "Collection_H" + #define PLUGIN_SET_COLLECTION + #define PLUGIN_SET_COLLECTION_H + #define CONTROLLER_SET_COLLECTION + #define NOTIFIER_SET_COLLECTION + #define PLUGIN_BUILD_NORMAL // add stable +#endif + #ifndef PLUGIN_BUILD_CUSTOM #ifndef PLUGIN_BUILD_NORMAL #define PLUGIN_BUILD_NORMAL // defaults to stable, if not custom #endif #endif -#ifdef PLUGIN_CLIMATE_COLLECTION +#ifdef PLUGIN_CLIMATE_A_COLLECTION #ifdef PLUGIN_BUILD_NORMAL #undef PLUGIN_BUILD_NORMAL #endif @@ -370,7 +401,39 @@ To create/register a plugin, you have to : #ifndef FEATURE_RULES_EASY_COLOR_CODE #define FEATURE_RULES_EASY_COLOR_CODE 1 #endif -#endif +#endif // ifdef PLUGIN_CLIMATE_A_COLLECTION + +#ifdef PLUGIN_CLIMATE_B_COLLECTION + #ifdef PLUGIN_BUILD_NORMAL + #undef PLUGIN_BUILD_NORMAL + #endif + #define PLUGIN_SET_NONE // Specifically configured below + #define CONTROLLER_SET_STABLE + #define NOTIFIER_SET_STABLE + #ifndef FEATURE_ESPEASY_P2P + #define FEATURE_ESPEASY_P2P 1 + #endif + #if defined(ESP8266) && !defined(LIMIT_BUILD_SIZE) + #define LIMIT_BUILD_SIZE + #endif + + #ifndef FEATURE_I2CMULTIPLEXER + #define FEATURE_I2CMULTIPLEXER 1 + #endif + #ifndef FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES + #define FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES 1 + #endif + #define KEEP_TRIGONOMETRIC_FUNCTIONS_RULES + #ifndef FEATURE_PLUGIN_STATS + #define FEATURE_PLUGIN_STATS 1 + #endif + #ifndef FEATURE_CHART_JS + #define FEATURE_CHART_JS 1 + #endif + #ifndef FEATURE_RULES_EASY_COLOR_CODE + #define FEATURE_RULES_EASY_COLOR_CODE 1 + #endif +#endif // ifdef PLUGIN_CLIMATE_B_COLLECTION #ifdef PLUGIN_BUILD_NORMAL #define PLUGIN_SET_STABLE @@ -391,8 +454,13 @@ To create/register a plugin, you have to : #define FEATURE_PLUGIN_STATS 1 #endif #ifndef FEATURE_CHART_JS + #ifdef ESP8266 + #define FEATURE_CHART_JS 0 + #endif + #ifdef ESP32 #define FEATURE_CHART_JS 1 #endif + #endif #ifndef FEATURE_RULES_EASY_COLOR_CODE #define FEATURE_RULES_EASY_COLOR_CODE 1 #endif @@ -579,6 +647,9 @@ To create/register a plugin, you have to : #ifdef WEBSERVER_LOG #undef WEBSERVER_LOG #endif + #ifdef WEBSERVER_JSON + #undef WEBSERVER_JSON + #endif #ifdef WEBSERVER_GITHUB_COPY #undef WEBSERVER_GITHUB_COPY #endif @@ -625,6 +696,10 @@ To create/register a plugin, you have to : #undef FEATURE_EXT_RTC #endif #define FEATURE_EXT_RTC 0 + #ifdef FEATURE_SYSLOG + #undef FEATURE_SYSLOG + #endif + #define FEATURE_SYSLOG 0 #endif @@ -882,8 +957,34 @@ To create/register a plugin, you have to : #define USES_P028 // BME280 #endif +/* +#if defined(PLUGIN_SET_COLLECTION_A_ESP32) +# ifndef LIMIT_BUILD_SIZE +#define LIMIT_BUILD_SIZE +#endif +#endif +*/ + +// Reduce build size for Collection builds +#if defined(PLUGIN_SET_COLLECTION_A_ESP32) || defined(PLUGIN_SET_COLLECTION_B_ESP32) || defined(PLUGIN_SET_COLLECTION_C_ESP32) || defined(PLUGIN_SET_COLLECTION_D_ESP32) || defined(PLUGIN_SET_COLLECTION_E_ESP32) || defined(PLUGIN_SET_COLLECTION_F_ESP32) || defined(PLUGIN_SET_COLLECTION_G_ESP32) +# ifndef BUILD_NO_DEBUG +# define BUILD_NO_DEBUG +# endif +# ifdef FEATURE_NETWORK_STATS +# undef FEATURE_NETWORK_STATS +# endif +# define FEATURE_NETWORK_STATS 0 +# ifdef FEATURE_PPP_MODEM +# undef FEATURE_PPP_MODEM +# endif +# define FEATURE_PPP_MODEM 0 +#ifndef P036_LIMIT_BUILD_SIZE + #define P036_LIMIT_BUILD_SIZE // Reduce build size for P036 (FramedOLED) only +#endif +#endif + #ifdef PLUGIN_SET_COLLECTION_ESP32 - #if !defined(PLUGIN_SET_COLLECTION_B_ESP32) && !defined(PLUGIN_SET_COLLECTION_C_ESP32) && !defined(PLUGIN_SET_COLLECTION_D_ESP32) && !defined(PLUGIN_SET_COLLECTION_E_ESP32) && !defined(PLUGIN_SET_COLLECTION_F_ESP32) && !defined(PLUGIN_SET_COLLECTION_G_ESP32) + #if !defined(PLUGIN_SET_COLLECTION_B_ESP32) && !defined(PLUGIN_SET_COLLECTION_C_ESP32) && !defined(PLUGIN_SET_COLLECTION_D_ESP32) && !defined(PLUGIN_SET_COLLECTION_E_ESP32) && !defined(PLUGIN_SET_COLLECTION_F_ESP32) && !defined(PLUGIN_SET_COLLECTION_G_ESP32) && !defined(PLUGIN_SET_COLLECTION_H_ESP32) #ifndef PLUGIN_DESCR // COLLECTION_A_ESP32_IRExt also passes here #define PLUGIN_DESCR "Collection_A ESP32" #endif @@ -895,6 +996,14 @@ To create/register a plugin, you have to : #ifdef ESP8266 #undef ESP8266 #endif + #if !defined(PLUGIN_BUILD_MAX_ESP32) + #ifndef BUILD_NO_DEBUG + #define BUILD_NO_DEBUG + #endif + #ifndef P036_LIMIT_BUILD_SIZE + #define P036_LIMIT_BUILD_SIZE + #endif + #endif // if !defined(PLUGIN_BUILD_MAX_ESP32) // Undefine contradictionary defines #ifdef PLUGIN_SET_NONE #undef PLUGIN_SET_NONE @@ -910,9 +1019,6 @@ To create/register a plugin, you have to : #define CONTROLLER_SET_COLLECTION #define NOTIFIER_SET_STABLE #define PLUGIN_SET_STABLE // add stable - // See also PLUGIN_SET_COLLECTION_ESP32 section at end, - // where incompatible plugins will be disabled. - // TODO : Check compatibility of plugins for ESP32 board. #endif #ifdef PLUGIN_SET_COLLECTION_B_ESP32 @@ -941,9 +1047,6 @@ To create/register a plugin, you have to : #define CONTROLLER_SET_COLLECTION #define NOTIFIER_SET_STABLE #define PLUGIN_SET_STABLE // add stable - // See also PLUGIN_SET_COLLECTION_ESP32 section at end, - // where incompatible plugins will be disabled. - // TODO : Check compatibility of plugins for ESP32 board. #endif #ifdef PLUGIN_SET_COLLECTION_C_ESP32 @@ -956,6 +1059,11 @@ To create/register a plugin, you have to : #ifdef ESP8266 #undef ESP8266 #endif + #if !defined(PLUGIN_BUILD_MAX_ESP32) + #ifndef P037_LIMIT_BUILD_SIZE + #define P037_LIMIT_BUILD_SIZE // Reduce build size for P037 (MQTT Import) only + #endif + #endif // if !defined(PLUGIN_BUILD_MAX_ESP32) // Undefine contradictionary defines #ifdef PLUGIN_SET_NONE #undef PLUGIN_SET_NONE @@ -972,9 +1080,6 @@ To create/register a plugin, you have to : #define CONTROLLER_SET_COLLECTION #define NOTIFIER_SET_STABLE #define PLUGIN_SET_STABLE // add stable - // See also PLUGIN_SET_COLLECTION_ESP32 section at end, - // where incompatible plugins will be disabled. - // TODO : Check compatibility of plugins for ESP32 board. #endif #ifdef PLUGIN_SET_COLLECTION_D_ESP32 @@ -1003,9 +1108,6 @@ To create/register a plugin, you have to : #define CONTROLLER_SET_COLLECTION #define NOTIFIER_SET_STABLE #define PLUGIN_SET_STABLE // add stable - // See also PLUGIN_SET_COLLECTION_ESP32 section at end, - // where incompatible plugins will be disabled. - // TODO : Check compatibility of plugins for ESP32 board. #endif #ifdef PLUGIN_SET_COLLECTION_E_ESP32 @@ -1034,9 +1136,6 @@ To create/register a plugin, you have to : #define CONTROLLER_SET_COLLECTION #define NOTIFIER_SET_STABLE #define PLUGIN_SET_STABLE // add stable - // See also PLUGIN_SET_COLLECTION_ESP32 section at end, - // where incompatible plugins will be disabled. - // TODO : Check compatibility of plugins for ESP32 board. #endif #ifdef PLUGIN_SET_COLLECTION_F_ESP32 @@ -1065,15 +1164,15 @@ To create/register a plugin, you have to : #define CONTROLLER_SET_COLLECTION #define NOTIFIER_SET_STABLE #define PLUGIN_SET_STABLE // add stable - // See also PLUGIN_SET_COLLECTION_ESP32 section at end, - // where incompatible plugins will be disabled. - // TODO : Check compatibility of plugins for ESP32 board. #endif #ifdef PLUGIN_SET_COLLECTION_G_ESP32 #ifndef PLUGIN_DESCR // COLLECTION_G_ESP32_IRExt also passes here #define PLUGIN_DESCR "Collection_G ESP32" #endif + #ifndef BUILD_NO_DEBUG + #define BUILD_NO_DEBUG + #endif #ifndef ESP32 #define ESP32 #endif @@ -1096,9 +1195,34 @@ To create/register a plugin, you have to : #define CONTROLLER_SET_COLLECTION #define NOTIFIER_SET_STABLE #define PLUGIN_SET_STABLE // add stable - // See also PLUGIN_SET_COLLECTION_ESP32 section at end, - // where incompatible plugins will be disabled. - // TODO : Check compatibility of plugins for ESP32 board. +#endif + +#ifdef PLUGIN_SET_COLLECTION_H_ESP32 + #ifndef PLUGIN_DESCR // COLLECTION_H_ESP32_IRExt also passes here + #define PLUGIN_DESCR "Collection_H ESP32" + #endif + #ifndef ESP32 + #define ESP32 + #endif + #ifdef ESP8266 + #undef ESP8266 + #endif + // Undefine contradictionary defines + #ifdef PLUGIN_SET_NONE + #undef PLUGIN_SET_NONE + #endif + #ifdef PLUGIN_SET_ONLY_SWITCH + #undef PLUGIN_SET_ONLY_SWITCH + #endif + #ifdef PLUGIN_SET_ONLY_TEMP_HUM + #undef PLUGIN_SET_ONLY_TEMP_HUM + #endif + #define PLUGIN_SET_COLLECTION + #define PLUGIN_SET_COLLECTION_H + #define CONTROLLER_SET_STABLE + #define CONTROLLER_SET_COLLECTION + #define NOTIFIER_SET_STABLE + #define PLUGIN_SET_STABLE // add stable #endif #ifdef PLUGIN_BUILD_MAX_ESP32 @@ -1115,17 +1239,20 @@ To create/register a plugin, you have to : #define PLUGIN_SET_MAX #define CONTROLLER_SET_ALL #define NOTIFIER_SET_ALL - #ifndef TESTING_FEATURE_USE_IPV6 - #define TESTING_FEATURE_USE_IPV6 - #endif #ifndef PLUGIN_ENERGY_COLLECTION #define PLUGIN_ENERGY_COLLECTION #endif - #ifndef PLUGIN_DISPLAY_COLLECTION - #define PLUGIN_DISPLAY_COLLECTION + #ifndef PLUGIN_DISPLAY_A_COLLECTION + #define PLUGIN_DISPLAY_A_COLLECTION #endif - #ifndef PLUGIN_CLIMATE_COLLECTION - #define PLUGIN_CLIMATE_COLLECTION + #ifndef PLUGIN_DISPLAY_B_COLLECTION + #define PLUGIN_DISPLAY_B_COLLECTION + #endif + #ifndef PLUGIN_CLIMATE_A_COLLECTION + #define PLUGIN_CLIMATE_A_COLLECTION + #endif + #ifndef PLUGIN_CLIMATE_B_COLLECTION + #define PLUGIN_CLIMATE_B_COLLECTION #endif #ifndef PLUGIN_NEOPIXEL_COLLECTION #define PLUGIN_NEOPIXEL_COLLECTION @@ -1133,6 +1260,15 @@ To create/register a plugin, you have to : #ifndef FEATURE_PLUGIN_STATS #define FEATURE_PLUGIN_STATS 1 #endif + #ifndef FEATURE_NETWORK_STATS + #define FEATURE_NETWORK_STATS 1 + #endif + #ifndef FEATURE_NETWORK_TRAFFIC_COUNT + #define FEATURE_NETWORK_TRAFFIC_COUNT 1 + #endif + #if defined(FEATURE_CHART_JS) && !FEATURE_CHART_JS + #undef FEATURE_CHART_JS + #endif #ifndef FEATURE_CHART_JS #define FEATURE_CHART_JS 1 #endif @@ -1141,6 +1277,7 @@ To create/register a plugin, you have to : #endif #define FEATURE_MQTT_TLS 1 #define FEATURE_EMAIL_TLS 1 + #define FEATURE_HTTP_TLS 1 #ifdef FEATURE_CUSTOM_PROVISIONING #undef FEATURE_CUSTOM_PROVISIONING @@ -1280,6 +1417,9 @@ To create/register a plugin, you have to : #ifdef PLUGIN_SET_COLLECTION_G #undef PLUGIN_SET_COLLECTION_G #endif + #ifdef PLUGIN_SET_COLLECTION_H + #undef PLUGIN_SET_COLLECTION_H + #endif #ifdef PLUGIN_SET_EXPERIMENTAL #undef PLUGIN_SET_EXPERIMENTAL #endif @@ -1379,6 +1519,9 @@ To create/register a plugin, you have to : #ifndef PLUGIN_SET_COLLECTION_G #define PLUGIN_SET_COLLECTION_G #endif + #ifndef PLUGIN_SET_COLLECTION_H + #define PLUGIN_SET_COLLECTION_H + #endif #endif @@ -1433,7 +1576,9 @@ To create/register a plugin, you have to : #define USES_P033 // Dummy #define USES_P034 // DHT12 // #define USES_P035 // IRTX +#ifndef ESP8266_1M #define USES_P036 // FrameOLED +#endif #define USES_P037 // MQTTImport #define USES_P038 // NeoPixel #define USES_P039 // Environment - Thermocouple @@ -1491,7 +1636,7 @@ To create/register a plugin, you have to : #endif #endif -#if defined(PLUGIN_SET_COLLECTION) || defined(PLUGIN_SET_COLLECTION_A) || defined(PLUGIN_SET_COLLECTION_B) || defined(PLUGIN_SET_COLLECTION_C) || defined(PLUGIN_SET_COLLECTION_D) || defined(PLUGIN_SET_COLLECTION_E) || defined(PLUGIN_SET_COLLECTION_F) || defined(PLUGIN_SET_COLLECTION_G) +#if defined(PLUGIN_SET_COLLECTION) || defined(PLUGIN_SET_COLLECTION_A) || defined(PLUGIN_SET_COLLECTION_B) || defined(PLUGIN_SET_COLLECTION_C) || defined(PLUGIN_SET_COLLECTION_D) || defined(PLUGIN_SET_COLLECTION_E) || defined(PLUGIN_SET_COLLECTION_F) || defined(PLUGIN_SET_COLLECTION_G) || defined(PLUGIN_SET_COLLECTION_H) #if !defined(PLUGIN_SET_MAX) && !defined(ESP32) #ifndef LIMIT_BUILD_SIZE #define LIMIT_BUILD_SIZE @@ -1544,7 +1689,7 @@ To create/register a plugin, you have to : #define USES_P082 // GPS #define USES_P089 // Ping #if !defined(USES_P095) && defined(ESP32) && !defined(PLUGIN_BUILD_IR_EXTENDED) - #define USES_P095 // TFT ILI9xxx +// #define USES_P095 // TFT ILI9xxx #endif #if !defined(PLUGIN_BUILD_NORMAL_IRext) // IRext builds do need quite a lot of build space @@ -1563,6 +1708,11 @@ To create/register a plugin, you have to : #if !defined(USES_P180) && defined(ESP32) #define USES_P180 // Generic - I2C Generic #endif + // Remove plugins from 'collection' builds which rely on the neopixel library + // to make sure those builds will fit again. + #ifdef USES_P038 + #undef USES_P038 + #endif #endif #ifdef PLUGIN_SET_COLLECTION_A @@ -1711,6 +1861,9 @@ To create/register a plugin, you have to : #ifndef USES_P170 #define USES_P170 // Input - I2C Liquid level sensor #endif +#endif // ifdef PLUGIN_SET_COLLECTION_G + +#ifdef PLUGIN_SET_COLLECTION_H #if !defined(USES_P173) && defined(ESP32) #define USES_P173 // Environment - SHTC3 #endif @@ -1720,7 +1873,7 @@ To create/register a plugin, you have to : #if !defined(USES_P178) && defined(ESP32) #define USES_P178 // Extra IO - LU9685 Servo controller #endif -#endif // ifdef PLUGIN_SET_COLLECTION_G +#endif // ifdef PLUGIN_SET_COLLECTION_H // Collection of all energy related plugins. #ifdef PLUGIN_ENERGY_COLLECTION @@ -1756,6 +1909,9 @@ To create/register a plugin, you have to : #ifndef USES_P085 #define USES_P085 // AcuDC24x #endif + #if !defined(USES_P087) && defined(ESP32) + #define USES_P087 // SerialProxy + #endif #ifndef USES_P089 #define USES_P089 // Ping #endif @@ -1795,91 +1951,202 @@ To create/register a plugin, you have to : #endif // ifdef PLUGIN_ENERGY_COLLECTION -// Collection of all display plugins. (also NeoPixel) -#ifdef PLUGIN_DISPLAY_COLLECTION +// Collection of display plugins, set A (non-AdaGFX_Helper). +#ifdef PLUGIN_DISPLAY_A_COLLECTION #ifndef PLUGIN_DESCR - #define PLUGIN_DESCR "Display" + #define PLUGIN_DESCR "Display A" #endif - #if !defined(LIMIT_BUILD_SIZE) && (defined(ESP8266) || !(ESP_IDF_VERSION_MAJOR > 3)) - #ifndef PLUGIN_BUILD_MAX_ESP32 - #define LIMIT_BUILD_SIZE // Reduce buildsize (on ESP8266 / pre-IDF4.x) to fit in all Display plugins - #define KEEP_I2C_MULTIPLEXER - #ifndef P036_LIMIT_BUILD_SIZE - #define P036_LIMIT_BUILD_SIZE // Reduce build size for P036 (FramedOLED) only - #endif - #ifndef P037_LIMIT_BUILD_SIZE - #define P037_LIMIT_BUILD_SIZE // Reduce build size for P037 (MQTT Import) only - #endif - #define NOTIFIER_SET_NONE - #ifdef USES_N001 - #undef USES_N001 // Email - #endif - #ifdef USES_N002 - #undef USES_N002 // Buzzer - #endif - #endif - #endif - #if defined(ESP8266) - #if defined(FEATURE_I2C_DEVICE_CHECK) - #undef FEATURE_I2C_DEVICE_CHECK - #endif - #define FEATURE_I2C_DEVICE_CHECK 0 // Disable I2C device check code + #if !defined(LIMIT_BUILD_SIZE) && (defined(ESP8266) || !(ESP_IDF_VERSION_MAJOR > 3)) + #ifndef PLUGIN_BUILD_MAX_ESP32 + #define LIMIT_BUILD_SIZE // Reduce buildsize (on ESP8266 / pre-IDF4.x) to fit in all Display plugins + #define KEEP_I2C_MULTIPLEXER + #ifndef P036_LIMIT_BUILD_SIZE + #define P036_LIMIT_BUILD_SIZE // Reduce build size for P036 (FramedOLED) only + #endif + #ifndef P037_LIMIT_BUILD_SIZE + #define P037_LIMIT_BUILD_SIZE // Reduce build size for P037 (MQTT Import) only + #endif + #define NOTIFIER_SET_NONE + #ifdef USES_N001 + #undef USES_N001 // Email + #endif + #ifdef USES_N002 + #undef USES_N002 // Buzzer + #endif + #endif + #endif + #if defined(ESP8266) + #if defined(FEATURE_I2C_DEVICE_CHECK) + #undef FEATURE_I2C_DEVICE_CHECK + #endif + #define FEATURE_I2C_DEVICE_CHECK 0 // Disable I2C device check code // #if !defined(FEATURE_TARSTREAM_SUPPORT) // #define FEATURE_TARSTREAM_SUPPORT 0 // Disable TarStream support for size // #endif // FEATURE_TARSTREAM_SUPPORT - #endif - #if !defined(FEATURE_SD) && !defined(ESP8266) - #define FEATURE_SD 1 - #endif - #ifndef USES_P012 - #define USES_P012 // LCD - #endif - #ifndef USES_P023 + #endif + #if !defined(FEATURE_SD) && !defined(ESP8266) + #define FEATURE_SD 1 + #endif + #ifndef USES_P012 + #define USES_P012 // LCD + #endif + #ifndef USES_P023 #define USES_P023 // OLED - #endif - #ifndef USES_P036 + #endif + #ifndef USES_P036 #define USES_P036 // FrameOLED - #endif - #ifdef USES_P038 + #endif + #ifdef USES_P038 #undef USES_P038 // DISABLE NeoPixel - #endif - #ifdef USES_P041 + #endif + #ifdef USES_P041 #undef USES_P041 // DISABLE NeoClock - #endif - #ifdef USES_P042 + #endif + #ifdef USES_P042 #undef USES_P042 // DISABLE Candle - #endif - #ifndef USES_P057 + #endif + #ifndef USES_P057 #define USES_P057 // HT16K33_LED - #endif - #ifdef USES_P070 + #endif + #ifdef USES_P070 #undef USES_P070 // DISABLE NeoPixel_Clock - #endif - #ifndef USES_P075 + #endif + #ifndef USES_P075 #define USES_P075 // Nextion - #endif - #ifndef USES_P095 - #define USES_P095 // TFT ILI9341 - #endif - #ifndef USES_P096 - #define USES_P096 // eInk (Needs lib_deps = Adafruit GFX Library, LOLIN_EPD ) - #endif - #ifndef USES_P099 - #define USES_P099 // XPT2046 Touchscreen - #endif - #ifndef USES_P104 + #endif + // #ifndef USES_P095 + // #define USES_P095 // TFT ILI9341 + // #endif + // #ifndef USES_P096 + // #define USES_P096 // eInk (Needs lib_deps = Adafruit GFX Library, LOLIN_EPD ) + // #endif + // #ifndef USES_P099 + // #define USES_P099 // XPT2046 Touchscreen + // #endif + #ifndef USES_P104 // Plugin adds over 40k to build size #define USES_P104 // MAX7219 dot matrix - #endif - #if !defined(USES_P109) && defined(ESP32) - #define USES_P109 // ThermoOLED - #endif - #ifndef USES_P116 - #define USES_P116 // ST77xx - #endif - #if !defined(USES_P123) && defined(ESP32) - #define USES_P123 // I2C Touchscreens - #endif + #endif + #if !defined(USES_P109) && defined(ESP32) + #define USES_P109 // ThermoOLED + #endif + // #ifndef USES_P116 + // #define USES_P116 // ST77xx + // #endif + // #if !defined(USES_P123) && defined(ESP32) + // #define USES_P123 // I2C Touchscreens + // #endif + #if !defined(USES_P137) && defined(ESP32) + #define USES_P137 // AXP192 + #endif + #if !defined(USES_P138) && defined(ESP32) + #define USES_P138 // IP5306 + #endif + #if !defined(USES_P139) && defined(ESP32) + #define USES_P139 // AXP2101 + #endif + // #ifndef USES_P141 + // #define USES_P141 // PCD8544 Nokia 5110 + // #endif + #ifndef USES_P143 + #define USES_P143 // I2C Rotary encoders + #endif + #if !defined(USES_P148) && defined(ESP32) + #define USES_P148 // Sonoff POWR3xxD and THR3xxD display + #endif + // #if !defined(USES_P165) && defined(ESP32) + // #define USES_P165 // Display - NeoPixel (7-Segment) + // #endif + #if !defined(USES_P180) && defined(ESP32) + #define USES_P180 // Generic - I2C Generic + #endif +#endif // ifdef PLUGIN_DISPLAY_A_COLLECTION + +// Collection of display plugins, set B (AdaGFX_Helper). +#ifdef PLUGIN_DISPLAY_B_COLLECTION + #ifndef PLUGIN_DESCR + #define PLUGIN_DESCR "Display B" + #endif + #if !defined(LIMIT_BUILD_SIZE) && (defined(ESP8266) || !(ESP_IDF_VERSION_MAJOR > 3)) + #ifndef PLUGIN_BUILD_MAX_ESP32 + #define LIMIT_BUILD_SIZE // Reduce buildsize (on ESP8266 / pre-IDF4.x) to fit in all Display plugins + #define KEEP_I2C_MULTIPLEXER + #ifndef P036_LIMIT_BUILD_SIZE + #define P036_LIMIT_BUILD_SIZE // Reduce build size for P036 (FramedOLED) only + #endif + #ifndef P037_LIMIT_BUILD_SIZE + #define P037_LIMIT_BUILD_SIZE // Reduce build size for P037 (MQTT Import) only + #endif + #define NOTIFIER_SET_NONE + #ifdef USES_N001 + #undef USES_N001 // Email + #endif + #ifdef USES_N002 + #undef USES_N002 // Buzzer + #endif + #endif + #endif + #if defined(ESP8266) + #if defined(FEATURE_I2C_DEVICE_CHECK) + #undef FEATURE_I2C_DEVICE_CHECK + #endif + #define FEATURE_I2C_DEVICE_CHECK 0 // Disable I2C device check code + // #if !defined(FEATURE_TARSTREAM_SUPPORT) + // #define FEATURE_TARSTREAM_SUPPORT 0 // Disable TarStream support for size + // #endif // FEATURE_TARSTREAM_SUPPORT + #endif + #if !defined(FEATURE_SD) && !defined(ESP8266) + #define FEATURE_SD 1 + #endif + #ifdef USES_P012 + #undef USES_P012 // DISABLE LCD + #endif + #ifdef USES_P023 + #undef USES_P023 // DISABLE OLED + #endif + #ifndef USES_P036 + #define USES_P036 // FrameOLED + #endif + #ifdef USES_P038 + #undef USES_P038 // DISABLE NeoPixel + #endif + #ifdef USES_P041 + #undef USES_P041 // DISABLE NeoClock + #endif + #ifdef USES_P042 + #undef USES_P042 // DISABLE Candle + #endif + #ifdef USES_P057 + #undef USES_P057 // DISABLE HT16K33_LED + #endif + #ifdef USES_P070 + #undef USES_P070 // DISABLE NeoPixel_Clock + #endif + #ifdef USES_P075 + #undef USES_P075 // DISABLE Nextion + #endif + #ifndef USES_P095 + #define USES_P095 // TFT ILI9341 + #endif + #ifndef USES_P096 + #define USES_P096 // eInk (Needs lib_deps = Adafruit GFX Library, LOLIN_EPD ) + #endif + #ifndef USES_P099 + #define USES_P099 // XPT2046 Touchscreen + #endif + #ifdef USES_P104 + // Plugin adds over 40k to build size + #undef USES_P104 // DISABLE MAX7219 dot matrix + #endif + #if !defined(USES_P109) && defined(ESP32) + #define USES_P109 // ThermoOLED + #endif + #ifndef USES_P116 + #define USES_P116 // ST77xx + #endif + #if !defined(USES_P123) && defined(ESP32) + #define USES_P123 // I2C Touchscreens + #endif #if !defined(USES_P137) && defined(ESP32) #define USES_P137 // AXP192 #endif @@ -1895,21 +2162,21 @@ To create/register a plugin, you have to : #ifndef USES_P143 #define USES_P143 // I2C Rotary encoders #endif - #ifndef USES_P148 + #if !defined(USES_P148) && defined(ESP32) #define USES_P148 // Sonoff POWR3xxD and THR3xxD display #endif - #if !defined(USES_P165) && defined(ESP32) - #define USES_P165 // Display - NeoPixel (7-Segment) - #endif + // #if !defined(USES_P165) && defined(ESP32) + // #define USES_P165 // Display - NeoPixel (7-Segment) + // #endif #if !defined(USES_P180) && defined(ESP32) #define USES_P180 // Generic - I2C Generic #endif -#endif // ifdef PLUGIN_DISPLAY_COLLECTION +#endif // ifdef PLUGIN_DISPLAY_B_COLLECTION -// Collection of all climate plugins. -#ifdef PLUGIN_CLIMATE_COLLECTION +// Collection of climate A plugins. +#ifdef PLUGIN_CLIMATE_A_COLLECTION #ifndef PLUGIN_DESCR - #define PLUGIN_DESCR "Climate" + #define PLUGIN_DESCR "Climate A" #endif // Features and plugins cherry picked from stable set @@ -2066,7 +2333,88 @@ To create/register a plugin, you have to : #define USES_P168 // Light - VEML6030/VEML7700 #endif - #ifndef USES_P169 + #if !defined(USES_P180) && defined(ESP32) + #define USES_P180 // Generic - I2C Generic + #endif + + + // Controllers + #ifndef USES_C011 + #define USES_C011 // HTTP Advanced + #endif + + #ifdef ESP32 + #ifndef USES_C018 +// #define USES_C018 // TTN RN2483 + #endif + #ifndef USES_C023 + #define USES_C023 // AT-command LoRaWAN + #endif + #endif + + +#endif // ifdef PLUGIN_CLIMATE_A_COLLECTION + +#ifdef PLUGIN_CLIMATE_B_COLLECTION + #ifndef PLUGIN_DESCR + #define PLUGIN_DESCR "Climate B" + #endif + + // Features and plugins cherry picked from stable set + #ifndef FEATURE_SERVO + #define FEATURE_SERVO 1 + #endif + #ifndef FEATURE_RTTTL + #define FEATURE_RTTTL 1 + #endif + + #define USES_P001 // Switch + #define USES_P002 // ADC + #define USES_P003 // Pulse + #define USES_P004 // Dallas + #define USES_P005 // DHT + #define USES_P006 // BMP085 + + #define USES_P010 // BH1750 + #define USES_P011 // PME + #define USES_P012 // LCD + #define USES_P013 // HCSR04 + #define USES_P014 // SI7021 + #define USES_P015 // TSL2561 + #define USES_P018 // Dust + #define USES_P019 // PCF8574 + + #define USES_P021 // Level + #define USES_P023 // OLED + #define USES_P024 // MLX90614 + #define USES_P025 // ADS1x15 + #define USES_P026 // SysInfo + #define USES_P028 // BME280 + #define USES_P029 // Output + + #define USES_P031 // SHT1X + #define USES_P032 // MS5611 + #define USES_P033 // Dummy + #define USES_P034 // DHT12 + #define USES_P036 // FrameOLED + #define USES_P037 // MQTTImport + #define USES_P038 // NeoPixel + #define USES_P039 // Environment - Thermocouple + + #define USES_P043 // ClkOutput + #define USES_P044 // P1WifiGateway + #define USES_P049 // MHZ19 + + #define USES_P052 // SenseAir + #define USES_P053 // PMSx003 + #define USES_P056 // SDS011-Dust + #define USES_P059 // Encoder + + #define USES_P073 // 7DGT + + // Enable extra climate-related plugins (CO2/Temp/Hum) + + #if defined(USES_P169) && defined(ESP32) #define USES_P169 // Environment - AS3935 Lightning Detector #endif #if !defined(USES_P173) // && defined(ESP32) @@ -2090,13 +2438,24 @@ To create/register a plugin, you have to : #ifndef USES_C011 #define USES_C011 // HTTP Advanced #endif -#endif // ifdef PLUGIN_CLIMATE_COLLECTION + #ifdef ESP32 + #ifndef USES_C018 +// #define USES_C018 // TTN RN2483 + #endif + #ifndef USES_C023 + #define USES_C023 // AT-command LoRaWAN + #endif + #endif +#endif // ifdef PLUGIN_CLIMATE_B_COLLECTION // Collection of all NeoPixel plugins #ifdef PLUGIN_NEOPIXEL_COLLECTION #ifndef PLUGIN_DESCR #define PLUGIN_DESCR "NeoPixel" #endif + #ifndef BUILD_NO_DEBUG + #define BUILD_NO_DEBUG + #endif #if !defined(FEATURE_SD) && !defined(ESP8266) #define FEATURE_SD 1 #endif @@ -2168,18 +2527,25 @@ To create/register a plugin, you have to : #define USES_C012 // Blynk HTTP #endif #ifndef USES_C014 + #ifdef ESP32 #define USES_C014 // homie 3 & 4dev MQTT #endif + #endif #ifndef USES_C015 //#define USES_C015 // Blynk #endif #ifndef USES_C017 + #ifdef ESP32 #define USES_C017 // Zabbix #endif + #endif #ifdef ESP32 #ifndef USES_C018 #define USES_C018 // TTN RN2483 #endif + #ifndef USES_C023 + #define USES_C023 // AT-command LoRaWAN + #endif #endif #ifndef USES_C019 // #define USES_C019 // ESPEasy-NOW @@ -2202,12 +2568,18 @@ To create/register a plugin, you have to : #ifdef USES_P040 #undef USES_P040 // RFID - ID12LA/RDM6300 #endif + #ifdef USES_P042 + #undef USES_P042 // Candle + #endif #ifdef USES_P043 #undef USES_P043 // ClkOutput #endif #ifdef USES_P079 #undef USES_P079 // Wemos Motoshield #endif + #ifdef USES_P073 + #undef USES_P073 // 7DGT + #endif #endif // ifdef PLUGIN_BUILD_IR_EXTENDED_NO_RX // EXPERIMENTAL (playground) ####################### @@ -2329,6 +2701,13 @@ To create/register a plugin, you have to : #ifndef FEATURE_EMAIL_TLS #define FEATURE_EMAIL_TLS 1 #endif + #ifndef FEATURE_HTTP_TLS + #define FEATURE_HTTP_TLS 1 + #endif + + #ifdef BUILD_NO_DEBUG + #undef BUILD_NO_DEBUG + #endif // Plugins #ifndef USES_P016 @@ -2346,6 +2725,24 @@ To create/register a plugin, you have to : #ifndef USES_P046 #define USES_P046 // VentusW266 #endif + #ifndef USES_P038 + #define USES_P038 // NeoPixel + #endif + #ifndef USES_P041 + #define USES_P041 // NeoClock + #endif + #ifndef USES_P042 + #define USES_P042 // Candle + #endif + #ifndef USES_P057 + #define USES_P057 // HT16K33_LED + #endif + #ifndef USES_P070 + #define USES_P070 // NeoPixel_Clock + #endif + #ifndef USES_P075 + #define USES_P075 // Nextion + #endif #ifndef USES_P087 #define USES_P087 // Serial Proxy #endif @@ -2562,13 +2959,13 @@ To create/register a plugin, you have to : #ifndef USES_P168 #define USES_P168 // Light - VEML6030/VEML7700 #endif - #ifndef USES_P170 - #define USES_P170 // Input - I2C Liquid level sensor - #endif - #ifndef USES_P169 #define USES_P169 // Environment - AS3935 Lightning Detector #endif + + #ifndef USES_P170 + #define USES_P170 // Input - I2C Liquid level sensor + #endif #ifndef USES_P173 #define USES_P173 // Environment - SHTC3 #endif @@ -2599,6 +2996,10 @@ To create/register a plugin, you have to : #ifndef USES_C018 #define USES_C018 // TTN RN2483 #endif + #ifndef USES_C023 + #define USES_C023 // AT-command LoRaWAN + #endif + // Notifiers @@ -2680,7 +3081,7 @@ To create/register a plugin, you have to : #define FEATURE_ESPEASY_P2P 1 #endif -#if defined(USES_C018) +#if defined(USES_C018) || defined(USES_C023) #ifdef FEATURE_PACKED_RAW_DATA #undef FEATURE_PACKED_RAW_DATA #endif @@ -2737,7 +3138,7 @@ To create/register a plugin, you have to : // Disable Homie plugin for now in the dev build to make it fit. -#if defined(PLUGIN_DISPLAY_COLLECTION) && defined(USES_C014) && defined(ESP8266) +#if (defined(PLUGIN_DISPLAY_A_COLLECTION) || defined(PLUGIN_DISPLAY_B_COLLECTION)) && defined(USES_C014) && defined(ESP8266) #undef USES_C014 #endif @@ -2806,6 +3207,9 @@ To create/register a plugin, you have to : #ifdef LIMIT_BUILD_SIZE #undef LIMIT_BUILD_SIZE #endif + #ifdef BUILD_NO_DEBUG + #undef BUILD_NO_DEBUG + #endif #endif // Disable some diagnostic parts to make builds fit. @@ -2903,6 +3307,10 @@ To create/register a plugin, you have to : #ifdef USES_C018 #undef USES_C018 // LoRa TTN - RN2483/RN2903 #endif + #ifdef USES_C023 + #undef USES_C023 // AT-command LoRaWAN + #endif + #if defined(FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES) && !defined(KEEP_TRIGONOMETRIC_FUNCTIONS_RULES) #undef FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES #endif @@ -3094,7 +3502,7 @@ To create/register a plugin, you have to : #endif // Here we can re-enable specific features in the COLLECTION sets as we have created some space there by splitting them up -#if defined(COLLECTION_FEATURE_RTTTL) && (defined(PLUGIN_SET_COLLECTION_A) || defined(PLUGIN_SET_COLLECTION_B) || defined(PLUGIN_SET_COLLECTION_C) || defined(PLUGIN_SET_COLLECTION_D) || defined(PLUGIN_SET_COLLECTION_E) || defined(PLUGIN_SET_COLLECTION_F) || defined(PLUGIN_SET_COLLECTION_G)) +#if defined(COLLECTION_FEATURE_RTTTL) && (defined(PLUGIN_SET_COLLECTION_A) || defined(PLUGIN_SET_COLLECTION_B) || defined(PLUGIN_SET_COLLECTION_C) || defined(PLUGIN_SET_COLLECTION_D) || defined(PLUGIN_SET_COLLECTION_E) || defined(PLUGIN_SET_COLLECTION_F) || defined(PLUGIN_SET_COLLECTION_G) || defined(PLUGIN_SET_COLLECTION_H)) #ifndef FEATURE_RTTTL #define FEATURE_RTTTL 1 #endif @@ -3116,7 +3524,6 @@ To create/register a plugin, you have to : #endif #endif - #ifdef ESP8266 // It just doesn't work on ESP8266, too slow, too high memory requirements //#if defined(LIMIT_BUILD_SIZE) || defined(ESP8266_1M) @@ -3128,9 +3535,13 @@ To create/register a plugin, you have to : #undef FEATURE_EMAIL_TLS #define FEATURE_EMAIL_TLS 0 #endif + #if FEATURE_HTTP_TLS + #undef FEATURE_HTTP_TLS + #define FEATURE_HTTP_TLS 0 + #endif #endif -#if FEATURE_MQTT_TLS +#if FEATURE_MQTT_TLS || FEATURE_EMAIL_TLS || FEATURE_HTTP_TLS #if defined(FEATURE_TLS) && !FEATURE_TLS #undef FEATURE_TLS #endif @@ -3140,16 +3551,6 @@ To create/register a plugin, you have to : #endif -#if FEATURE_EMAIL_TLS - #if defined(FEATURE_TLS) && !FEATURE_TLS - #undef FEATURE_TLS - #endif - #ifndef FEATURE_TLS - #define FEATURE_TLS 1 - #endif -#endif - - #if !defined(FEATURE_MQTT_DISCOVER) && FEATURE_MQTT #if defined(LIMIT_BUILD_SIZE) || defined(ESP8266) // Must enable this explicitly for ESP8266 Custom build #define FEATURE_MQTT_DISCOVER 0 @@ -3279,6 +3680,39 @@ To create/register a plugin, you have to : #define FEATURE_ETHERNET 0 #endif +#ifndef FEATURE_PPP_MODEM +#ifdef ESP32 +#ifdef ESP32_4M +// For now we need to keep build size in mind +#define FEATURE_PPP_MODEM 0 +#else +#if CONFIG_LWIP_PPP_SUPPORT +#define FEATURE_PPP_MODEM 1 +#else +#define FEATURE_PPP_MODEM 0 +#endif +#endif +#else +#define FEATURE_PPP_MODEM 0 +#endif +#endif + +#if FEATURE_PPP_MODEM && defined __has_include && !__has_include("esp_modem_c_api_types.h") +#undef FEATURE_PPP_MODEM +#define FEATURE_PPP_MODEM 0 +#endif + + +#ifndef FEATURE_WIFI +#ifdef ESP8266 +#define FEATURE_WIFI 1 +#elif SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED +#define FEATURE_WIFI 1 +#else +#define FEATURE_WIFI 0 +#endif +#endif + #ifndef FEATURE_EXT_RTC #define FEATURE_EXT_RTC 0 #endif @@ -3339,6 +3773,29 @@ To create/register a plugin, you have to : #define FEATURE_PLUGIN_STATS 0 #endif +#if FEATURE_NETWORK_STATS && !FEATURE_PLUGIN_STATS +// FEATURE_NETWORK_STATS requires FEATURE_PLUGIN_STATS +#undef FEATURE_NETWORK_STATS +#endif + + +#ifndef FEATURE_NETWORK_STATS +#if defined(ESP32) && !defined(LIMIT_BUILD_SIZE) && defined(FEATURE_CHART_JS) && FEATURE_CHART_JS && defined(FEATURE_PLUGIN_STATS) && FEATURE_PLUGIN_STATS +#define FEATURE_NETWORK_STATS 1 +#else +#define FEATURE_NETWORK_STATS 0 +#endif +#endif + +#ifndef FEATURE_NETWORK_TRAFFIC_COUNT +#if FEATURE_NETWORK_STATS +#define FEATURE_NETWORK_TRAFFIC_COUNT 1 +#else +#define FEATURE_NETWORK_TRAFFIC_COUNT 0 +#endif +#endif + + #ifndef FEATURE_REPORTING #define FEATURE_REPORTING 0 #endif @@ -3469,6 +3926,14 @@ To create/register a plugin, you have to : #define FEATURE_HTTP_CLIENT 1 // Enable because required for these controllers/features #endif +#ifndef FEATURE_HTTP_TLS + #if defined(ESP32) && (FEATURE_SEND_TO_HTTP || FEATURE_POST_TO_HTTP || FEATURE_PUT_TO_HTTP) + #define FEATURE_HTTP_TLS 1 + #else + #define FEATURE_HTTP_TLS 0 + #endif +#endif // ifndef FEATURE_HTTP_TLS + #ifndef FEATURE_AUTO_DARK_MODE #ifdef LIMIT_BUILD_SIZE #define FEATURE_AUTO_DARK_MODE 0 @@ -3477,19 +3942,6 @@ To create/register a plugin, you have to : #endif #endif -#ifndef FEATURE_ESP8266_DIRECT_WIFI_SCAN - // Feature still in development, do not yet use. - #define FEATURE_ESP8266_DIRECT_WIFI_SCAN 0 -#endif - -#if FEATURE_ESP8266_DIRECT_WIFI_SCAN - #ifdef ESP32 - // ESP8266 only feature - #undef FEATURE_ESP8266_DIRECT_WIFI_SCAN - #define FEATURE_ESP8266_DIRECT_WIFI_SCAN 0 - #endif -#endif - #ifndef DISABLE_SC16IS752_SPI #define DISABLE_SC16IS752_SPI #endif @@ -3503,7 +3955,7 @@ To create/register a plugin, you have to : #endif #ifndef FEATURE_DEFINE_SERIAL_CONSOLE_PORT - #ifdef ESP8266_1M + #if defined(ESP8266_1M) || defined(LIMIT_BUILD_SIZE) #define FEATURE_DEFINE_SERIAL_CONSOLE_PORT 0 #else #define FEATURE_DEFINE_SERIAL_CONSOLE_PORT 1 @@ -3549,15 +4001,19 @@ To create/register a plugin, you have to : #endif #ifndef FEATURE_SET_WIFI_TX_PWR - #ifdef ESP32 - #if defined(ESP32S2) || defined(ESP32S3) || defined(ESP32C2) || defined(ESP32C3) || defined(ESP32C6) - #define FEATURE_SET_WIFI_TX_PWR 1 - #else - // TD-er: Disable setting TX power on ESP32 as it seems to cause issues on IDF4.4 + # ifndef SOC_WIFI_SUPPORTED + #define FEATURE_SET_WIFI_TX_PWR 0 + #else + #ifdef ESP32 + #if defined(ESP32S2) || defined(ESP32S3) || defined(ESP32C2) || defined(ESP32C3) || defined(ESP32C5) || defined(ESP32C6) + #define FEATURE_SET_WIFI_TX_PWR 1 + #else + // TD-er: Disable setting TX power on ESP32 as it seems to cause issues on IDF4.4 + #define FEATURE_SET_WIFI_TX_PWR 1 + #endif + #elif defined(ESP8266) #define FEATURE_SET_WIFI_TX_PWR 1 #endif - #elif defined(ESP8266) - #define FEATURE_SET_WIFI_TX_PWR 1 #endif #endif @@ -3606,7 +4062,7 @@ To create/register a plugin, you have to : #endif #ifndef FEATURE_CHART_STORAGE_LAYOUT - #if defined(LIMIT_BUILD_SIZE) || defined(BUILD_MINIMAL_OTA) + #if defined(LIMIT_BUILD_SIZE) || defined(BUILD_MINIMAL_OTA) || defined(BUILD_NO_DEBUG) #define FEATURE_CHART_STORAGE_LAYOUT 0 #else #define FEATURE_CHART_STORAGE_LAYOUT 1 @@ -3634,8 +4090,13 @@ To create/register a plugin, you have to : #ifndef FEATURE_CLEAR_I2C_STUCK #ifdef ESP8266 + #ifdef ESP8266_1M + // Typically 1M builds do not use any I2C + #define FEATURE_CLEAR_I2C_STUCK 0 + #else #define FEATURE_CLEAR_I2C_STUCK 1 #endif + #endif #ifdef ESP32 #if ESP_IDF_VERSION_MAJOR >= 5 // Messing with the I2C pin mode doesn't work well on IDF5.x @@ -3662,12 +4123,8 @@ To create/register a plugin, you have to : #endif #ifndef FEATURE_USE_IPV6 -# if ESP_IDF_VERSION_MAJOR>=5 && defined(LWIP_IPV6) -# ifdef TESTING_FEATURE_USE_IPV6 -# define FEATURE_USE_IPV6 1 -# else -# define FEATURE_USE_IPV6 0 -# endif +# if ESP_IDF_VERSION_MAJOR>=5 && defined(LWIP_IPV6)// && !defined(ESP32C2) +# define FEATURE_USE_IPV6 1 # else # define FEATURE_USE_IPV6 0 # endif @@ -3824,6 +4281,37 @@ To create/register a plugin, you have to : #endif #endif // if FEATURE_TASKVALUE_ATTRIBUTES +#ifndef FEATURE_PLUGIN_LIST + #ifdef ESP32 + #define FEATURE_PLUGIN_LIST 1 + #endif + #ifdef ESP8266 + #define FEATURE_PLUGIN_LIST 0 // Disabled by default on ESP8266 + #endif +#endif // ifndef FEATURE_PLUGIN_LIST + +#ifndef FEATURE_MQTT_CONNECT_BACKGROUND + #ifdef ESP32 + #define FEATURE_MQTT_CONNECT_BACKGROUND 1 + #endif + #ifdef ESP8266 + #define FEATURE_MQTT_CONNECT_BACKGROUND 0 // Disabled explicitly on ESP8266 + #endif +#endif // if FEATURE_MQTT_CONNECT_BACKGROUND +#if defined(ESP8266) && FEATURE_MQTT_CONNECT_BACKGROUND + #undef FEATURE_MQTT_CONNECT_BACKGROUND + #define FEATURE_MQTT_CONNECT_BACKGROUND 0 // Disable always on ESP8266 +#endif // if defined(ESP8266) && FEATURE_MQTT_CONNECT_BACKGROUND + +#ifndef FEATURE_LAT_LONG_VAR_CMD + #ifdef ESP32 + #define FEATURE_LAT_LONG_VAR_CMD 1 + #endif + #ifdef ESP8266 + #define FEATURE_LAT_LONG_VAR_CMD 0 + #endif +#endif // ifndef FEATURE_LAT_LONG_VAR_CMD + //-------------------HTTPResponseParser Section---------------- #ifndef FEATURE_THINGSPEAK_EVENT #if defined(PLUGIN_BUILD_MAX_ESP32) @@ -3868,5 +4356,111 @@ To create/register a plugin, you have to : #endif #endif +#if FEATURE_PPP_MODEM && !defined(ESP32) +#undef FEATURE_PPP_MODEM +#define FEATURE_PPP_MODEM 0 +#endif -#endif // CUSTOMBUILD_DEFINE_PLUGIN_SETS_H \ No newline at end of file +#if FEATURE_WIFI + #ifndef USES_NW001 + #define USES_NW001 + #endif + #ifndef USES_NW002 + #define USES_NW002 + #endif +#else + #ifdef USES_NW001 + #undef USES_NW001 + #endif + #ifdef USES_NW002 + #undef USES_NW002 + #endif +#endif +#if FEATURE_ETHERNET && CONFIG_ETH_USE_ESP32_EMAC + #ifndef USES_NW003 + #define USES_NW003 + #endif +#else + #ifdef USES_NW003 + #undef USES_NW003 + #endif +#endif + +#if FEATURE_ETHERNET && CONFIG_ETH_USE_SPI_ETHERNET + #ifndef USES_NW004 + #define USES_NW004 + #endif +#else + #ifdef USES_NW004 + #undef USES_NW004 + #endif +#endif + + + +#if FEATURE_PPP_MODEM + #ifndef USES_NW005 + #define USES_NW005 + #endif +#else + #ifdef USES_NW005 + #undef USES_NW005 + #endif +#endif + +#ifndef FEATURE_SYSLOG +#define FEATURE_SYSLOG 1 +#endif + +#if !defined(SOC_WIFI_SUPPORTED) && defined(ESP32) +#define FEATURE_OTA_FW_UPDATE_ESP_HOSTED_MCU 1 +#else +#define FEATURE_OTA_FW_UPDATE_ESP_HOSTED_MCU 0 +#endif + + +#if defined(USES_NW003) || defined(USES_NW004) || defined(USES_NW005) +# ifndef FEATURE_STORE_NETWORK_INTERFACE_SETTINGS +# define FEATURE_STORE_NETWORK_INTERFACE_SETTINGS 1 +# endif +#else +# ifdef FEATURE_STORE_NETWORK_INTERFACE_SETTINGS +# undef FEATURE_STORE_NETWORK_INTERFACE_SETTINGS +# endif +# define FEATURE_STORE_NETWORK_INTERFACE_SETTINGS 0 +#endif + +#ifndef FEATURE_STORE_CREDENTIALS_SEPARATE_FILE +# ifdef ESP32 +# define FEATURE_STORE_CREDENTIALS_SEPARATE_FILE 1 +# endif +# ifdef ESP8266 +# define FEATURE_STORE_CREDENTIALS_SEPARATE_FILE 0 +# endif +#endif + +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE || FEATURE_STORE_NETWORK_INTERFACE_SETTINGS +# ifdef FEATURE_ESPEASY_KEY_VALUE_STORE +# undef FEATURE_ESPEASY_KEY_VALUE_STORE +# endif +# define FEATURE_ESPEASY_KEY_VALUE_STORE 1 +#endif + +#ifdef USES_P180 +#ifdef FEATURE_BUS_COMMAND +#undef FEATURE_BUS_COMMAND +#endif +#ifndef FEATURE_BUS_COMMAND +#define FEATURE_BUS_COMMAND 1 +#endif +#endif + +#ifndef FEATURE_COLORIZE_CONSOLE_LOGS +#ifdef LIMIT_BUILD_SIZE +#define FEATURE_COLORIZE_CONSOLE_LOGS 0 +#else +#define FEATURE_COLORIZE_CONSOLE_LOGS 1 +#endif +#endif + +#endif // CUSTOMBUILD_DEFINE_PLUGIN_SETS_H diff --git a/src/src/DataStructs/C013_p2p_SensorDataStruct.cpp b/src/src/DataStructs/C013_p2p_SensorDataStruct.cpp index 881a37823..218e65d1e 100644 --- a/src/src/DataStructs/C013_p2p_SensorDataStruct.cpp +++ b/src/src/DataStructs/C013_p2p_SensorDataStruct.cpp @@ -37,27 +37,40 @@ bool C013_SensorDataStruct::prepareForSend() validTaskIndex(destTaskIndex); } -bool C013_SensorDataStruct::setData(const uint8_t *data, size_t size) +UP_C013_SensorDataStruct C013_SensorDataStruct::create(const uint8_t *data, size_t size) { - // First clear entire struct - memset(this, 0, sizeof(C013_SensorDataStruct)); + { + UP_C013_SensorDataStruct invalid_res{}; - if (size < 6) { - return false; + if (size < 6) { + return invalid_res; + } + + if ((data[0] != 255) || // header + (data[1] != 5)) { // ID + return invalid_res; + } + + constexpr unsigned len_upto_checksum = offsetof(C013_SensorDataStruct, checksum); + const ShortChecksumType tmpChecksum( + data, + size, + len_upto_checksum); + + if (size >= (len_upto_checksum + 4)) { + // Data could have checksum, see if it is valid. + uint8_t buf[4]; + memcpy(buf, data + len_upto_checksum, sizeof(buf)); + const ShortChecksumType checksum_data(buf); + + if (checksum_data.isSet()) { + if (!(tmpChecksum == checksum_data)) { + return invalid_res; + } + } + } } - if ((data[0] != 255) || // header - (data[1] != 5)) { // ID - return false; - } - - constexpr unsigned len_upto_checksum = offsetof(C013_SensorDataStruct, checksum); - const ShortChecksumType tmpChecksum( - data, - size, - len_upto_checksum); - - // Need to keep track of different possible versions of data which still need to be supported. // Really old versions of ESPEasy might send upto 80 bytes of uninitialized data // meaning for sizes > 24 bytes we may need to check the version of ESPEasy running on the node. @@ -74,25 +87,28 @@ bool C013_SensorDataStruct::setData(const uint8_t *data, size_t size) } } + MakeC013_SensorData(res); + + if (!AllocatedC013_SensorData(res)) { return res; } + + memcpy((uint8_t*)res.get(), data, size); + + if (size <= 24) { - deviceNumber = INVALID_PLUGIN_ID; - sensorType = Sensor_VType::SENSOR_TYPE_NONE; + res->deviceNumber = INVALID_PLUGIN_ID; + res->sensorType = Sensor_VType::SENSOR_TYPE_NONE; if (sourceNode != nullptr) { - sourceNodeBuild = sourceNode->build; + res->sourceNodeBuild = sourceNode->build; } } - memcpy(this, data, size); - - if (checksum.isSet()) { - if (!(tmpChecksum == checksum)) { - return false; - } + if (!(validTaskIndex(res->sourceTaskIndex) && + validTaskIndex(res->destTaskIndex))) + { + res.reset(); } - - return validTaskIndex(sourceTaskIndex) && - validTaskIndex(destTaskIndex); + return res; } bool C013_SensorDataStruct::matchesPluginID(pluginID_t pluginID) const diff --git a/src/src/DataStructs/C013_p2p_SensorDataStruct.h b/src/src/DataStructs/C013_p2p_SensorDataStruct.h index 6c7ffebe6..e19214943 100644 --- a/src/src/DataStructs/C013_p2p_SensorDataStruct.h +++ b/src/src/DataStructs/C013_p2p_SensorDataStruct.h @@ -13,14 +13,15 @@ # include "../DataTypes/TaskValues_Data.h" # include "../DataTypes/PluginID.h" +struct __attribute__((__packed__)) C013_SensorDataStruct; +DEF_UP(C013_SensorDataStruct); // These structs are sent to other nodes, so make sure not to change order or offset in struct. struct __attribute__((__packed__)) C013_SensorDataStruct { C013_SensorDataStruct() = default; - bool setData(const uint8_t *data, - size_t size); + static UP_C013_SensorDataStruct create(const uint8_t *data, size_t size); bool prepareForSend(); @@ -54,6 +55,16 @@ struct __attribute__((__packed__)) C013_SensorDataStruct uint32_t IDX = 0; }; +#include "../Helpers/Memory.h" + + +#define MakeC013_SensorData(T) void * calloc_ptr = special_calloc(1,sizeof(C013_SensorDataStruct)); UP_C013_SensorDataStruct T(new (calloc_ptr) C013_SensorDataStruct()); + +// Check to see if MakeC013_SensorData was successful +#define AllocatedC013_SensorData(T) (T.get() != nullptr) + + + #endif // ifdef USES_C013 #endif // ifndef DATASTRUCTS_C013_P2P_SENSORDATASTRUCTS_H diff --git a/src/src/DataStructs/C013_p2p_SensorInfoStruct.cpp b/src/src/DataStructs/C013_p2p_SensorInfoStruct.cpp index d215bd07d..1c2c94d67 100644 --- a/src/src/DataStructs/C013_p2p_SensorInfoStruct.cpp +++ b/src/src/DataStructs/C013_p2p_SensorInfoStruct.cpp @@ -47,13 +47,13 @@ bool C013_SensorInfoStruct::prepareForSend(size_t& sizeToSend) TaskDeviceErrorValue[x] = ExtraTaskSettings.TaskDeviceErrorValue[x]; VariousBits[x] = ExtraTaskSettings.VariousBits[x]; -/* - ZERO_FILL(TaskDeviceFormula[x]); + /* + ZERO_FILL(TaskDeviceFormula[x]); - if (ExtraTaskSettings.TaskDeviceFormula[x][0] != 0) { - safe_strncpy(TaskDeviceFormula[x], ExtraTaskSettings.TaskDeviceFormula[x], sizeof(TaskDeviceFormula[x])); - } -*/ + if (ExtraTaskSettings.TaskDeviceFormula[x][0] != 0) { + safe_strncpy(TaskDeviceFormula[x], ExtraTaskSettings.TaskDeviceFormula[x], sizeof(TaskDeviceFormula[x])); + } + */ } for (uint8_t x = 0; x < PLUGIN_CONFIGVAR_MAX; ++x) { @@ -94,54 +94,70 @@ bool C013_SensorInfoStruct::prepareForSend(size_t& sizeToSend) return true; } -bool C013_SensorInfoStruct::setData(const uint8_t *data, size_t size) +UP_C013_SensorInfoStruct C013_SensorInfoStruct::create(const uint8_t *data, size_t size) { - // First clear entire struct - memset(this, 0, sizeof(C013_SensorInfoStruct)); + { + UP_C013_SensorInfoStruct invalid_res{}; - if (size < 6) { - return false; + if (size < 6) { + return invalid_res; + } + + if ((data[0] != 255) || // header + (data[1] != 3)) { // ID + return invalid_res; + } + + // Before copying the data, compute the checksum of the entire packet + constexpr unsigned len_upto_checksum = offsetof(C013_SensorInfoStruct, checksum); + const ShortChecksumType tmpChecksum( + data, + size, + len_upto_checksum); + + if (size >= (len_upto_checksum + 4)) { + // Data could have checksum, see if it is valid. + uint8_t buf[4]; + memcpy(buf, data + len_upto_checksum, sizeof(buf)); + const ShortChecksumType checksum_data(buf); + + if (checksum_data.isSet()) { + if (!(tmpChecksum == checksum_data)) { + return invalid_res; + } + } + } } - if ((data[0] != 255) || // header - (data[1] != 3)) { // ID - return false; - } - - // Before copying the data, compute the checksum of the entire packet - constexpr unsigned len_upto_checksum = offsetof(C013_SensorInfoStruct, checksum); - const ShortChecksumType tmpChecksum( - data, - size, - len_upto_checksum); - // Need to keep track of different possible versions of data which still need to be supported. if (size > sizeof(C013_SensorInfoStruct)) { size = sizeof(C013_SensorInfoStruct); } + MakeC013_SensorInfo(res); + + if (!AllocatedC013_SensorInfo(res)) { return res; } + + memcpy((uint8_t *)res.get(), data, size); + if (size <= 138) { - deviceNumber = INVALID_PLUGIN_ID; - sensorType = Sensor_VType::SENSOR_TYPE_NONE; + res->deviceNumber = INVALID_PLUGIN_ID; + res->sensorType = Sensor_VType::SENSOR_TYPE_NONE; NodeStruct *sourceNode = Nodes.getNode(data[2]); // sourceUnit if (sourceNode != nullptr) { - sourceNodeBuild = sourceNode->build; + res->sourceNodeBuild = sourceNode->build; } } - memcpy(this, data, size); - - if (checksum.isSet()) { - if (!(tmpChecksum == checksum)) { - return false; - } + if (!(validTaskIndex(res->sourceTaskIndex) && + validTaskIndex(res->destTaskIndex) && + validPluginID(res->deviceNumber))) + { + res.reset(); } - - return validTaskIndex(sourceTaskIndex) && - validTaskIndex(destTaskIndex) && - validPluginID(deviceNumber); + return res; } #endif // ifdef USES_C013 diff --git a/src/src/DataStructs/C013_p2p_SensorInfoStruct.h b/src/src/DataStructs/C013_p2p_SensorInfoStruct.h index 7f4650698..656833e09 100644 --- a/src/src/DataStructs/C013_p2p_SensorInfoStruct.h +++ b/src/src/DataStructs/C013_p2p_SensorInfoStruct.h @@ -14,13 +14,18 @@ # include "../DataTypes/PluginID.h" +#include "../Helpers/Memory.h" + +struct __attribute__((__packed__)) C013_SensorInfoStruct; +DEF_UP(C013_SensorInfoStruct); + // These structs are sent to other nodes, so make sure not to change order or offset in struct. struct __attribute__((__packed__)) C013_SensorInfoStruct { C013_SensorInfoStruct() = default; - bool setData(const uint8_t *data, - size_t size); + + static UP_C013_SensorInfoStruct create(const uint8_t *data, size_t size); bool prepareForSend(size_t& sizeToSend); @@ -60,6 +65,13 @@ struct __attribute__((__packed__)) C013_SensorInfoStruct }; +#define MakeC013_SensorInfo(T) void * calloc_ptr = special_calloc(1,sizeof(C013_SensorInfoStruct)); UP_C013_SensorInfoStruct T(new (calloc_ptr) C013_SensorInfoStruct()); + +// Check to see if MakeC013_SensorInfo was successful +#define AllocatedC013_SensorInfo(T) (T.get() != nullptr) + + + #endif // ifdef USES_C013 #endif // ifndef DATASTRUCTS_C013_P2P_SENSORINFOSTRUCTS_H diff --git a/src/src/DataStructs/Caches.cpp b/src/src/DataStructs/Caches.cpp index 4d47b4d11..29f76b27e 100644 --- a/src/src/DataStructs/Caches.cpp +++ b/src/src/DataStructs/Caches.cpp @@ -3,7 +3,8 @@ #include "../Globals/Device.h" #include "../Globals/ExtraTaskSettings.h" #include "../Globals/Settings.h" -#include "../Globals/WiFi_AP_Candidates.h" + +#include "../../ESPEasy/net/Globals/WiFi_AP_Candidates.h" #include "../Helpers/ESPEasy_Storage.h" #include "../Helpers/StringConverter.h" @@ -20,7 +21,7 @@ void Caches::clearAllCaches() void Caches::clearAllButTaskCaches() { clearFileCaches(); - WiFi_AP_Candidates.clearCache(); + ESPEasy::net::wifi::WiFi_AP_Candidates.clearCache(); rulesHelper.closeAllFiles(); } diff --git a/src/src/DataStructs/ChartJS_dataset_config.h b/src/src/DataStructs/ChartJS_dataset_config.h index ac42ccec8..9e0fbfbd7 100644 --- a/src/src/DataStructs/ChartJS_dataset_config.h +++ b/src/src/DataStructs/ChartJS_dataset_config.h @@ -1,5 +1,4 @@ -#ifndef DATASTRUCTS_CHARTJS_DATASET_CONFIG_H -#define DATASTRUCTS_CHARTJS_DATASET_CONFIG_H +#pragma once #include "../../ESPEasy_common.h" @@ -7,7 +6,8 @@ # include "../DataStructs/PluginStats_Config.h" -struct ChartJS_dataset_config { +class ChartJS_dataset_config { +public: ChartJS_dataset_config() = default; ChartJS_dataset_config( @@ -26,4 +26,3 @@ struct ChartJS_dataset_config { }; #endif // if FEATURE_CHART_JS -#endif // ifndef DATASTRUCTS_CHARTJS_DATASET_CONFIG_H diff --git a/src/src/DataStructs/ControllerSettingsStruct.cpp b/src/src/DataStructs/ControllerSettingsStruct.cpp index 9afc59e96..56d6dbb32 100644 --- a/src/src/DataStructs/ControllerSettingsStruct.cpp +++ b/src/src/DataStructs/ControllerSettingsStruct.cpp @@ -4,7 +4,8 @@ #include "../CustomBuild/ESPEasyLimits.h" #include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../../ESPEasy/net/ESPEasyNetwork.h" +#include "../../ESPEasy/net/Globals/NWPlugins.h" #include "../Helpers/Misc.h" #include "../Helpers/Networking.h" #include "../Helpers/StringConverter.h" @@ -16,12 +17,6 @@ #include -ControllerSettingsStruct::ControllerSettingsStruct() -{ - memset(this, 0, sizeof(ControllerSettingsStruct)); - safe_strncpy(ClientID, F(CONTROLLER_DEFAULT_CLIENTID), sizeof(ClientID)); -} - void ControllerSettingsStruct::reset() { // Need to make sure every byte between the members is also zero // Otherwise the checksum will fail and settings will be saved too often. @@ -37,6 +32,9 @@ void ControllerSettingsStruct::reset() { MustCheckReply = DEFAULT_CONTROLLER_MUST_CHECK_REPLY; SampleSetInitiator = INVALID_TASK_INDEX; KeepAliveTime = CONTROLLER_KEEP_ALIVE_TIME_DFLT; + + // All these bits are already set to 0 by the memset call + /* VariousBits1.mqtt_cleanSession = 0; VariousBits1.mqtt_not_sendLWT = 0; VariousBits1.mqtt_not_willRetain = 0; @@ -49,6 +47,7 @@ void ControllerSettingsStruct::reset() { VariousBits1.useLocalSystemTime = 0; VariousBits1.TLStype = 0; VariousBits1.mqttAutoDiscovery = 0; + */ safe_strncpy(ClientID, F(CONTROLLER_DEFAULT_CLIENTID), sizeof(ClientID)); } @@ -212,7 +211,7 @@ bool ControllerSettingsStruct::updateIPcache() { return true; } - if (!NetworkConnected()) { return false; } + if (!ESPEasy::net::NetworkConnected()) { return false; } IPAddress tmpIP; if (resolveHostByName(HostName, tmpIP, ClientTimeout)) { @@ -326,7 +325,7 @@ bool ControllerSettingsStruct::mqtt_cleanSession() const } */ -#if FEATURE_MQTT_TLS +#if FEATURE_MQTT_TLS || FEATURE_HTTP_TLS String ControllerSettingsStruct::getCertificateFilename() const { return getCertificateFilename(TLStype()); @@ -368,4 +367,27 @@ String ControllerSettingsStruct::getCertificateFilename(TLS_types tls_type) cons return certFile; } -#endif +#endif // #if FEATURE_MQTT_TLS || FEATURE_HTTP_TLS + + +uint32_t ControllerSettingsStruct::getSuggestedTimeout(int index) const +{ + if (MustCheckReply) { + auto data = ESPEasy::net::getDefaultRoute_NWPluginData_static_runtime(); + if (data) { + return data->getSuggestedTimeout(index, ClientTimeout); + } + } + return ClientTimeout; +} + +UP_ControllerSettingsStruct doMakeControllerSettings() +{ + // Need to make sure every byte between the members is also zero + // Otherwise the checksum will fail and settings will be saved too often. + // Therefore we're using calloc instead of malloc. + void * calloc_ptr = special_calloc(1,sizeof(ControllerSettingsStruct)); + UP_ControllerSettingsStruct T(new (calloc_ptr) ControllerSettingsStruct()); + if (T) T->reset(); + return T; +} \ No newline at end of file diff --git a/src/src/DataStructs/ControllerSettingsStruct.h b/src/src/DataStructs/ControllerSettingsStruct.h index 065237e40..c5918ff64 100644 --- a/src/src/DataStructs/ControllerSettingsStruct.h +++ b/src/src/DataStructs/ControllerSettingsStruct.h @@ -6,7 +6,7 @@ \*********************************************************************************************/ #include "../../ESPEasy_common.h" -#include // For std::shared_ptr +#include // For std::unique_ptr #include // for std::nothrow #include @@ -15,7 +15,7 @@ #include "../DataStructs/ChecksumType.h" -#if FEATURE_MQTT_TLS +#if FEATURE_MQTT_TLS || FEATURE_HTTP_TLS #include "../DataTypes/TLS_types.h" #endif @@ -48,10 +48,10 @@ // Timeout of the client in msec. #ifndef CONTROLLER_CLIENTTIMEOUT_MAX -# define CONTROLLER_CLIENTTIMEOUT_MAX 4000 // Not sure if this may trigger SW watchdog. +# define CONTROLLER_CLIENTTIMEOUT_MAX 10000 // Not sure if this may trigger SW watchdog. #endif // ifndef CONTROLLER_CLIENTTIMEOUT_MAX #ifndef CONTROLLER_CLIENTTIMEOUT_DFLT -# define CONTROLLER_CLIENTTIMEOUT_DFLT 100 +# define CONTROLLER_CLIENTTIMEOUT_DFLT DEFAULT_CONTROLLER_TIMEOUT #endif // ifndef CONTROLLER_CLIENTTIMEOUT_DFLT // MQTT Keep Alive Timeout @@ -125,7 +125,7 @@ struct ControllerSettingsStruct }; - ControllerSettingsStruct(); + ControllerSettingsStruct() = default; void reset(); @@ -193,15 +193,18 @@ struct ControllerSettingsStruct void mqtt_retainDiscovery(bool value) { VariousBits1.mqttRetainDiscovery = value; } #endif -#if FEATURE_MQTT_TLS +#if FEATURE_MQTT_TLS || FEATURE_HTTP_TLS TLS_types TLStype() const { return static_cast(VariousBits1.TLStype); } void TLStype(TLS_types tls_type) { VariousBits1.TLStype = static_cast(tls_type); } String getCertificateFilename() const; String getCertificateFilename(TLS_types tls_type) const; -#endif +#endif // #if FEATURE_MQTT_TLS || FEATURE_HTTP_TLS + uint32_t getSuggestedTimeout(int index) const; + + bool UseDNS; uint8_t IP[4]; uint8_t UNUSED_1[3]; @@ -268,14 +271,12 @@ private: #include "../Helpers/Memory.h" -typedef std::shared_ptr ControllerSettingsStruct_ptr_type; -/* -# ifdef USE_SECOND_HEAP -#define MakeControllerSettings(T) HeapSelectIram ephemeral; ControllerSettingsStruct_ptr_type T(new (std::nothrow) ControllerSettingsStruct()); -#else -*/ -#define MakeControllerSettings(T) void * calloc_ptr = special_calloc(1,sizeof(ControllerSettingsStruct)); ControllerSettingsStruct_ptr_type T(new (calloc_ptr) ControllerSettingsStruct()); -//#endif +DEF_UP(ControllerSettingsStruct); + +UP_ControllerSettingsStruct doMakeControllerSettings(); + + +#define MakeControllerSettings(T) UP_ControllerSettingsStruct T = doMakeControllerSettings(); // Check to see if MakeControllerSettings was successful #define AllocatedControllerSettings() (ControllerSettings.get() != nullptr) diff --git a/src/src/DataStructs/DeviceStruct.h b/src/src/DataStructs/DeviceStruct.h index be2d3c777..a1aff7ab0 100644 --- a/src/src/DataStructs/DeviceStruct.h +++ b/src/src/DataStructs/DeviceStruct.h @@ -116,8 +116,8 @@ struct DeviceStruct uint32_t CustomVTypeVar : 1; // User-selectable VType per value uint32_t MqttStateClass : 1; // MQTT StateClass setting in DevicesPage - uint32_t Dummy23 : 1; // Dummy added to force alignment, can be re-used - uint32_t Dummy24 : 1; // Dummy added to force alignment, can be re-used + uint32_t HideDerivedValues : 1; // Hide the options for derived values + uint32_t NoDeviceSettings : 1; // Indicating the device is not referencing actual hardware, like the Dummy task uint32_t Dummy25 : 1; // Dummy added to force alignment, can be re-used uint32_t Dummy26 : 1; // Dummy added to force alignment, can be re-used uint32_t Dummy27 : 1; // Dummy added to force alignment, can be re-used diff --git a/src/src/DataStructs/ESPEasyControllerCache.h b/src/src/DataStructs/ESPEasyControllerCache.h index f27b99d49..a498ce030 100644 --- a/src/src/DataStructs/ESPEasyControllerCache.h +++ b/src/src/DataStructs/ESPEasyControllerCache.h @@ -42,7 +42,7 @@ struct ControllerCache_struct { void resetpeek(); - bool peekDataAvailable() const; + bool peekDataAvailable(); int getPeekFilePos(int& peekFileNr) const; diff --git a/src/src/DataStructs/ESPEasy_EventStruct.h b/src/src/DataStructs/ESPEasy_EventStruct.h index 93a19e7f3..99bf0c592 100644 --- a/src/src/DataStructs/ESPEasy_EventStruct.h +++ b/src/src/DataStructs/ESPEasy_EventStruct.h @@ -7,8 +7,13 @@ #include "../DataTypes/EventValueSource.h" #include "../DataTypes/TaskIndex.h" #include "../DataTypes/NotifierIndex.h" +#include "../ESPEasy/net/DataTypes/NetworkIndex.h" #include "../DataStructs/DeviceStruct.h" +#include "../Helpers/KeyValueWriter.h" +#ifdef ESP32 +# include +#endif /*********************************************************************************************\ * EventStruct @@ -18,29 +23,34 @@ struct EventStruct { EventStruct() = default; + // Delete the copy constructor - EventStruct(const struct EventStruct& event) = delete; + EventStruct(const struct EventStruct&event) = delete; + private: + // Hide the copy assignment operator by making it private - EventStruct& operator=(const EventStruct&) = default; + EventStruct& operator=(const EventStruct&) = default; public: - EventStruct(struct EventStruct&& event) = default; - EventStruct& operator=(struct EventStruct&& other) = default; + + EventStruct(struct EventStruct&&event) = default; + EventStruct& operator=(struct EventStruct&&other) = default; explicit EventStruct(taskIndex_t taskIndex); // Explicit deep_copy function to make sure this object is not accidentally copied using the copy-constructor // Copy constructor and assignment operator should not be used. - void deep_copy(const struct EventStruct& other); - void deep_copy(const struct EventStruct* other); + void deep_copy(const struct EventStruct&other); + void deep_copy(const struct EventStruct*other); + // explicit EventStruct(const struct EventStruct& event); // EventStruct& operator=(const struct EventStruct& other); - void setTaskIndex(taskIndex_t taskIndex); + void setTaskIndex(taskIndex_t taskIndex); - void clear(); + void clear(); // Check (and update) sensorType if not set, plus return (corrected) sensorType Sensor_VType getSensorType(); @@ -49,17 +59,22 @@ public: void setUnixTimeTimestamp(); void setLocalTimeTimestamp(); - String String1; - String String2; - String String3; - String String4; - String String5; + String String1; + String String2; + String String3; + String String4; + String String5; + String String6; + #ifdef ESP32 + NetworkInterface*networkInterface = nullptr; + #endif + KeyValueWriter* kvWriter = nullptr; - uint32_t timestamp_sec = 0u; - uint32_t timestamp_frac = 0u; - uint8_t *Data = nullptr; - int idx = 0; + uint32_t timestamp_sec = 0u; + uint32_t timestamp_frac = 0u; + uint8_t *Data = nullptr; + int idx = 0; union { struct { int Par1; @@ -67,20 +82,47 @@ public: int Par3; int Par4; int Par5; + int Par6; + int Par7; + int Par8; }; - int ParN[5] = { 0 }; + + struct { + float Parf_1; + float Parf_2; + float Parf_3; + float Parf_4; + float Parf_5; + float Parf_6; + float Parf_7; + float Parf_8; + }; + + float ParfN[8]; + struct { + int64_t Par64_1; + int64_t Par64_2; + int64_t Par64_3; + int64_t Par64_4; + }; + int64_t Par64N[4]; + + int ParN[8] = { 0 }; + }; // The origin of the values in the event. See EventValueSource.h - EventValueSource::Enum Source = EventValueSource::Enum::VALUE_SOURCE_NOT_SET; - taskIndex_t TaskIndex = INVALID_TASK_INDEX; // index position in TaskSettings array, 0-11 - controllerIndex_t ControllerIndex = INVALID_CONTROLLER_INDEX; // index position in Settings.Controller, 0-3 + EventValueSource::Enum Source = EventValueSource::Enum::VALUE_SOURCE_NOT_SET; + taskIndex_t TaskIndex = INVALID_TASK_INDEX; // index position in TaskSettings array, 0-11 + controllerIndex_t ControllerIndex = INVALID_CONTROLLER_INDEX; // index position in Settings.Controller, 0-3 #if FEATURE_NOTIFIER - notifierIndex_t NotificationIndex = INVALID_NOTIFIER_INDEX; // index position in Settings.Notification, 0-3 + notifierIndex_t NotificationIndex = INVALID_NOTIFIER_INDEX; // index position in Settings.Notification, 0-3 #endif - uint8_t BaseVarIndex = 0; - Sensor_VType sensorType = Sensor_VType::SENSOR_TYPE_NOT_SET; - uint8_t OriginTaskIndex = 0; + ESPEasy::net::networkIndex_t NetworkIndex = ESPEasy::net::INVALID_NETWORK_INDEX; // index position in Settings.NWPluginID, 0-7 + uint8_t BaseVarIndex = 0; + Sensor_VType sensorType = Sensor_VType::SENSOR_TYPE_NOT_SET; + uint8_t OriginTaskIndex = 0; + }; #endif // DATASTRUCTS_ESPEASY_EVENTSTRUCT_H diff --git a/src/src/DataStructs/ESPeasyControllerCache.cpp b/src/src/DataStructs/ESPeasyControllerCache.cpp index bae5fbee6..33742a54b 100644 --- a/src/src/DataStructs/ESPeasyControllerCache.cpp +++ b/src/src/DataStructs/ESPeasyControllerCache.cpp @@ -63,6 +63,7 @@ bool ControllerCache_struct::deleteOldestCacheBlock() { void ControllerCache_struct::closeOpenFiles() { if (_RTC_cache_handler != nullptr) { + _RTC_cache_handler->flush(); _RTC_cache_handler->closeOpenFiles(); } } @@ -87,7 +88,7 @@ void ControllerCache_struct::resetpeek() { } } -bool ControllerCache_struct::peekDataAvailable() const { +bool ControllerCache_struct::peekDataAvailable() { if (_RTC_cache_handler == nullptr) { return false; } diff --git a/src/src/DataStructs/EthernetEventData.cpp b/src/src/DataStructs/EthernetEventData.cpp deleted file mode 100644 index 008320e9e..000000000 --- a/src/src/DataStructs/EthernetEventData.cpp +++ /dev/null @@ -1,217 +0,0 @@ -#include "../DataStructs/EthernetEventData.h" - -#if FEATURE_ETHERNET - -#include "../ESPEasyCore/ESPEasy_Log.h" -#include "../Globals/Settings.h" -#include "../Helpers/Networking.h" - -#include - -// Bit numbers for Eth status -#define ESPEASY_ETH_CONNECTED 0 -#define ESPEASY_ETH_GOT_IP 1 -#define ESPEASY_ETH_SERVICES_INITIALIZED 2 - - -#if FEATURE_USE_IPV6 -#include - -// ----------------------------------------------------------------------------------------------------------------------- -// ---------------------------------------------------- Private functions ------------------------------------------------ -// ----------------------------------------------------------------------------------------------------------------------- - -esp_netif_t* get_esp_interface_netif(esp_interface_t interface); -#endif - - - -bool EthernetEventData_t::EthConnectAllowed() const { - if (!ethConnectAttemptNeeded) return false; - if (last_eth_connect_attempt_moment.isSet()) { - // TODO TD-er: Make this time more dynamic. - if (!last_eth_connect_attempt_moment.timeoutReached(10000)) { - return false; - } - } - return true; -} - -bool EthernetEventData_t::unprocessedEthEvents() const { - if (processedConnect && processedDisconnect && processedGotIP && processedDHCPTimeout -#if FEATURE_USE_IPV6 - && processedGotIP6 -#endif - ) - { - return false; - } - return true; -} - -void EthernetEventData_t::clearAll() { - lastDisconnectMoment.clear(); - lastConnectMoment.clear(); - lastGetIPmoment.clear(); - last_eth_connect_attempt_moment.clear(); - - lastEthResetMoment.setNow(); - eth_considered_stable = false; - - // Mark all flags to default to prevent handling old events. - processedConnect = true; - processedDisconnect = true; - processedGotIP = true; - #if FEATURE_USE_IPV6 - processedGotIP6 = true; - #endif - processedDHCPTimeout = true; - ethConnectAttemptNeeded = true; - dns0_cache = IPAddress(); - dns1_cache = IPAddress(); -} - -void EthernetEventData_t::markEthBegin() { - lastDisconnectMoment.clear(); - lastConnectMoment.clear(); - lastGetIPmoment.clear(); - last_eth_connect_attempt_moment.setNow(); - eth_considered_stable = false; - ethConnectInProgress = true; - ++eth_connect_attempt; -} - -bool EthernetEventData_t::EthDisconnected() const { - return ethStatus == ESPEASY_ETH_DISCONNECTED; -} - -bool EthernetEventData_t::EthGotIP() const { - - return bitRead(ethStatus, ESPEASY_ETH_GOT_IP); -} - -bool EthernetEventData_t::EthConnected() const { - return bitRead(ethStatus, ESPEASY_ETH_CONNECTED); -} - -bool EthernetEventData_t::EthServicesInitialized() const { - return bitRead(ethStatus, ESPEASY_ETH_SERVICES_INITIALIZED); -} - -void EthernetEventData_t::setEthDisconnected() { - processedConnect = true; - processedDisconnect = true; - processedGotIP = true; - #if FEATURE_USE_IPV6 - processedGotIP6 = true; - #endif - processedDHCPTimeout = true; - - ethStatus = ESPEASY_ETH_DISCONNECTED; -} - -void EthernetEventData_t::setEthGotIP() { - bitSet(ethStatus, ESPEASY_ETH_GOT_IP); - setEthServicesInitialized(); -} - -void EthernetEventData_t::setEthConnected() { - bitSet(ethStatus, ESPEASY_ETH_CONNECTED); - setEthServicesInitialized(); -} - -bool EthernetEventData_t::setEthServicesInitialized() { - if (!unprocessedEthEvents() && !EthServicesInitialized()) { - if (EthGotIP() && EthConnected()) { - if (valid_DNS_address(ETH.dnsIP(0))) { - dns0_cache = ETH.dnsIP(0); - } - if (valid_DNS_address(ETH.dnsIP(1))) { - dns1_cache = ETH.dnsIP(1); - } - - #ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("Eth : Eth services initialized")); - #endif - bitSet(ethStatus, ESPEASY_ETH_SERVICES_INITIALIZED); - ethConnectInProgress = false; - return true; - } - } - return false; -} - -void EthernetEventData_t::markGotIP() { - lastGetIPmoment.setNow(); - - // Create the 'got IP event' so mark the ethStatus to not have the got IP flag set - // This also implies the services are not fully initialized. - bitClear(ethStatus, ESPEASY_ETH_GOT_IP); - bitClear(ethStatus, ESPEASY_ETH_SERVICES_INITIALIZED); - processedGotIP = false; -} - -#if FEATURE_USE_IPV6 -void EthernetEventData_t::markGotIPv6(const IPAddress& ip6) { - processedGotIP6 = false; - unprocessed_IP6 = ip6; -} -#endif - - -void EthernetEventData_t::markLostIP() { - bitClear(ethStatus, ESPEASY_ETH_GOT_IP); - bitClear(ethStatus, ESPEASY_ETH_SERVICES_INITIALIZED); - lastGetIPmoment.clear(); - processedGotIP = false; -} - -void EthernetEventData_t::markDisconnect() { - lastDisconnectMoment.setNow(); - - if (last_eth_connect_attempt_moment.isSet() && !lastConnectMoment.isSet()) { - // There was an unsuccessful connection attempt - lastConnectedDuration_us = last_eth_connect_attempt_moment.timeDiff(lastDisconnectMoment); - } else { - lastConnectedDuration_us = lastConnectMoment.timeDiff(lastDisconnectMoment); - } - lastConnectMoment.clear(); - processedDisconnect = false; -#if ESP_IDF_VERSION_MAJOR >= 5 - WiFi.STA.setDefault(); -#endif -} - -void EthernetEventData_t::markConnected() { - lastConnectMoment.setNow(); - processedConnect = false; -#if ESP_IDF_VERSION_MAJOR >= 5 - ETH.setDefault(); -#endif - -#if FEATURE_USE_IPV6 - if (Settings.EnableIPv6()) { - ETH.enableIPv6(true); - } -#endif -} - -String EthernetEventData_t::ESPEasyEthStatusToString() const { - String log; - if (EthDisconnected()) { - log = F("DISCONNECTED"); - } else { - if (EthConnected()) { - log += F("Conn. "); - } - if (EthGotIP()) { - log += F("IP "); - } - if (EthServicesInitialized()) { - log += F("Init"); - } - } - return log; -} - -#endif diff --git a/src/src/DataStructs/EthernetEventData.h b/src/src/DataStructs/EthernetEventData.h deleted file mode 100644 index d17fe04b4..000000000 --- a/src/src/DataStructs/EthernetEventData.h +++ /dev/null @@ -1,99 +0,0 @@ -#ifndef DATASTRUCTS_ETHERNETEVENTDATA_H -#define DATASTRUCTS_ETHERNETEVENTDATA_H - -#include "../../ESPEasy_common.h" - -#if FEATURE_ETHERNET - -#include "../Helpers/LongTermTimer.h" - - - - -#ifdef ESP32 -# include -# include - -#endif // ifdef ESP32 - -#include - - -// EthStatus -#define ESPEASY_ETH_DISCONNECTED 0 - - -struct EthernetEventData_t { - bool EthConnectAllowed() const; - - bool unprocessedEthEvents() const; - - void clearAll(); - void markEthBegin(); - - bool EthDisconnected() const; - bool EthGotIP() const; - bool EthConnected() const; - bool EthServicesInitialized() const; - - void setEthDisconnected(); - void setEthGotIP(); - void setEthConnected(); - bool setEthServicesInitialized(); - - - void markGotIP(); -#if FEATURE_USE_IPV6 - void markGotIPv6(const IPAddress& ip6); -#endif - void markLostIP(); - void markDisconnect(); - void markConnected(); - - String ESPEasyEthStatusToString() const; - - - // Eth related data - bool ethSetup = false; - uint8_t ethStatus = ESPEASY_ETH_DISCONNECTED; - LongTermTimer last_eth_connect_attempt_moment; - unsigned int eth_connect_attempt = 0; - bool eth_considered_stable = false; - int eth_reconnects = -1; // First connection attempt is not a reconnect. - - LongTermTimer lastConnectMoment; - LongTermTimer lastDisconnectMoment; - LongTermTimer lastEthResetMoment; - LongTermTimer lastGetIPmoment; - LongTermTimer::Duration lastConnectedDuration_us = 0ll; - - IPAddress dns0_cache{}; - IPAddress dns1_cache{}; - #if FEATURE_USE_IPV6 - IPAddress unprocessed_IP6; - #endif - - // Semaphore like bools for processing data gathered from Eth events. - bool processedConnect = true; - bool processedDisconnect = true; - bool processedGotIP = true; - #if FEATURE_USE_IPV6 - bool processedGotIP6 = true; - #endif - bool processedDHCPTimeout = true; - bool ethConnectAttemptNeeded = true; - bool ethConnectInProgress = false; - - - bool ethInitSuccess = false; - unsigned long connectionFailures = 0; - -#ifdef ESP32 - WiFiEventId_t wm_event_id = 0; -#endif // ifdef ESP32 - -}; - -#endif - -#endif // ifndef DATASTRUCTS_ETHERNETEVENTDATA_H diff --git a/src/src/DataStructs/FactoryDefaultPref.h b/src/src/DataStructs/FactoryDefaultPref.h index 2188266af..cb0e23580 100644 --- a/src/src/DataStructs/FactoryDefaultPref.h +++ b/src/src/DataStructs/FactoryDefaultPref.h @@ -45,8 +45,9 @@ private: uint32_t storeCredentials : 1; uint32_t fetchProvisioningDat : 1; uint32_t keepCustomCdnUrl : 1; + uint32_t fetchDeviceSecurityDat : 1; - uint32_t unused : 5; + uint32_t unused : 4; } bits; uint32_t _preference{}; }; @@ -150,6 +151,17 @@ public: bits.fetchProvisioningDat = fetch; } +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + + bool fetchDeviceSecurityDat() const { + return bits.fetchDeviceSecurityDat; + } + + void fetchDeviceSecurityDat(bool fetch) { + bits.fetchDeviceSecurityDat = fetch; + } +#endif + bool deleteFirst() const { return bits.deleteFirst; } diff --git a/src/src/DataStructs/FactoryDefault_Network_NVS.cpp b/src/src/DataStructs/FactoryDefault_Network_NVS.cpp index 97a725e8c..e93ea261b 100644 --- a/src/src/DataStructs/FactoryDefault_Network_NVS.cpp +++ b/src/src/DataStructs/FactoryDefault_Network_NVS.cpp @@ -38,9 +38,15 @@ bool FactoryDefault_Network_NVS::applyToSettings_from_NVS(ESPEasy_NVS_Helper& pr Settings.ETH_Pin_mdc_cs = bits.ETH_Pin_mdc_cs; Settings.ETH_Pin_mdio_irq = bits.ETH_Pin_mdio_irq; Settings.ETH_Pin_power_rst = bits.ETH_Pin_power_rst; - Settings.ETH_Phy_Type = static_cast(bits.ETH_Phy_Type); - Settings.ETH_Clock_Mode = static_cast(bits.ETH_Clock_Mode); - Settings.NetworkMedium = static_cast(bits.NetworkMedium); +#if FEATURE_ETHERNET + Settings.ETH_Phy_Type = static_cast(bits.ETH_Phy_Type); +#else + Settings.ETH_Phy_Type = 0; +#endif +# if CONFIG_ETH_USE_ESP32_EMAC && FEATURE_ETHERNET + Settings.ETH_Clock_Mode = static_cast(bits.ETH_Clock_Mode); +#endif + Settings.NetworkMedium = static_cast(bits.NetworkMedium); res = true; } # endif // ifdef FEATURE_ETHERNET diff --git a/src/src/DataStructs/FactoryDefault_WiFi_NVS.cpp b/src/src/DataStructs/FactoryDefault_WiFi_NVS.cpp index 8cc486e54..736afce27 100644 --- a/src/src/DataStructs/FactoryDefault_WiFi_NVS.cpp +++ b/src/src/DataStructs/FactoryDefault_WiFi_NVS.cpp @@ -17,8 +17,8 @@ void FactoryDefault_WiFi_NVS::fromSettings() { bits.IncludeHiddenSSID = Settings.IncludeHiddenSSID(); - bits.ApDontForceSetup = Settings.ApDontForceSetup(); - bits.DoNotStartAP = Settings.DoNotStartAP(); + bits.ApCaptivePortal_inv = Settings.ApCaptivePortal(); + bits.DoNotStartAPfallback_ConnectFail = Settings.DoNotStartAPfallback_ConnectFail(); bits.ForceWiFi_bg_mode = Settings.ForceWiFi_bg_mode(); bits.WiFiRestart_connection_lost = Settings.WiFiRestart_connection_lost(); bits.WifiNoneSleep = Settings.WifiNoneSleep(); @@ -34,8 +34,8 @@ void FactoryDefault_WiFi_NVS::fromSettings() { void FactoryDefault_WiFi_NVS::applyToSettings() const { Settings.IncludeHiddenSSID(bits.IncludeHiddenSSID); - Settings.ApDontForceSetup(bits.ApDontForceSetup); - Settings.DoNotStartAP(bits.DoNotStartAP); + Settings.ApCaptivePortal(bits.ApCaptivePortal_inv); + Settings.DoNotStartAPfallback_ConnectFail(bits.DoNotStartAPfallback_ConnectFail); Settings.ForceWiFi_bg_mode(bits.ForceWiFi_bg_mode); Settings.WiFiRestart_connection_lost(bits.WiFiRestart_connection_lost); Settings.WifiNoneSleep(bits.WifiNoneSleep); diff --git a/src/src/DataStructs/FactoryDefault_WiFi_NVS.h b/src/src/DataStructs/FactoryDefault_WiFi_NVS.h index e3638b084..ab89546b6 100644 --- a/src/src/DataStructs/FactoryDefault_WiFi_NVS.h +++ b/src/src/DataStructs/FactoryDefault_WiFi_NVS.h @@ -29,8 +29,8 @@ private: union { struct { uint64_t IncludeHiddenSSID : 1; - uint64_t ApDontForceSetup : 1; - uint64_t DoNotStartAP : 1; + uint64_t ApCaptivePortal_inv : 1; // Was ApDontForceSetup, so store inverted to remain compatible with existing settings + uint64_t DoNotStartAPfallback_ConnectFail : 1; uint64_t ForceWiFi_bg_mode : 1; uint64_t WiFiRestart_connection_lost : 1; uint64_t WifiNoneSleep : 1; diff --git a/src/src/DataStructs/GpioFactorySettingsStruct.cpp b/src/src/DataStructs/GpioFactorySettingsStruct.cpp index b0e6838a8..c00ea9a6b 100644 --- a/src/src/DataStructs/GpioFactorySettingsStruct.cpp +++ b/src/src/DataStructs/GpioFactorySettingsStruct.cpp @@ -8,11 +8,15 @@ GpioFactorySettingsStruct::GpioFactorySettingsStruct(DeviceModel model) i2c_sda(DEFAULT_PIN_I2C_SDA), i2c_scl(DEFAULT_PIN_I2C_SCL), eth_phyaddr(DEFAULT_ETH_PHY_ADDR), +#if FEATURE_ETHERNET eth_phytype(DEFAULT_ETH_PHY_TYPE), +#endif eth_mdc(DEFAULT_ETH_PIN_MDC), eth_mdio(DEFAULT_ETH_PIN_MDIO), eth_power(DEFAULT_ETH_PIN_POWER), +#if FEATURE_ETHERNET eth_clock_mode(DEFAULT_ETH_CLOCK_MODE), +#endif network_medium(DEFAULT_NETWORK_MEDIUM) { @@ -23,8 +27,9 @@ GpioFactorySettingsStruct::GpioFactorySettingsStruct(DeviceModel model) #ifndef LIMIT_BUILD_SIZE - switch (model) { -#if defined(ESP8266) + switch (model) + { +# if defined(ESP8266) case DeviceModel::DeviceModel_Sonoff_Basic: case DeviceModel::DeviceModel_Sonoff_TH1x: case DeviceModel::DeviceModel_Sonoff_S2x: @@ -65,33 +70,33 @@ GpioFactorySettingsStruct::GpioFactorySettingsStruct(DeviceModel model) break; case DeviceModel::DeviceModel_Sonoff_4ch: - button[0] = 0; // Button 1 - button[1] = 9; // Button 2 - button[2] = 10; // Button 3 - button[3] = 14; // Button 4 - relais[0] = 12; // Red Led and Relay1 (0 = Off, 1 = On) - relais[1] = 5; // Red Led and Relay2 (0 = Off, 1 = On) - relais[2] = 4; // Red Led and Relay3 (0 = Off, 1 = On) - relais[3] = 15; // Red Led and Relay4 (0 = Off, 1 = On) - status_led = 13; // Blue Led (0 = On, 1 = Off) - i2c_sda = -1; // GPIO4 conflicts with GPIO_REL3 - i2c_scl = -1; // GPIO5 conflicts with GPIO_REL2 + button[0] = 0; // Button 1 + button[1] = 9; // Button 2 + button[2] = 10; // Button 3 + button[3] = 14; // Button 4 + relais[0] = 12; // Red Led and Relay1 (0 = Off, 1 = On) + relais[1] = 5; // Red Led and Relay2 (0 = Off, 1 = On) + relais[2] = 4; // Red Led and Relay3 (0 = Off, 1 = On) + relais[3] = 15; // Red Led and Relay4 (0 = Off, 1 = On) + status_led = 13; // Blue Led (0 = On, 1 = Off) + i2c_sda = -1; // GPIO4 conflicts with GPIO_REL3 + i2c_scl = -1; // GPIO5 conflicts with GPIO_REL2 break; case DeviceModel::DeviceModel_Shelly1: - button[0] = 5; // Single Button - relais[0] = 4; // Red Led and Relay (0 = Off, 1 = On) - status_led = 15; // Blue Led (0 = On, 1 = Off) - i2c_sda = -1; // GPIO4 conflicts with relay control. - i2c_scl = -1; // GPIO5 conflicts with SW input + button[0] = 5; // Single Button + relais[0] = 4; // Red Led and Relay (0 = Off, 1 = On) + status_led = 15; // Blue Led (0 = On, 1 = Off) + i2c_sda = -1; // GPIO4 conflicts with relay control. + i2c_scl = -1; // GPIO5 conflicts with SW input break; case DeviceModel::DeviceModel_ShellyPLUG_S: - button[0] = 13; // Single Button - relais[0] = 15; // Red Led and Relay (0 = Off, 1 = On) - status_led = 2; // Blue Led (0 = On, 1 = Off) - i2c_sda = -1; // GPIO4 conflicts with relay control. - i2c_scl = -1; // GPIO5 conflicts with SW input + button[0] = 13; // Single Button + relais[0] = 15; // Red Led and Relay (0 = Off, 1 = On) + status_led = 2; // Blue Led (0 = On, 1 = Off) + i2c_sda = -1; // GPIO4 conflicts with relay control. + i2c_scl = -1; // GPIO5 conflicts with SW input break; -#else +# else // if defined(ESP8266) case DeviceModel::DeviceModel_Sonoff_Basic: case DeviceModel::DeviceModel_Sonoff_TH1x: case DeviceModel::DeviceModel_Sonoff_S2x: @@ -104,54 +109,56 @@ GpioFactorySettingsStruct::GpioFactorySettingsStruct(DeviceModel model) case DeviceModel::DeviceModel_Shelly1: case DeviceModel::DeviceModel_ShellyPLUG_S: break; -#endif +# endif // if defined(ESP8266) -# if CONFIG_ETH_USE_ESP32_EMAC +# if CONFIG_ETH_USE_ESP32_EMAC && FEATURE_ETHERNET +# if CONFIG_IDF_TARGET_ESP32 case DeviceModel::DeviceModel_Olimex_ESP32_PoE: - button[0] = 34; // BUT1 Button - relais[0] = -1; // No LED's or relays on board - status_led = -1; - i2c_sda = 13; - i2c_scl = 16; - eth_phyaddr = 0; - eth_phytype = EthPhyType_t::LAN8720; - eth_mdc = 23; - eth_mdio = 18; - eth_power = 12; - eth_clock_mode = EthClockMode_t::Int_50MHz_GPIO_17_inv; - network_medium = NetworkMedium_t::Ethernet; + button[0] = 34; // BUT1 Button + relais[0] = -1; // No LED's or relays on board + status_led = -1; + i2c_sda = 13; + i2c_scl = 16; + eth_phyaddr = 0; + eth_phytype = ESPEasy::net::EthPhyType_t::LAN8720; + eth_mdc = 23; + eth_mdio = 18; + eth_power = 12; + eth_clock_mode = ESPEasy::net::EthClockMode_t::Int_50MHz_GPIO_17_inv; + network_medium = ESPEasy::net::NetworkMedium_t::Ethernet; break; case DeviceModel::DeviceModel_Olimex_ESP32_EVB: button[0] = 34; // BUT1 Button relais[0] = 32; // LED1 + Relay1 (0 = Off, 1 = On) relais[1] = 33; // LED2 + Relay2 (0 = Off, 1 = On) - status_led = -1; - i2c_sda = 13; - i2c_scl = 16; - eth_phyaddr = 0; - eth_phytype = EthPhyType_t::LAN8720; - eth_mdc = 23; - eth_mdio = 18; - eth_power = -1; // No Ethernet power pin - eth_clock_mode = EthClockMode_t::Ext_crystal_osc; - network_medium = NetworkMedium_t::Ethernet; + status_led = -1; + i2c_sda = 13; + i2c_scl = 16; + eth_phyaddr = 0; + eth_phytype = ESPEasy::net::EthPhyType_t::LAN8720; + eth_mdc = 23; + eth_mdio = 18; + eth_power = -1; // No Ethernet power pin + eth_clock_mode = ESPEasy::net::EthClockMode_t::Ext_crystal_osc; + network_medium = ESPEasy::net::NetworkMedium_t::Ethernet; break; case DeviceModel::DeviceModel_Olimex_ESP32_GATEWAY: - button[0] = 34; // BUT1 Button - relais[0] = -1; // No LED's or relays on board - status_led = 33; - i2c_sda = -1; - i2c_scl = -1; - eth_phyaddr = 0; - eth_phytype = EthPhyType_t::LAN8720; - eth_mdc = 23; - eth_mdio = 18; - eth_power = 5; - eth_clock_mode = EthClockMode_t::Int_50MHz_GPIO_17_inv; - network_medium = NetworkMedium_t::Ethernet; + button[0] = 34; // BUT1 Button + relais[0] = -1; // No LED's or relays on board + status_led = 33; + i2c_sda = -1; + i2c_scl = -1; + eth_phyaddr = 0; + eth_phytype = ESPEasy::net::EthPhyType_t::LAN8720; + eth_mdc = 23; + eth_mdio = 18; + eth_power = 5; + eth_clock_mode = ESPEasy::net::EthClockMode_t::Int_50MHz_GPIO_17_inv; + network_medium = ESPEasy::net::NetworkMedium_t::Ethernet; + // Rev A to E: // GPIO 5, 17 can be used only if Ethernet functionality is not used // GPIO 6, 7, 8, 9, 10, 11 used for internal flash and SD card @@ -174,32 +181,34 @@ GpioFactorySettingsStruct::GpioFactorySettingsStruct(DeviceModel model) break; case DeviceModel::DeviceModel_wESP32: - status_led = -1; - i2c_sda = 15; - i2c_scl = 4; - eth_phyaddr = 0; - eth_phytype = EthPhyType_t::LAN8720; - eth_mdc = 16; - eth_mdio = 17; - eth_power = -1; - eth_clock_mode = EthClockMode_t::Ext_crystal_osc; - network_medium = NetworkMedium_t::Ethernet; + status_led = -1; + i2c_sda = 15; + i2c_scl = 4; + eth_phyaddr = 0; + eth_phytype = ESPEasy::net::EthPhyType_t::LAN8720; + eth_mdc = 16; + eth_mdio = 17; + eth_power = -1; + eth_clock_mode = ESPEasy::net::EthClockMode_t::Ext_crystal_osc; + network_medium = ESPEasy::net::NetworkMedium_t::Ethernet; break; case DeviceModel::DeviceModel_WT32_ETH01: - status_led = -1; - i2c_sda = 21; - i2c_scl = 22; - eth_phyaddr = 1; - eth_phytype = EthPhyType_t::LAN8720; - eth_mdc = 23; - eth_mdio = 18; - eth_power = 12; // TODO TD-er: Better to use GPIO-16? as shown here: https://letscontrolit.com/forum/viewtopic.php?p=50133#p50133 - eth_clock_mode = EthClockMode_t::Ext_crystal_osc; - network_medium = NetworkMedium_t::Ethernet; + status_led = -1; + i2c_sda = 21; + i2c_scl = 22; + eth_phyaddr = 1; + eth_phytype = ESPEasy::net::EthPhyType_t::LAN8720; + eth_mdc = 23; + eth_mdio = 18; + eth_power = 12; // TODO TD-er: Better to use GPIO-16? as shown here: https://letscontrolit.com/forum/viewtopic.php?p=50133#p50133 + eth_clock_mode = ESPEasy::net::EthClockMode_t::Ext_crystal_osc; + network_medium = ESPEasy::net::NetworkMedium_t::Ethernet; break; - - #endif +# else // if CONFIG_IDF_TARGET_ESP32 + default: break; +# endif // if CONFIG_IDF_TARGET_ESP32 + # endif // if CONFIG_ETH_USE_ESP32_EMAC && FEATURE_ETHERNET case DeviceModel::DeviceModel_default: case DeviceModel::DeviceModel_MAX: @@ -208,5 +217,5 @@ GpioFactorySettingsStruct::GpioFactorySettingsStruct(DeviceModel model) // Do not use default: as this allows the compiler to detect any missing cases. // default: break; } - #endif + #endif // ifndef LIMIT_BUILD_SIZE } diff --git a/src/src/DataStructs/GpioFactorySettingsStruct.h b/src/src/DataStructs/GpioFactorySettingsStruct.h index bb54c84ff..c4a7c9c04 100644 --- a/src/src/DataStructs/GpioFactorySettingsStruct.h +++ b/src/src/DataStructs/GpioFactorySettingsStruct.h @@ -2,10 +2,10 @@ #define DATASTRUCTS_GPIOFACTORYSETTINGSSTRUCT_H #include "../DataTypes/DeviceModel.h" -#include "../DataTypes/EthernetParameters.h" -#include "../DataTypes/NetworkMedium.h" - - +#include "../../ESPEasy/net/DataTypes/NetworkMedium.h" +#if FEATURE_ETHERNET +#include "../../ESPEasy/net/DataTypes/EthernetParameters.h" +#endif struct GpioFactorySettingsStruct { GpioFactorySettingsStruct(DeviceModel model = DeviceModel::DeviceModel_default); @@ -16,12 +16,20 @@ struct GpioFactorySettingsStruct { int8_t i2c_sda; int8_t i2c_scl; int8_t eth_phyaddr; - EthPhyType_t eth_phytype; +#if FEATURE_ETHERNET + ESPEasy::net::EthPhyType_t eth_phytype; +#else + uint8_t eth_phytype{}; +#endif int8_t eth_mdc; int8_t eth_mdio; int8_t eth_power; - EthClockMode_t eth_clock_mode; - NetworkMedium_t network_medium; +#if FEATURE_ETHERNET && CONFIG_ETH_USE_ESP32_EMAC + ESPEasy::net::EthClockMode_t eth_clock_mode; +#else + uint8_t eth_clock_mode{}; +#endif + ESPEasy::net::NetworkMedium_t network_medium; }; diff --git a/src/src/DataStructs/KeyValueStruct.cpp b/src/src/DataStructs/KeyValueStruct.cpp new file mode 100644 index 000000000..99c75cc87 --- /dev/null +++ b/src/src/DataStructs/KeyValueStruct.cpp @@ -0,0 +1,176 @@ +#include "../DataStructs/KeyValueStruct.h" + +#include "../Helpers/ESPEasy_UnitOfMeasure.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/StringConverter_Numerical.h" + +#include "../WebServer/Markup.h" + + +// ******************************************************************************** +// KeyValueStruct +// ******************************************************************************** + +KeyValueStruct::KeyValueStruct(const __FlashStringHelper *key, Format format) : _key(key), _format(format) {} + +KeyValueStruct::KeyValueStruct(const String& key, Format format) : _key(key), _format(format) {} + + +KeyValueStruct::KeyValueStruct(const String& key, + const bool & val, + Format format) + : _key(key), _format(format) { + _values.emplace_back(ValueStruct(val)); +} + +KeyValueStruct::KeyValueStruct(const String& key, + int val, + Format format) + : _key(key), _format(format) { + _values.emplace_back(ValueStruct(val)); +} + +#if defined(ESP32) && !defined(__riscv) +KeyValueStruct::KeyValueStruct(const String& key, + int32_t val, + Format format) + : _key(key), _format(format) { + _values.emplace_back(ValueStruct(val)); +} + +#endif // if defined(ESP32) && !defined(__riscv) +KeyValueStruct::KeyValueStruct(const String& key, + uint32_t val, + Format format) + : _key(key), _format(format) { + _values.emplace_back(ValueStruct(val)); +} + +#if defined(ESP32) && !defined(__riscv) +KeyValueStruct::KeyValueStruct(const String& key, + size_t val, + Format format) + : _key(key), _format(format) { + _values.emplace_back(ValueStruct(val)); +} + +#endif // if defined(ESP32) && !defined(__riscv) +KeyValueStruct::KeyValueStruct(const String & key, + const uint64_t& val, + Format format) + : _key(key), _format(format) { + _values.emplace_back(ValueStruct(val)); +} + +KeyValueStruct::KeyValueStruct(const String & key, + const int64_t& val, + Format format) + : _key(key), _format(format) { + _values.emplace_back(ValueStruct(val)); +} + +KeyValueStruct::KeyValueStruct(const String& key, + const float & val, + uint8_t nrDecimals, + Format format) + : _key(key), _format(format) { + _values.emplace_back(ValueStruct(val, nrDecimals)); +} + +KeyValueStruct::KeyValueStruct(const String& key, + const double& val, + uint8_t nrDecimals, + Format format) + : _key(key), _format(format) { + _values.emplace_back(ValueStruct(val, nrDecimals)); +} + +KeyValueStruct::KeyValueStruct(const __FlashStringHelper *key, + const String & val, + Format format) + : _key(key), _format(format) { + _values.emplace_back(ValueStruct(val)); +} + +KeyValueStruct::KeyValueStruct(const String & key, + const __FlashStringHelper *val, + Format format) + : _key(key), _format(format) { + _values.emplace_back(ValueStruct(val)); +} + +KeyValueStruct::KeyValueStruct(const __FlashStringHelper *key, + const __FlashStringHelper *val, + Format format) + : _key(key), _format(format) { + _values.emplace_back(ValueStruct(val)); +} + +KeyValueStruct::KeyValueStruct(const __FlashStringHelper *key, + const char *val, + Format format) + : _key(key), _format(format) { + _values.emplace_back(ValueStruct(String(val))); +} + +KeyValueStruct::KeyValueStruct(const String& key, + const String& val, + Format format) + : _key(key), _format(format) { + _values.emplace_back(ValueStruct(val)); +} + +KeyValueStruct::KeyValueStruct(const __FlashStringHelper *key, + String && val, + Format format) + : _key(key), _format(format) { + _values.emplace_back(ValueStruct(std::move(val))); +} + +KeyValueStruct::KeyValueStruct(const String& key, + String && val, + Format format) + : _key(key), _format(format) { + _values.emplace_back(ValueStruct(std::move(val))); +} + +#if FEATURE_TASKVALUE_UNIT_OF_MEASURE + String KeyValueStruct::getUnit() const + { + return toUnitOfMeasureName(_uomIndex); + } +#endif + +void KeyValueStruct::setID(const String& id) { __id = id; } + +void KeyValueStruct::setID(const __FlashStringHelper *id) { __id = id; } + +void KeyValueStruct::appendValue(ValueStruct&& value) +{ + _values.emplace_back(std::move(value)); + _isArray = true; +} + +void KeyValueStruct::appendValue(const String& value) +{ + _values.emplace_back(ValueStruct(value)); + _isArray = true; +} + +void KeyValueStruct::appendValue(const __FlashStringHelper * value) +{ + _values.emplace_back(ValueStruct(value)); + _isArray = true; +} + +void KeyValueStruct::appendValue(String&& value) +{ + _values.emplace_back(ValueStruct(std::move(value))); + _isArray = true; +} + +String KeyValueStruct::getID() const +{ + if (__id.isEmpty()) { return to_internal_string(_key.toString(), '_'); } + return __id.toString(); +} diff --git a/src/src/DataStructs/KeyValueStruct.h b/src/src/DataStructs/KeyValueStruct.h new file mode 100644 index 000000000..902b75b05 --- /dev/null +++ b/src/src/DataStructs/KeyValueStruct.h @@ -0,0 +1,139 @@ +#pragma once + +#include "../DataStructs/ValueStruct.h" + +#include "../Helpers/StringProvider.h" + +#include + +// ******************************************************************************** +// KeyValueStruct +// ******************************************************************************** +struct KeyValueStruct +{ + enum class Format { + Default, + PreFormatted, + Note + + }; + + + KeyValueStruct(Format format = Format::Default) : _format(format) {} + + KeyValueStruct(const __FlashStringHelper *key, + Format format = Format::Default); + KeyValueStruct(const String& key, + Format format = Format::Default); + + KeyValueStruct(const String & key, + const bool & val, + Format format = Format::Default); + + KeyValueStruct(const String & key, + int val, + Format format = Format::Default); +#if defined(ESP32) && !defined(__riscv) + KeyValueStruct(const String & key, + int32_t val, + Format format = Format::Default); +#endif + KeyValueStruct(const String & key, + uint32_t val, + Format format = Format::Default); +#if defined(ESP32) && !defined(__riscv) + KeyValueStruct(const String & key, + size_t val, + Format format = Format::Default); +#endif + KeyValueStruct(const String & key, + const uint64_t & val, + Format format = Format::Default); + + KeyValueStruct(const String & key, + const int64_t & val, + Format format = Format::Default); + + + KeyValueStruct(const String & key, + const float & val, + uint8_t nrDecimals = 4, + Format format = Format::Default); + + KeyValueStruct(const String & key, + const double & val, + uint8_t nrDecimals = 4, + Format format = Format::Default); + + + KeyValueStruct(const __FlashStringHelper *key, + const String & val, + Format format = Format::Default); + + KeyValueStruct(const String & key, + const __FlashStringHelper *val, + Format format = Format::Default); + + KeyValueStruct(const __FlashStringHelper *key, + const __FlashStringHelper *val, + Format format = Format::Default); + + KeyValueStruct(const __FlashStringHelper *key, + const char *val, + Format format = Format::Default); + + KeyValueStruct(const String & key, + const String & val, + Format format = Format::Default); + + KeyValueStruct(const __FlashStringHelper *key, + String && val, + Format format = Format::Default); + + + KeyValueStruct(const String & key, + String && val, + Format format = Format::Default); + + /* + // TD-er: Do not use template types as it may 'explode' in binary size. + // For example "foo" and "test" will result in 2 compiled instances + // as they are const char[3] and const char[4] respectively. + template + KeyValueStruct(const String & key, + const T & val, + Format format = Format::Default, + ValueStruct::ValueType vType = ValueStruct::ValueType::Auto) + : _key(key), _format(format) { + _values.emplace_back(String(val), vType); + } + */ + +#if FEATURE_TASKVALUE_UNIT_OF_MEASURE + void setUnit(uint8_t uomIndex) { _uomIndex = uomIndex; } + String getUnit() const; +#endif + + void setID(const String& id); + void setID(const __FlashStringHelper *id); + + void appendValue(ValueStruct&& value); + void appendValue(const String& value); + void appendValue(const __FlashStringHelper * value); + void appendValue(String&& value); + + String getID() const; + + + ValueStruct _key; + ValueStruct __id; + std::vector_values; + + Format _format = Format::Default; +#if FEATURE_TASKVALUE_UNIT_OF_MEASURE + uint8_t _uomIndex{}; +#endif + + bool _isArray{}; + +}; \ No newline at end of file diff --git a/src/src/DataStructs/LogBuffer.cpp b/src/src/DataStructs/LogBuffer.cpp new file mode 100644 index 000000000..065db1c90 --- /dev/null +++ b/src/src/DataStructs/LogBuffer.cpp @@ -0,0 +1,75 @@ +#include "../DataStructs/LogBuffer.h" + +#include "../Helpers/ESPEasy_time_calc.h" +#include "../Helpers/StringConverter.h" + +void LogBuffer::add(LogEntry_t&& logEntry) { + clearExpiredEntries(); + + if (logEntry) { + LogEntries.emplace_back(std::move(logEntry)); + } +} + +bool LogBuffer::getNext(LogDestination logDestination, uint32_t& timestamp, String& message, uint8_t& loglevel) +{ + if (logDestination >= NR_LOG_TO_DESTINATIONS) { return false; } + + lastReadTimeStamp[logDestination] = millis(); + + while (cache_iterator_pos[logDestination] < LogEntries.size()) + { + const auto pos = cache_iterator_pos[logDestination]; + ++cache_iterator_pos[logDestination]; + + if (LogEntries[pos].validForSubscriber(logDestination)) { + timestamp = LogEntries[pos].getTimestamp(); + message = LogEntries[pos].getMessage(); + loglevel = LogEntries[pos].getLogLevel(); + LogEntries[pos].markReadBySubscriber(logDestination); + clearExpiredEntries(); + return true; + } + } + return false; +} + +uint32_t LogBuffer::getNrMessages(LogDestination logDestination) const +{ + uint32_t res{}; + + if (logDestination >= NR_LOG_TO_DESTINATIONS) { return res; } + + uint32_t pos = cache_iterator_pos[logDestination]; + + for (; pos < LogEntries.size(); ++pos) { + if (LogEntries[pos].validForSubscriber(logDestination)) { + ++res; + } + } + return res; +} + +bool LogBuffer::logActiveRead(LogDestination logDestination) { + if (logDestination >= NR_LOG_TO_DESTINATIONS) { return false; } + return timePassedSince(lastReadTimeStamp[logDestination]) < LOG_BUFFER_ACTIVE_READ_TIMEOUT; +} + +void LogBuffer::clearExpiredEntries() { + for (auto it = LogEntries.begin(); it != LogEntries.end();) + { + it->updateSubscribers(); + + if (it->isExpired()) { + for (size_t i = 0; i < NR_LOG_TO_DESTINATIONS; ++i) { + if (cache_iterator_pos[i]) { + --cache_iterator_pos[i]; + } + } + + it = LogEntries.erase(it); + } else { + return; + } + } +} diff --git a/src/src/DataStructs/LogBuffer.h b/src/src/DataStructs/LogBuffer.h new file mode 100644 index 000000000..8df20ab07 --- /dev/null +++ b/src/src/DataStructs/LogBuffer.h @@ -0,0 +1,72 @@ +#ifndef DATASTRUCTS_LOGSTRUCT_H +#define DATASTRUCTS_LOGSTRUCT_H + + +#include "../../ESPEasy_common.h" + +#include "../DataStructs/LogEntry.h" + +#include "../DataTypes/LogLevels.h" + +#include + +/*********************************************************************************************\ +* LogBuffer +\*********************************************************************************************/ +#ifdef ESP32 + # define LOG_STRUCT_MESSAGE_LINES 120 +#else + # ifdef USE_SECOND_HEAP + # define LOG_STRUCT_MESSAGE_LINES 60 + # else + # if defined(PLUGIN_BUILD_COLLECTION) || defined(PLUGIN_BUILD_DEV) + # define LOG_STRUCT_MESSAGE_LINES 10 + # else + # define LOG_STRUCT_MESSAGE_LINES 15 + # endif // if defined(PLUGIN_BUILD_COLLECTION) || defined(PLUGIN_BUILD_DEV) + # endif // ifdef USE_SECOND_HEAP +#endif // ifdef ESP32 + +#ifdef ESP32 + # define LOG_BUFFER_ACTIVE_READ_TIMEOUT 30000 +#else + # define LOG_BUFFER_ACTIVE_READ_TIMEOUT 5000 +#endif // ifdef ESP32 + +typedef std::deque LogEntry_queue; + + +struct LogBuffer { + + LogBuffer() = default; + + void add(LogEntry_t&& logEntry); + + bool isEmpty() const { + return LogEntries.empty(); + } + + // Returns whether a line was retrieved. + bool getNext(LogDestination logDestination, + uint32_t& timestamp, + String & message, + uint8_t & loglevel); + + // Return the number of messages left for given log destination. + uint32_t getNrMessages(LogDestination logDestination) const; + + bool logActiveRead(LogDestination logDestination); + + void clearExpiredEntries(); + +private: + + LogEntry_queue LogEntries; + uint32_t lastReadTimeStamp[NR_LOG_TO_DESTINATIONS]{}; + + uint32_t cache_iterator_pos[NR_LOG_TO_DESTINATIONS]{}; + +}; + + +#endif // DATASTRUCTS_LOGSTRUCT_H diff --git a/src/src/DataStructs/LogEntry.cpp b/src/src/DataStructs/LogEntry.cpp index ac45bfd01..3d915b0bf 100644 --- a/src/src/DataStructs/LogEntry.cpp +++ b/src/src/DataStructs/LogEntry.cpp @@ -1,71 +1,208 @@ #include "../DataStructs/LogEntry.h" + #include "../Helpers/ESPEasy_time_calc.h" #include "../Helpers/StringConverter.h" -#define LOG_STRUCT_MESSAGE_SIZE 128 -#ifdef ESP32 - #define LOG_BUFFER_EXPIRE 30000 // Time after which a buffered log item is considered expired. -#else - #define LOG_BUFFER_EXPIRE 5000 // Time after which a buffered log item is considered expired. -#endif +LogEntry_t::LogEntry_t(const uint8_t logLevel, + const __FlashStringHelper *message) : + _message((void *)(message)), + _timestamp(millis()), + _strLength(message ? strlen_P((const char *)(message)) : 0), + _isFlashString(true), + _logLevel(logLevel), + _subscriberPendingRead(0) +{} - -bool LogEntry_t::add(const uint8_t loglevel, const String& line) +LogEntry_t::LogEntry_t(const uint8_t logLevel, + const char *message) : + _message(nullptr), + _timestamp(millis()), + _strLength(message ? strlen_P((const char *)(message)) : 0), + _isFlashString(false), + _logLevel(logLevel), + _subscriberPendingRead(0) { - if (line.length() == 0) { - return false; - } + if (_strLength && loglevelActiveFor(logLevel)) { + _message = special_calloc(1, _strLength + 1); - { - if (line.length() > LOG_STRUCT_MESSAGE_SIZE - 1) { - #ifdef USE_SECOND_HEAP - - // Need to make a substring or a copy, which is a new allocation, on the 2nd heap - HeapSelectIram ephemeral; - #endif // ifdef USE_SECOND_HEAP - move_special(_message, line.substring(0, LOG_STRUCT_MESSAGE_SIZE - 1)); - } else { - reserve_special(_message, line.length()); - _message = line; + if (_message) { + memcpy_P(_message, message, _strLength); } } - _loglevel = loglevel; - _timestamp = millis(); - return true; } -bool LogEntry_t::add(const uint8_t loglevel, String&& line) +LogEntry_t::LogEntry_t(const uint8_t logLevel, + const String& message) : + _message(nullptr), + _timestamp(millis()), + _strLength(message.length()), + _isFlashString(false), + _logLevel(logLevel), + _subscriberPendingRead(0) { - if (line.length() == 0) { - return false; - } + if (_strLength && loglevelActiveFor(logLevel)) { - if (line.length() > LOG_STRUCT_MESSAGE_SIZE - 1) { - #ifdef USE_SECOND_HEAP + _message = special_calloc(1, _strLength + 1); - // Need to make a substring, which is a new allocation, on the 2nd heap - HeapSelectIram ephemeral; - #endif // ifdef USE_SECOND_HEAP - move_special(_message, line.substring(0, LOG_STRUCT_MESSAGE_SIZE - 1)); - } else { - move_special(_message, std::move(line)); + if (_message) { + memcpy_P(_message, message.begin(), _strLength); + } } - _loglevel = loglevel; - _timestamp = millis(); - return true; +} + +LogEntry_t::LogEntry_t(const uint8_t logLevel, + String && message) : + _message(nullptr), + _timestamp(millis()), + _strLength(message.length()), + _isFlashString(false), + _logLevel(logLevel), + _subscriberPendingRead(0) +{ + // We can't move the allocated memory from 'message'. + // Just use move so we make sure the memory is de-allocated after this call. + String str = std::move(message); + + if (_strLength && loglevelActiveFor(logLevel)) { + _message = special_calloc(1, _strLength + 1); + + if (_message) { + memcpy_P(_message, str.begin(), _strLength); + } + } +} + +LogEntry_t::~LogEntry_t() +{ + if (!_isFlashString && (_message != nullptr)) { + free(_message); + } +} + +LogEntry_t::LogEntry_t(LogEntry_t&& rhs) : + _message(rhs._message), + _timestamp(rhs._timestamp), + _flags(rhs._flags) +{ + rhs._message = nullptr; + rhs._timestamp = 0; + rhs._flags = 0; +} + +LogEntry_t& LogEntry_t::operator=(LogEntry_t&& rhs) +{ + _message = rhs._message; + _timestamp = rhs._timestamp; + _flags = rhs._flags; + + rhs._message = nullptr; + rhs._timestamp = 0; + rhs._flags = 0; + return *this; } void LogEntry_t::clear() { - free_string(_message); - _timestamp = 0; - _loglevel = 0; + if (!_isFlashString && (_message != nullptr)) { + free(_message); + } + _message = nullptr; + + // _strLength = 0; +} + +void LogEntry_t::setSubscribers() +{ + if (isValid()) { + for (uint32_t i = 0; i < NR_LOG_TO_DESTINATIONS; ++i) { + bitWrite(_subscriberPendingRead, i, + loglevelActiveFor(static_cast(i), _logLevel)); + } + } + + if (_subscriberPendingRead == 0) { + clear(); + } +} + +void LogEntry_t::updateSubscribers() +{ + if (isValid()) { + for (uint32_t i = 0; i < NR_LOG_TO_DESTINATIONS; ++i) { + if (bitRead(_subscriberPendingRead, i)) { + if (!loglevelActiveFor(static_cast(i), _logLevel)) { + bitClear(_subscriberPendingRead, i); + } + } + } + if (_subscriberPendingRead == 0) { + clear(); + } + } +} + +void LogEntry_t::markReadBySubscriber(uint8_t subscriber) +{ + if (subscriber < NR_LOG_TO_DESTINATIONS) { + bitClear(_subscriberPendingRead, subscriber); + } + + if (_subscriberPendingRead == 0) { + clear(); + } +} + +bool LogEntry_t::validForSubscriber(uint8_t subscriber) const +{ + return isValid() && (subscriber < NR_LOG_TO_DESTINATIONS) && bitRead(_subscriberPendingRead, subscriber); +} + +size_t LogEntry_t::print(Print& out, size_t offset, size_t length) const +{ + if (offset > _strLength) { return 0; } + + const char*begin = (const char *)_message; + const char*end = begin; + + if (_strLength > (offset + length)) { + end += (offset + length); + } + else { + end += _strLength; + } + const char*pos = begin + offset; + + for (; pos != end; ++pos) { + if ((*pos == '\0') || (out.write(*pos) == 0)) { return pos - begin - offset; } + } + return pos - begin - offset; +} + +String LogEntry_t::getMessage() const +{ + if (_message == nullptr) { return EMPTY_STRING; } + + if (_isFlashString) { + return String((const __FlashStringHelper *)_message); + } + return String((const char *)_message); } bool LogEntry_t::isExpired() const { - return timePassedSince(_timestamp) >= LOG_BUFFER_EXPIRE; + return + !isValid() || + timePassedSince(_timestamp) >= LOG_BUFFER_EXPIRE; +} -} \ No newline at end of file +bool LogEntry_t::isValid() const +{ + return _message && (_strLength != 0u); +} + +uint8_t LogEntry_t::getLogLevel() const +{ + return _logLevel; +} diff --git a/src/src/DataStructs/LogEntry.h b/src/src/DataStructs/LogEntry.h index 0fafa50af..15c81fd7c 100644 --- a/src/src/DataStructs/LogEntry.h +++ b/src/src/DataStructs/LogEntry.h @@ -1,27 +1,86 @@ -#ifndef DATASTRUCT_LOGENTRY_H -#define DATASTRUCT_LOGENTRY_H +#pragma once #include "../../ESPEasy_common.h" -class LogEntry_t { -public: - - LogEntry_t() = default; - - bool add(const uint8_t loglevel, - const String& line); - bool add(const uint8_t loglevel, - String && line); - - void clear(); - - bool isExpired() const; +#include "../DataTypes/LogLevels.h" - String _message; - unsigned long _timestamp{}; - uint8_t _loglevel{}; +#ifdef ESP32 + # define LOG_BUFFER_EXPIRE 30000 // Time after which a buffered log item is considered expired. +#else + # define LOG_BUFFER_EXPIRE 5000 // Time after which a buffered log item is considered expired. +#endif // ifdef ESP32 + + +struct LogEntry_t { + + LogEntry_t() = delete; + + LogEntry_t(const LogEntry_t& rhs) = delete; + + LogEntry_t(const uint8_t logLevel, + const __FlashStringHelper *message); + + LogEntry_t(const uint8_t logLevel, + const char *message); + + LogEntry_t(const uint8_t logLevel, + const String& message); + + LogEntry_t(const uint8_t logLevel, + String && message); + + ~LogEntry_t(); + + LogEntry_t(LogEntry_t&& rhs); + LogEntry_t& operator=(LogEntry_t&& rhs); + + void clear(); + + LogEntry_t& operator=(const LogEntry_t& rhs) = delete; + + operator bool() const { + return isValid(); + } + + void setSubscribers(); + + // This may clear pending reads when a log subscriber is no longer actively reading + void updateSubscribers(); + + void markReadBySubscriber(uint8_t subscriber); + + bool validForSubscriber(uint8_t subscriber) const; + + size_t print(Print& out, + size_t offset = 0, + size_t length = std::numeric_limits::max()) const; + + String getMessage() const; + + bool isExpired() const; + + bool isValid() const; + + uint8_t getLogLevel() const; + + uint32_t getTimestamp() const { return _timestamp; } + +private: + + void *_message{}; + uint32_t _timestamp; + union { + uint32_t _flags; + + struct { + uint32_t _strLength : 19; + uint32_t _isFlashString : 1; + uint32_t _logLevel : 4; + uint32_t _subscriberPendingRead : 8; // See NR_LOG_TO_DESTINATIONS + + }; + + }; + }; - - -#endif // ifndef DATASTRUCT_LOGENTRY_H diff --git a/src/src/DataStructs/LogStruct.cpp b/src/src/DataStructs/LogStruct.cpp deleted file mode 100644 index bea1740ce..000000000 --- a/src/src/DataStructs/LogStruct.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include "../DataStructs/LogStruct.h" - -#include "../Helpers/ESPEasy_time_calc.h" -#include "../Helpers/StringConverter.h" - -void LogStruct::add_end() { - if (isFull()) { - read_idx = nextIndex(read_idx); - } - write_idx = nextIndex(write_idx); - is_full = (write_idx == read_idx); -} - -void LogStruct::add(const uint8_t loglevel, const String& line) { - if (Message[write_idx].add(loglevel, line)) { - add_end(); - } -} - -void LogStruct::add(const uint8_t loglevel, String&& line) { - if (Message[write_idx].add(loglevel, std::move(line))) { - add_end(); - } -} - -bool LogStruct::getNext(bool& logLinesAvailable, unsigned long& timestamp, String& message, uint8_t& loglevel) { - lastReadTimeStamp = millis(); - logLinesAvailable = false; - - if (isEmpty()) { - return false; - } - timestamp = Message[read_idx]._timestamp; - message = std::move(Message[read_idx]._message); - loglevel = Message[read_idx]._loglevel; - clearOldest(); - - if (!isEmpty()) { - logLinesAvailable = true; - } - return true; -} - - -bool LogStruct::logActiveRead() { - clearExpiredEntries(); - return timePassedSince(lastReadTimeStamp) < LOG_BUFFER_ACTIVE_READ_TIMEOUT; -} - -void LogStruct::clearExpiredEntries() { - unsigned int maxLoops = LOG_STRUCT_MESSAGE_LINES; - - while (maxLoops > 0) { - --maxLoops; - - if (isEmpty() || // Nothing left - !Message[read_idx].isExpired()) - { - return; - } - clearOldest(); - } -} - -void LogStruct::clearOldest() { - if (!isEmpty()) { - is_full = false; - Message[read_idx].clear(); - read_idx = nextIndex(read_idx); - } -} diff --git a/src/src/DataStructs/LogStruct.h b/src/src/DataStructs/LogStruct.h deleted file mode 100644 index 4e3c64490..000000000 --- a/src/src/DataStructs/LogStruct.h +++ /dev/null @@ -1,75 +0,0 @@ -#ifndef DATASTRUCTS_LOGSTRUCT_H -#define DATASTRUCTS_LOGSTRUCT_H - - -#include "../../ESPEasy_common.h" - -#include "../DataStructs/LogEntry.h" - -/*********************************************************************************************\ - * LogStruct -\*********************************************************************************************/ -#ifdef ESP32 - #define LOG_STRUCT_MESSAGE_LINES 120 -#else - #ifdef USE_SECOND_HEAP - #define LOG_STRUCT_MESSAGE_LINES 60 - #else - #if defined(PLUGIN_BUILD_COLLECTION) || defined(PLUGIN_BUILD_DEV) - #define LOG_STRUCT_MESSAGE_LINES 10 - #else - #define LOG_STRUCT_MESSAGE_LINES 15 - #endif - #endif -#endif - -#ifdef ESP32 - #define LOG_BUFFER_ACTIVE_READ_TIMEOUT 30000 -#else - #define LOG_BUFFER_ACTIVE_READ_TIMEOUT 5000 -#endif - - -struct LogStruct { - - void add(const uint8_t loglevel, const String& line); - void add(const uint8_t loglevel, String&& line); - - // Read the next item and append it to the given string. - // Returns whether new lines are available. -// bool get(String& output, const String& lineEnd); - - // Returns whether a line was retrieved. - bool getNext(bool& logLinesAvailable, unsigned long& timestamp, String& message, uint8_t& loglevel); - - bool isEmpty() const { - return !is_full && (write_idx == read_idx); - } - - bool isFull() const { return is_full; } - - bool logActiveRead(); - - private: - - void add_end(); - - void clearExpiredEntries(); - - void clearOldest(); - - static int nextIndex(int idx) { -// return ((++idx) == LOG_STRUCT_MESSAGE_LINES) ? 0 : idx; - return (idx + 1) % LOG_STRUCT_MESSAGE_LINES; - } - - LogEntry_t Message[LOG_STRUCT_MESSAGE_LINES]; - int write_idx = 0; - int read_idx = 0; - unsigned long lastReadTimeStamp = 0; - bool is_full = false; -}; - - - -#endif // DATASTRUCTS_LOGSTRUCT_H \ No newline at end of file diff --git a/src/src/DataStructs/NodeStruct.cpp b/src/src/DataStructs/NodeStruct.cpp index f60632f61..f79f7f4d0 100644 --- a/src/src/DataStructs/NodeStruct.cpp +++ b/src/src/DataStructs/NodeStruct.cpp @@ -2,10 +2,14 @@ #if FEATURE_ESPEASY_P2P #include "../../ESPEasy-Globals.h" + #include "../DataTypes/NodeTypeID.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" + +#include "../../ESPEasy/net/ESPEasyNetwork.h" + #include "../Globals/SecuritySettings.h" #include "../Globals/Settings.h" + #include "../Helpers/ESPEasy_time_calc.h" #include "../Helpers/StringConverter.h" @@ -66,7 +70,7 @@ bool NodeStruct::validate(const IPAddress& remoteIP) { if (Settings.EnableIPv6() && hasIPv6_mac_based_link_global && remoteIP.type() == IPv6) { - const IPAddress this_global = NetworkGlobalIP6(); + const IPAddress this_global = ESPEasy::net::NetworkGlobalIP6(); // Check first 64 bit to see if we're in the same global scope for (int i = 0; i < 8 && hasIPv6_mac_based_link_global; ++i) { if (this_global[i] != remoteIP[i]) @@ -131,7 +135,7 @@ bool NodeStruct::operator<(const NodeStruct &other) const { } -const __FlashStringHelper * NodeStruct::getNodeTypeDisplayString() const { +String NodeStruct::getNodeTypeDisplayString() const { return toNodeTypeDisplayString(nodeType); } @@ -157,7 +161,7 @@ IPAddress NodeStruct::IPv6_link_local(bool stripZone) const if (Settings.EnableIPv6() && hasIPv6_mac_based_link_local) { // Base IPv6 on MAC address IPAddress ipv6; - if (IPv6_link_local_from_MAC(sta_mac, ipv6)) { + if (ESPEasy::net::IPv6_link_local_from_MAC(sta_mac, ipv6)) { if (stripZone) { return IPAddress(IPv6, &ipv6[0], 0); } @@ -172,7 +176,7 @@ IPAddress NodeStruct::IPv6_global() const if (Settings.EnableIPv6() && hasIPv6_mac_based_link_global) { // Base IPv6 on MAC address IPAddress ipv6; - if (IPv6_global_from_MAC(sta_mac, ipv6)) { + if (ESPEasy::net::IPv6_global_from_MAC(sta_mac, ipv6)) { return ipv6; } } @@ -199,7 +203,7 @@ MAC_address NodeStruct::ESPEasy_Now_MAC() const { return MAC_address(sta_mac); } -unsigned long NodeStruct::getAge() const { +uint32_t NodeStruct::getAge() const { return timePassedSince(lastUpdated); } @@ -314,8 +318,10 @@ bool NodeStruct::match(const MAC_address& mac) const bool NodeStruct::isThisNode() const { // Check to see if we process a node we've sent ourselves. - if (WifiSoftAPmacAddress() == ap_mac) return true; - if (WifiSTAmacAddress() == sta_mac) return true; + #if FEATURE_WIFI + if (ESPEasy::net::WifiSoftAPmacAddress() == ap_mac) return true; + #endif + if (ESPEasy::net::NetworkMacAddress() == sta_mac) return true; return false; } diff --git a/src/src/DataStructs/NodeStruct.h b/src/src/DataStructs/NodeStruct.h index 26025676b..2c0e1308f 100644 --- a/src/src/DataStructs/NodeStruct.h +++ b/src/src/DataStructs/NodeStruct.h @@ -5,7 +5,7 @@ #if FEATURE_ESPEASY_P2P #include "../Helpers/ESPEasy_time.h" -#include "../DataStructs/MAC_address.h" +#include "../../ESPEasy/net/DataStructs/MAC_address.h" #include #include @@ -29,7 +29,7 @@ struct __attribute__((__packed__)) NodeStruct // - lower load (TODO TD-er) bool operator<(const NodeStruct &other) const; - const __FlashStringHelper * getNodeTypeDisplayString() const; + String getNodeTypeDisplayString() const; String getNodeName() const; @@ -46,7 +46,7 @@ struct __attribute__((__packed__)) NodeStruct MAC_address ESPEasy_Now_MAC() const; - unsigned long getAge() const; + uint32_t getAge() const; bool isExpired() const; @@ -102,7 +102,7 @@ struct __attribute__((__packed__)) NodeStruct // When sending system info, this value contains the time since last time sync. // When kept as node info, this is the last time stamp the node info was updated. - unsigned long lastUpdated = (1 << 30); + uint32_t lastUpdated = (1 << 30); uint8_t version = 1; #if FEATURE_USE_IPV6 uint8_t hasIPv4 : 1; diff --git a/src/src/DataStructs/NodesHandler.cpp b/src/src/DataStructs/NodesHandler.cpp index d2e997db6..3914ad0f8 100644 --- a/src/src/DataStructs/NodesHandler.cpp +++ b/src/src/DataStructs/NodesHandler.cpp @@ -19,12 +19,12 @@ #endif #include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyWifi.h" +#include "../../ESPEasy/net/ESPEasyNetwork.h" + #include "../Globals/ESPEasy_time.h" -#include "../Globals/ESPEasyWiFiEvent.h" +#include "../../ESPEasy/net/Globals/ESPEasyWiFiEvent.h" #include "../Globals/MQTT.h" -#include "../Globals/NetworkState.h" +#include "../../ESPEasy/net/Globals/NetworkState.h" #include "../Globals/RTC.h" #include "../Globals/Settings.h" #include "../Helpers/ESPEasy_time_calc.h" @@ -33,6 +33,9 @@ #include "../Helpers/StringConverter.h" #include "../Helpers/StringGenerator_System.h" +#include "../../ESPEasy/net/wifi/ESPEasyWifi.h" + + #define ESPEASY_NOW_ALLOWED_AGE_NO_TRACEROUTE 35000 bool NodesHandler::addNode(const NodeStruct& node) @@ -152,7 +155,7 @@ bool NodesHandler::addNode(const NodeStruct& node, const ESPEasy_now_traceroute_ } ESPEasy_now_peermanager.addPeer(node.ESPEasy_Now_MAC(), node.channel); - +#ifndef BUILD_NO_DEBUG if (!node.isThisNode()) { if (traceRoute.getDistance() != 255) { if (loglevelActiveFor(LOG_LEVEL_INFO)) { @@ -168,6 +171,7 @@ bool NodesHandler::addNode(const NodeStruct& node, const ESPEasy_now_traceroute_ } } else {} } +#endif return isNewNode; } #endif @@ -269,7 +273,7 @@ const NodeStruct* NodesHandler::getPreferredNode_notMatching(uint8_t unit_nr) co } const NodeStruct * NodesHandler::getPreferredNode_notMatching(const MAC_address& not_matching) const { - MAC_address this_mac = NetworkMacAddress(); + MAC_address this_mac = ESPEasy::net::NetworkMacAddress(); const NodeStruct *thisNode = getNodeByMac(this_mac); const NodeStruct *reject = getNodeByMac(not_matching); @@ -391,19 +395,19 @@ void NodesHandler::updateThisNode() { // Set local data { - MAC_address mac = NetworkMacAddress(); + MAC_address mac = ESPEasy::net::NetworkMacAddress(); mac.get(thisNode.sta_mac); } WiFi.softAPmacAddress(thisNode.ap_mac); { - const bool addIP = NetworkConnected(); + const bool addIP = ESPEasy::net::NetworkConnected(); #ifdef USES_ESPEASY_NOW if (use_EspEasy_now) { thisNode.useAP_ESPEasyNow = 1; } #endif if (addIP) { - const IPAddress localIP = NetworkLocalIP(); + const IPAddress localIP = ESPEasy::net::NetworkLocalIP(); for (uint8_t i = 0; i < 4; ++i) { thisNode.ip[i] = localIP[i]; @@ -413,7 +417,7 @@ void NodesHandler::updateThisNode() { #ifdef USES_ESPEASY_NOW thisNode.channel = getESPEasyNOW_channel(); #else - thisNode.channel = WiFiEventData.usedChannel; + thisNode.channel = WiFi.channel(); //WiFiEventData.usedChannel; #endif if (thisNode.channel == 0) { thisNode.channel = WiFi.channel(); @@ -509,8 +513,8 @@ void NodesHandler::updateThisNode() { #if FEATURE_USE_IPV6 thisNode.hasIPv4 = thisNode.IP() != INADDR_NONE; - thisNode.hasIPv6_mac_based_link_local = is_IPv6_link_local_from_MAC(thisNode.sta_mac); - thisNode.hasIPv6_mac_based_link_global = is_IPv6_global_from_MAC(thisNode.sta_mac); + thisNode.hasIPv6_mac_based_link_local = ESPEasy::net::is_IPv6_link_local_from_MAC(thisNode.sta_mac); + thisNode.hasIPv6_mac_based_link_global = ESPEasy::net::is_IPv6_global_from_MAC(thisNode.sta_mac); #endif #ifdef USES_ESPEASY_NOW @@ -527,7 +531,7 @@ void NodesHandler::updateThisNode() { const NodeStruct * NodesHandler::getThisNode() { // node_time.now(); updateThisNode(); - MAC_address this_mac = NetworkMacAddress(); + MAC_address this_mac = ESPEasy::net::NetworkMacAddress(); return getNodeByMac(this_mac.mac); } @@ -614,7 +618,7 @@ bool NodesHandler::isEndpoint() const } #endif - if (!NetworkConnected()) return false; + if (!ESPEasy::net::NetworkConnected()) return false; return false; } @@ -622,14 +626,14 @@ bool NodesHandler::isEndpoint() const #ifdef USES_ESPEASY_NOW uint8_t NodesHandler::getESPEasyNOW_channel() const { - if (active_network_medium == NetworkMedium_t::WIFI && NetworkConnected()) { + if (active_network_medium == ESPEasy::net::NetworkMedium_t::WIFI && ESPEasy::net::NetworkConnected()) { return WiFi.channel(); } - if (Settings.ForceESPEasyNOWchannel > 0) { - return Settings.ForceESPEasyNOWchannel; + if (Settings.WiFiAP_channel > 0) { + return Settings.WiFiAP_channel; } if (isEndpoint()) { - if (active_network_medium == NetworkMedium_t::WIFI) { + if (active_network_medium == ESPEasy::net::NetworkMedium_t::WIFI) { return WiFi.channel(); } } @@ -639,7 +643,7 @@ uint8_t NodesHandler::getESPEasyNOW_channel() const return preferred->channel; } } - return WiFiEventData.usedChannel; + return WiFi.channel(); // WiFiEventData.usedChannel; } #endif diff --git a/src/src/DataStructs/NodesHandler.h b/src/src/DataStructs/NodesHandler.h index 79c16b188..6d6486528 100644 --- a/src/src/DataStructs/NodesHandler.h +++ b/src/src/DataStructs/NodesHandler.h @@ -4,7 +4,7 @@ #include "../../ESPEasy_common.h" #if FEATURE_ESPEASY_P2P -#include "../DataStructs/MAC_address.h" +#include "../../ESPEasy/net/DataStructs/MAC_address.h" #include "../DataStructs/NodeStruct.h" #include "../DataStructs/NTP_candidate.h" diff --git a/src/src/DataStructs/NotificationSettingsStruct.h b/src/src/DataStructs/NotificationSettingsStruct.h index 4684bbeaf..7dde6e843 100644 --- a/src/src/DataStructs/NotificationSettingsStruct.h +++ b/src/src/DataStructs/NotificationSettingsStruct.h @@ -5,7 +5,7 @@ #if FEATURE_NOTIFIER -#include // For std::shared_ptr +#include // For std::unique_ptr # define NPLUGIN_001_DEF_TM 8000 // Email Server Default Response Time, in mS. # define NPLUGIN_001_MIN_TM 5000 @@ -35,9 +35,9 @@ struct NotificationSettingsStruct //its safe to extend this struct, up to 4096 bytes, default values in config are 0 }; -typedef std::shared_ptr NotificationSettingsStruct_ptr_type; +DEF_UP(NotificationSettingsStruct); -#define MakeNotificationSettings(T) void * calloc_ptr = special_calloc(1,sizeof(NotificationSettingsStruct)); NotificationSettingsStruct_ptr_type T(new (calloc_ptr) NotificationSettingsStruct()); +#define MakeNotificationSettings(T) void * calloc_ptr = special_calloc(1,sizeof(NotificationSettingsStruct)); UP_NotificationSettingsStruct T(new (calloc_ptr) NotificationSettingsStruct()); // Check to see if MakeNotificationSettings was successful #define AllocatedNotificationSettings() (NotificationSettings.get() != nullptr) diff --git a/src/src/DataStructs/PluginStats.cpp b/src/src/DataStructs/PluginStats.cpp index c836af50a..7da578bda 100644 --- a/src/src/DataStructs/PluginStats.cpp +++ b/src/src/DataStructs/PluginStats.cpp @@ -141,6 +141,23 @@ size_t PluginStats::getNrSamples() const { return _samples->size(); } +size_t PluginStats::getNrUsableSamples() const +{ + const size_t nrSamples = getNrSamples(); + PluginStatsBuffer_t::index_t samplesUsed = 0; + + PluginStatsBuffer_t::index_t i = 0; + + for (; i < nrSamples; ++i) { + const float sample((*_samples)[i]); + + if (usableValue(sample)) { + ++samplesUsed; + } + } + return samplesUsed; +} + float PluginStats::getSampleAvg() const { return getSampleAvg(getNrSamples()); } @@ -369,21 +386,27 @@ bool PluginStats::plugin_get_config_value_base(struct EventStruct *event, String break; case 'm': - if (matchedCommand(command, F("min"), nrSamples)) { + if (equals(command, F("minp"))) { // [taskname#valuename.minp] Lowest value seen since value reset + value = getPeakLow(); + success = true; + } else if (matchedCommand(command, F("min"), nrSamples)) { success = nrSamples != 0; - if (nrSamples < 0) { // [taskname#valuename.min] Lowest value seen since value reset - value = getPeakLow(); + if (nrSamples < 0) { // [taskname#valuename.min] Lowest value of all recent samples + value = getSampleExtreme(getNrSamples(), false); } else { // Check for "minN", where N is the number of most recent samples to use. if (nrSamples > 0) { value = getSampleExtreme(nrSamples, false); } } + } else if (equals(command, F("maxp"))) { // [taskname#valuename.maxp] Highest value seen since value reset + value = getPeakHigh(); + success = true; } else if (matchedCommand(command, F("max"), nrSamples)) { success = nrSamples != 0; - if (nrSamples < 0) { // [taskname#valuename.max] Highest value seen since value reset - value = getPeakHigh(); + if (nrSamples < 0) { // [taskname#valuename.max] Highest value of all recent samples + value = getSampleExtreme(getNrSamples(), true); } else { // Check for "maxN", where N is the number of most recent samples to use. if (nrSamples > 0) { value = getSampleExtreme(nrSamples, true); @@ -441,6 +464,9 @@ bool PluginStats::plugin_get_config_value_base(struct EventStruct *event, String bool PluginStats::webformLoad_show_stats(struct EventStruct *event) const { + if (getNrUsableSamples() == 0) { + return false; + } bool somethingAdded = false; if (webformLoad_show_avg(event)) { somethingAdded = true; } @@ -566,21 +592,21 @@ void PluginStats::webformLoad_show_val( } # if FEATURE_CHART_JS -void PluginStats::plot_ChartJS_dataset() const + +void PluginStats::plot_ChartJS_dataset(KeyValueWriter& dataset) const { - add_ChartJS_dataset_header(_ChartJS_dataset_config); + auto data = add_ChartJS_dataset_header(dataset, _ChartJS_dataset_config); - PluginStatsBuffer_t::index_t i = 0; - const size_t nrSamples = getNrSamples(); + if (data) { + PluginStatsBuffer_t::index_t i = 0; + const size_t nrSamples = getNrSamples(); - for (; i < nrSamples; ++i) { - if (i != 0) { - addHtml(','); + for (; i < nrSamples; ++i) { + const float value = (*_samples)[i]; + + data->write({ EMPTY_STRING, value, _nrDecimals }); } - - addHtmlFloat_NaN_toNull((*_samples)[i], _nrDecimals); } - add_ChartJS_dataset_footer(); } # endif // if FEATURE_CHART_JS diff --git a/src/src/DataStructs/PluginStats.h b/src/src/DataStructs/PluginStats.h index 2a320eb80..c3ab83bb1 100644 --- a/src/src/DataStructs/PluginStats.h +++ b/src/src/DataStructs/PluginStats.h @@ -12,6 +12,7 @@ # if FEATURE_CHART_JS +# include "../Helpers/KeyValueWriter.h" # include "../WebServer/Chart_JS_title.h" # endif // if FEATURE_CHART_JS @@ -67,6 +68,8 @@ public: return _maxValue >= _minValue; } + uint8_t getNrDecimals() const { return _nrDecimals; } + // Set the peaks to unset values void resetPeaks(); @@ -74,6 +77,8 @@ public: size_t getNrSamples() const; + size_t getNrUsableSamples() const; + // Compute average over all stored values float getSampleAvg() const; @@ -157,7 +162,7 @@ public: } # if FEATURE_CHART_JS - void plot_ChartJS_dataset() const; + void plot_ChartJS_dataset(KeyValueWriter & parent) const; # endif // if FEATURE_CHART_JS # if FEATURE_CHART_JS diff --git a/src/src/DataStructs/PluginStats_array.cpp b/src/src/DataStructs/PluginStats_array.cpp index 14a7d4782..d7e4faae2 100644 --- a/src/src/DataStructs/PluginStats_array.cpp +++ b/src/src/DataStructs/PluginStats_array.cpp @@ -11,7 +11,7 @@ # include "../WebServer/Chart_JS.h" # if FEATURE_TASKVALUE_UNIT_OF_MEASURE -# include "../WebServer/Markup.h" +# include "../Helpers/ESPEasy_UnitOfMeasure.h" # endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE PluginStats_array::~PluginStats_array() @@ -29,7 +29,37 @@ PluginStats_array::~PluginStats_array() } } -void PluginStats_array::initPluginStats(taskIndex_t taskIndex, taskVarIndex_t taskVarIndex) +void PluginStats_array::initPluginStats(taskVarIndex_t taskVarIndex) +{ + if (taskVarIndex < VARS_PER_TASK) { + + String label; + + if (ExtraTaskSettings.enabledPluginStats(taskVarIndex)) { + label = ExtraTaskSettings.TaskDeviceValueNames[taskVarIndex]; +# if FEATURE_TASKVALUE_UNIT_OF_MEASURE + const uint8_t uomIndex = ExtraTaskSettings.getTaskVarUnitOfMeasure(taskVarIndex); + + if (uomIndex != 0) { + label = strformat(F("%s (%s)"), ExtraTaskSettings.TaskDeviceValueNames[taskVarIndex], toUnitOfMeasureName(uomIndex).c_str()); + } +# endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE + } + initPluginStats( + taskVarIndex, + label, + ExtraTaskSettings.TaskDeviceValueDecimals[taskVarIndex], + ExtraTaskSettings.TaskDeviceErrorValue[taskVarIndex], + ExtraTaskSettings.getPluginStatsConfig(taskVarIndex)); + } +} + +void PluginStats_array::initPluginStats( + taskVarIndex_t taskVarIndex, + const String & label, + uint8_t nrDecimals, + float errorValue, + const PluginStats_Config_t& displayConfig) { if (taskVarIndex < VARS_PER_TASK) { delete _plugin_stats[taskVarIndex]; @@ -42,35 +72,24 @@ void PluginStats_array::initPluginStats(taskIndex_t taskIndex, taskVarIndex_t ta } } - if (ExtraTaskSettings.enabledPluginStats(taskVarIndex)) { + if (label.length()) { // Try to allocate in PSRAM or 2nd heap if possible constexpr unsigned size = sizeof(PluginStats); void *ptr = special_calloc(1, size); if (ptr == nullptr) { _plugin_stats[taskVarIndex] = nullptr; } else { - _plugin_stats[taskVarIndex] = new (ptr) PluginStats( - ExtraTaskSettings.TaskDeviceValueDecimals[taskVarIndex], - ExtraTaskSettings.TaskDeviceErrorValue[taskVarIndex]); + _plugin_stats[taskVarIndex] = + new (ptr) PluginStats(nrDecimals, errorValue); } if (_plugin_stats[taskVarIndex] != nullptr) { - # if FEATURE_TASKVALUE_UNIT_OF_MEASURE - const uint8_t uomIndex = ExtraTaskSettings.getTaskVarUnitOfMeasure(taskVarIndex); - String label(ExtraTaskSettings.TaskDeviceValueNames[taskVarIndex]); - - if (uomIndex != 0) { - label = strformat(F("%s (%s)"), ExtraTaskSettings.TaskDeviceValueNames[taskVarIndex], toUnitOfMeasureName(uomIndex).c_str()); - } _plugin_stats[taskVarIndex]->setLabel(label); - # else // if FEATURE_TASKVALUE_UNIT_OF_MEASURE - _plugin_stats[taskVarIndex]->setLabel(ExtraTaskSettings.TaskDeviceValueNames[taskVarIndex]); - # endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE # if FEATURE_CHART_JS const __FlashStringHelper *colors[] = { F("#A52422"), F("#BEA57D"), F("#0F4C5C"), F("#A4BAB7") }; _plugin_stats[taskVarIndex]->_ChartJS_dataset_config.color = colors[taskVarIndex]; - _plugin_stats[taskVarIndex]->_ChartJS_dataset_config.displayConfig = ExtraTaskSettings.getPluginStatsConfig(taskVarIndex); + _plugin_stats[taskVarIndex]->_ChartJS_dataset_config.displayConfig = displayConfig; # endif // if FEATURE_CHART_JS if (_plugin_stats_timestamps != nullptr) { @@ -192,47 +211,60 @@ void PluginStats_array::pushPluginStatsValues( if (valueCount > 0) { const Sensor_VType sensorType = event->getSensorType(); - const int64_t timestamp_sysmicros = event->getTimestamp_as_systemMicros(); - - if (onlyUpdateTimestampWhenSame && (_plugin_stats_timestamps != nullptr)) { - // When only updating the timestamp of the last entry, - // we should look at the last 2 entries to see if they are the same. - bool isSame = true; - size_t i = 0; - - while (isSame && i < valueCount) { - if (_plugin_stats[i] != nullptr) { - const float value = UserVar.getAsDouble(event->TaskIndex, i, sensorType); - - if (!_plugin_stats[i]->matchesLastTwoEntries(value)) { - isSame = false; - } - } - ++i; - } - - if (isSame) { - _plugin_stats_timestamps->updateLast(timestamp_sysmicros); - return; - } - } - - if (_plugin_stats_timestamps != nullptr) { - _plugin_stats_timestamps->push(timestamp_sysmicros); - } + EventStruct tempEvent; for (size_t i = 0; i < valueCount; ++i) { if (_plugin_stats[i] != nullptr) { - const float value = UserVar.getAsDouble(event->TaskIndex, i, sensorType); - _plugin_stats[i]->push(value); - - if (trackPeaks) { - _plugin_stats[i]->trackPeak(value, timestamp_sysmicros); - } + tempEvent.ParfN[i] = UserVar.getAsDouble(event->TaskIndex, i, sensorType); } } + pushStatsValues(&tempEvent, valueCount, trackPeaks, onlyUpdateTimestampWhenSame); + } + } +} + +bool PluginStats_array::pushStatsValues(struct EventStruct *event, + size_t valueCount, + bool trackPeaks, + bool onlyUpdateTimestampWhenSame) +{ + const int64_t timestamp_sysmicros = event->getTimestamp_as_systemMicros(); + + if (onlyUpdateTimestampWhenSame && (_plugin_stats_timestamps != nullptr)) { + // When only updating the timestamp of the last entry, + // we should look at the last 2 entries to see if they are the same. + bool isSame = true; + size_t i = 0; + + while (isSame && i < valueCount) { + if (_plugin_stats[i] != nullptr) { + if (!_plugin_stats[i]->matchesLastTwoEntries(event->ParfN[i])) { + isSame = false; + } + } + ++i; + } + + if (isSame) { + _plugin_stats_timestamps->updateLast(timestamp_sysmicros); + return false; + } + } + + if (_plugin_stats_timestamps != nullptr) { + _plugin_stats_timestamps->push(timestamp_sysmicros); + } + + for (size_t i = 0; i < valueCount; ++i) { + if (_plugin_stats[i] != nullptr) { + _plugin_stats[i]->push(event->ParfN[i]); + + if (trackPeaks) { + _plugin_stats[i]->trackPeak(event->ParfN[i], timestamp_sysmicros); + } } } + return true; } bool PluginStats_array::plugin_get_config_value_base(struct EventStruct *event, @@ -323,6 +355,7 @@ bool PluginStats_array::webformLoad_show_stats(struct EventStruct *event, bool s } # if FEATURE_CHART_JS + void PluginStats_array::plot_ChartJS(bool onlyJSON) const { const size_t nrSamples = nrSamplesPresent(); @@ -330,84 +363,86 @@ void PluginStats_array::plot_ChartJS(bool onlyJSON) const if (nrSamples == 0) { return; } // Chart Header + + ChartJS_options_scales scales; { - ChartJS_options_scales scales; - { - ChartJS_options_scale scaleOption(F("x")); - - if (_plugin_stats_timestamps != nullptr) { - scaleOption.scaleType = F("time"); - } - scales.add(scaleOption); - } - - for (size_t i = 0; i < VARS_PER_TASK; ++i) { - if (_plugin_stats[i] != nullptr) { - ChartJS_options_scale scaleOption( - _plugin_stats[i]->_ChartJS_dataset_config.displayConfig, - _plugin_stats[i]->getLabel()); - scaleOption.axisTitle.color = _plugin_stats[i]->_ChartJS_dataset_config.color; - scales.add(scaleOption); - - _plugin_stats[i]->_ChartJS_dataset_config.axisID = scaleOption.axisID; - } - } - - scales.update_Yaxis_TickCount(); - - const bool enableZoom = true; - - add_ChartJS_chart_header( - F("line"), - F("TaskStatsChart"), - {}, - 500 + (70 * (scales.nr_Y_scales() - 1)), - 500, - scales.toString(), - enableZoom, - nrSamples, - onlyJSON); - } - - - // Add labels - addHtml(F("\"labels\":[")); - - for (size_t i = 0; i < nrSamples; ++i) { - if (i != 0) { - addHtml(','); - } + ChartJS_options_scale scaleOption(F("x")); if (_plugin_stats_timestamps != nullptr) { - struct tm ts; - uint32_t unix_time_frac{}; - const uint32_t unixtime_sec = node_time.systemMicros_to_Unixtime((*_plugin_stats_timestamps)[i], unix_time_frac); - const uint32_t local_timestamp = time_zone.toLocal(unixtime_sec); - breakTime(local_timestamp, ts); - addHtml('"'); - addHtml(formatDateTimeString(ts)); - addHtml(strformat(F(".%03u"), unix_time_frac_to_millis(unix_time_frac))); - addHtml('"'); - } else { - addHtmlInt(i); + scaleOption.scaleType = F("time"); } + scales.add(scaleOption); } - addHtml(F("],\n\"datasets\":[")); - - - // Data sets - bool first = true; for (size_t i = 0; i < VARS_PER_TASK; ++i) { if (_plugin_stats[i] != nullptr) { - if (!first) { - addHtml(','); + ChartJS_options_scale scaleOption( + _plugin_stats[i]->_ChartJS_dataset_config.displayConfig, + _plugin_stats[i]->getLabel()); + scaleOption.axisTitle.color = _plugin_stats[i]->_ChartJS_dataset_config.color; + scales.add(scaleOption); + + _plugin_stats[i]->_ChartJS_dataset_config.axisID = scaleOption.axisID; + } + } + + scales.update_Yaxis_TickCount(); + + const bool enableZoom = true; + { + auto chart = add_ChartJS_chart_header( + F("line"), + F("TaskStatsChart"), + {}, + scales, + enableZoom, + nrSamples, + onlyJSON); + + if (chart) { + auto data = chart->createChild(F("data")); + + if (data) { + { + auto labels = data->createChildArray(F("labels")); + + if (labels) { + for (size_t i = 0; i < nrSamples; ++i) { + if (_plugin_stats_timestamps != nullptr) { + struct tm ts; + uint32_t unix_time_frac{}; + const uint32_t unixtime_sec = node_time.systemMicros_to_Unixtime((*_plugin_stats_timestamps)[i], unix_time_frac); + const uint32_t local_timestamp = time_zone.toLocal(unixtime_sec); + breakTime(local_timestamp, ts); + labels->write({ + EMPTY_STRING, + formatDateTimeString(ts) + strformat(F(".%03u"), unix_time_frac_to_millis(unix_time_frac)) + }); + } else { + labels->write({ EMPTY_STRING, i }); + } + } + + } + } + { + auto datasets = data->createChildArray(F("datasets")); + + if (datasets) { + for (size_t i = 0; i < VARS_PER_TASK; ++i) { + if (_plugin_stats[i] != nullptr) { + auto dataset = datasets->createChild(); + + if (dataset) { + _plugin_stats[i]->plot_ChartJS_dataset(*dataset); + } + } + } + } + } } - first = false; - _plugin_stats[i]->plot_ChartJS_dataset(); } } - add_ChartJS_chart_footer(onlyJSON); } void PluginStats_array::plot_ChartJS_scatter( @@ -416,8 +451,6 @@ void PluginStats_array::plot_ChartJS_scatter( const __FlashStringHelper *id, const ChartJS_title & chartTitle, const ChartJS_dataset_config& datasetConfig, - int width, - int height, bool showAverage, const String & options, bool onlyJSON) const @@ -433,74 +466,107 @@ void PluginStats_array::plot_ChartJS_scatter( return; } - String axisOptions; - { ChartJS_options_scales scales; scales.add({ F("x"), stats_X->getLabel() }); scales.add({ F("y"), stats_Y->getLabel() }); - axisOptions = scales.toString(); - } - const size_t nrSamples = stats_X->getNrSamples(); - const bool enableZoom = false; + const size_t nrSamples = stats_X->getNrSamples(); + const bool enableZoom = false; - add_ChartJS_chart_header( - F("scatter"), - id, - chartTitle, - width, - height, - axisOptions, - enableZoom, - nrSamples, - onlyJSON); + auto chart = add_ChartJS_chart_header( + F("scatter"), + id, + chartTitle, + scales, + enableZoom, + nrSamples, + onlyJSON); - // Add labels, which will be shown in a tooltip when hovering with the mouse over a point. - addHtml(F("\"labels\":[")); + if (chart) { + chart->allowFormatOverrides(false); + auto data = chart->createChild(F("data")); - for (size_t i = 0; i < nrSamples; ++i) { - if (i != 0) { - addHtml(','); + if (data) { + { + // Add labels, which will be shown in a tooltip when hovering with the mouse over a point. + auto labels = data->createChildArray(F("labels")); + + if (labels) { + for (size_t i = 0; i < nrSamples; ++i) { + labels->write({ EMPTY_STRING, i }); + } + } + } + { + + auto datasets = data->createChildArray(F("datasets")); + + if (datasets) { + { + auto dataset = datasets->createChild(); + + if (dataset) { + dataset->write({ F("showLine"), true }); + + { + // Long/Lat Coordinates + auto data = add_ChartJS_dataset_header(*dataset, datasetConfig); + + if (data) { + + const uint8_t nrDecimalsX = stats_X->getNrDecimals(); + const uint8_t nrDecimalsY = stats_Y->getNrDecimals(); + + // Add scatter data + for (size_t i = 0; i < nrSamples; ++i) { + const float valX = (*stats_X)[i]; + const float valY = (*stats_Y)[i]; + add_ChartJS_scatter_data_point(*data, valX, valY, nrDecimalsX, nrDecimalsY); + } + } + } + } + } + + if (showAverage) { + auto dataset = datasets->createChild(); + + if (dataset) { + dataset->write({ F("pointRadius"), 6 }); + dataset->write({ F("pointHoverRadius"), 10 }); + + { + // Add single point showing the average + auto data = add_ChartJS_dataset_header( + *dataset, + { + F("Average"), + F("#0F4C5C") }); + + if (data) { + const float valX = stats_X->getSampleAvg(); + const float valY = stats_Y->getSampleAvg(); + add_ChartJS_scatter_data_point( + *data, + valX, + valY, + stats_X->getNrDecimals(), + stats_Y->getNrDecimals()); + } + } + } + } + } + } + } } - addHtmlInt(i); } - addHtml(F("],\n\"datasets\":[")); - - // Long/Lat Coordinates - add_ChartJS_dataset_header(datasetConfig); - - // Add scatter data - for (size_t i = 0; i < nrSamples; ++i) { - const float valX = (*stats_X)[i]; - const float valY = (*stats_Y)[i]; - add_ChartJS_scatter_data_point(valX, valY, 6); - } - - add_ChartJS_dataset_footer(F("\"showLine\":true")); - - if (showAverage) { - // Add single point showing the average - addHtml(','); - add_ChartJS_dataset_header( - { - F("Average"), - F("#0F4C5C") }); - - { - const float valX = stats_X->getSampleAvg(); - const float valY = stats_Y->getSampleAvg(); - add_ChartJS_scatter_data_point(valX, valY, 6); - } - add_ChartJS_dataset_footer(F("\"pointRadius\":6,\"pointHoverRadius\":10")); - } - add_ChartJS_chart_footer(onlyJSON); } # endif // if FEATURE_CHART_JS - PluginStats * PluginStats_array::getPluginStats(taskVarIndex_t taskVarIndex) const { if ((taskVarIndex < VARS_PER_TASK)) { diff --git a/src/src/DataStructs/PluginStats_array.h b/src/src/DataStructs/PluginStats_array.h index ec3e1df71..8b9d62521 100644 --- a/src/src/DataStructs/PluginStats_array.h +++ b/src/src/DataStructs/PluginStats_array.h @@ -16,15 +16,22 @@ # include "../WebServer/Chart_JS_title.h" # endif // if FEATURE_CHART_JS +struct PluginStats_Config_t; -class PluginStats_array { +class PluginStats_array +{ public: PluginStats_array() = default; ~PluginStats_array(); - void initPluginStats(taskIndex_t taskIndex, - taskVarIndex_t taskVarIndex); + void initPluginStats(taskVarIndex_t taskVarIndex); + void initPluginStats( + taskVarIndex_t taskVarIndex, + const String & label, + uint8_t nrDecimals, + float errorValue, + const PluginStats_Config_t& displayConfig); void clearPluginStats(taskVarIndex_t taskVarIndex); // Update any logged timestamp with this newly set system time. @@ -44,6 +51,12 @@ public: bool trackPeaks, bool onlyUpdateTimestampWhenSame); + bool pushStatsValues(struct EventStruct *event, + size_t valueCount, + bool trackPeaks, + bool onlyUpdateTimestampWhenSame); + + bool plugin_get_config_value_base(struct EventStruct *event, String & string) const; @@ -62,8 +75,6 @@ public: const __FlashStringHelper *id, const ChartJS_title & chartTitle, const ChartJS_dataset_config& datasetConfig, - int width, - int height, bool showAverage = true, const String & options = EMPTY_STRING, bool onlyJSON = false) const; @@ -80,7 +91,7 @@ private: PluginStats *_plugin_stats[VARS_PER_TASK] = {}; PluginStats_timestamp *_plugin_stats_timestamps = nullptr; -}; +}; // class PluginStats_array #endif // if FEATURE_PLUGIN_STATS #endif // ifndef HELPERS_PLUGINSTATS_ARRAY_H diff --git a/src/src/DataStructs/PluginStats_timestamp.cpp b/src/src/DataStructs/PluginStats_timestamp.cpp index f71f05873..73b21af66 100644 --- a/src/src/DataStructs/PluginStats_timestamp.cpp +++ b/src/src/DataStructs/PluginStats_timestamp.cpp @@ -104,17 +104,9 @@ uint32_t PluginStats_timestamp::systemMicros_to_internalTimestamp(const int64_t& int64_t PluginStats_timestamp::internalTimestamp_to_systemMicros(const uint32_t& internalTimestamp) const { - const uint64_t cur_micros = getMicros64(); - const uint64_t overflow_step = 4294967296ull * _internal_to_micros_ratio; - - uint64_t sysMicros = static_cast(internalTimestamp) * _internal_to_micros_ratio; - // Try to get in the range of the current system micros // This only does play a role in high res mode, when uptime is over 994 days. - while ((sysMicros + overflow_step) < cur_micros) { - sysMicros += overflow_step; - } - return sysMicros; + return node_time.internalTimestamp_to_systemMicros(internalTimestamp, _internal_to_micros_ratio); } #endif // if FEATURE_PLUGIN_STATS diff --git a/src/src/DataStructs/PluginTaskData_base.cpp b/src/src/DataStructs/PluginTaskData_base.cpp index 99e8346f4..f7aeb9dc3 100644 --- a/src/src/DataStructs/PluginTaskData_base.cpp +++ b/src/src/DataStructs/PluginTaskData_base.cpp @@ -56,7 +56,8 @@ size_t PluginTaskData_base::nrSamplesPresent() const { } #if FEATURE_PLUGIN_STATS -void PluginTaskData_base::initPluginStats(taskIndex_t taskIndex, taskVarIndex_t taskVarIndex) + +void PluginTaskData_base::initPluginStats(taskVarIndex_t taskVarIndex) { if (taskVarIndex < VARS_PER_TASK) { if (_plugin_stats_array == nullptr) { @@ -69,7 +70,7 @@ void PluginTaskData_base::initPluginStats(taskIndex_t taskIndex, taskVarIndex_t } if (_plugin_stats_array != nullptr) { - _plugin_stats_array->initPluginStats(taskIndex, taskVarIndex); + _plugin_stats_array->initPluginStats(taskVarIndex); } } } @@ -132,6 +133,7 @@ bool PluginTaskData_base::plugin_write_base(struct EventStruct *event, } #if FEATURE_PLUGIN_STATS + bool PluginTaskData_base::webformLoad_show_stats(struct EventStruct *event) const { if (_plugin_stats_array != nullptr) { @@ -141,6 +143,7 @@ bool PluginTaskData_base::webformLoad_show_stats(struct EventStruct *event) cons } # if FEATURE_CHART_JS + void PluginTaskData_base::plot_ChartJS(bool onlyJSON) const { if (_plugin_stats_array != nullptr) { @@ -154,8 +157,6 @@ void PluginTaskData_base::plot_ChartJS_scatter( const __FlashStringHelper *id, const ChartJS_title & chartTitle, const ChartJS_dataset_config& datasetConfig, - int width, - int height, bool showAverage, const String & options, bool onlyJSON) const @@ -167,8 +168,6 @@ void PluginTaskData_base::plot_ChartJS_scatter( id, chartTitle, datasetConfig, - width, - height, showAverage, options, onlyJSON); @@ -177,7 +176,6 @@ void PluginTaskData_base::plot_ChartJS_scatter( # endif // if FEATURE_CHART_JS - PluginStats * PluginTaskData_base::getPluginStats(taskVarIndex_t taskVarIndex) const { if (_plugin_stats_array != nullptr) { diff --git a/src/src/DataStructs/PluginTaskData_base.h b/src/src/DataStructs/PluginTaskData_base.h index f8fa2f287..7c1ed004d 100644 --- a/src/src/DataStructs/PluginTaskData_base.h +++ b/src/src/DataStructs/PluginTaskData_base.h @@ -32,7 +32,7 @@ struct PluginTaskData_base { size_t nrSamplesPresent() const; #if FEATURE_PLUGIN_STATS - void initPluginStats(taskIndex_t taskIndex, taskVarIndex_t taskVarIndex); + void initPluginStats(taskVarIndex_t taskVarIndex); void clearPluginStats(taskVarIndex_t taskVarIndex); // Update any logged timestamp with this newly set system time. @@ -64,8 +64,6 @@ struct PluginTaskData_base { const __FlashStringHelper *id, const ChartJS_title & chartTitle, const ChartJS_dataset_config& datasetConfig, - int width, - int height, bool showAverage = true, const String & options = EMPTY_STRING, bool onlyJSON = false) const; @@ -93,6 +91,7 @@ protected: protected: bool _baseClassOnly = false; + }; #endif // ifndef DATASTRUCTS_PLUGINTASKDATA_BASE_H diff --git a/src/src/DataStructs/ProvisioningStruct.cpp b/src/src/DataStructs/ProvisioningStruct.cpp index 839e84989..cec1cf381 100644 --- a/src/src/DataStructs/ProvisioningStruct.cpp +++ b/src/src/DataStructs/ProvisioningStruct.cpp @@ -43,6 +43,10 @@ bool ProvisioningStruct::fetchFileTypeAllowed(FileType::Enum filetype, unsigned case FileType::NOTIFICATION_DAT: return allowedFlags.allowFetchNotificationDat; case FileType::RULES_TXT: return (filenr < RULESETS_MAX) && bitRead(allowedFlags.allowFetchRules, filenr); case FileType::PROVISIONING_DAT: return allowedFlags.allowFetchProvisioningDat; +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + case FileType::DEV_SECURITY_DAT: return allowedFlags.allowFetchDeviceSecurityDat; +#endif + case FileType::MAX_FILETYPE: break; @@ -57,6 +61,9 @@ void ProvisioningStruct::setFetchFileTypeAllowed(FileType::Enum filetype, unsign case FileType::SECURITY_DAT: allowedFlags.allowFetchSecurityDat = checked; break; case FileType::NOTIFICATION_DAT: allowedFlags.allowFetchNotificationDat = checked; break; case FileType::PROVISIONING_DAT: allowedFlags.allowFetchProvisioningDat = checked; break; +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + case FileType::DEV_SECURITY_DAT: allowedFlags.allowFetchDeviceSecurityDat = checked; break; +#endif case FileType::RULES_TXT: if (filenr < RULESETS_MAX) { diff --git a/src/src/DataStructs/ProvisioningStruct.h b/src/src/DataStructs/ProvisioningStruct.h index 5d0589928..a8d105f59 100644 --- a/src/src/DataStructs/ProvisioningStruct.h +++ b/src/src/DataStructs/ProvisioningStruct.h @@ -8,7 +8,7 @@ # include "../DataStructs/FactoryDefaultPref.h" # include "../DataTypes/ESPEasyFileType.h" -# include // for std::shared_ptr +# include // for std::unique_ptr /*********************************************************************************************\ * ProvisioningStruct @@ -47,16 +47,17 @@ struct ProvisioningStruct uint16_t allowFetchNotificationDat :1; uint16_t allowFetchProvisioningDat :1; uint16_t allowFetchRules :4; + uint16_t allowFetchDeviceSecurityDat :1; - uint16_t unused :7; // Add to use full 16 bit. + uint16_t unused :6; // Add to use full 16 bit. } allowedFlags; uint16_t _allBits; }; }; -typedef std::shared_ptr ProvisioningStruct_ptr_type; +DEF_UP(ProvisioningStruct); -#define MakeProvisioningSettings(T) void * calloc_ptr = special_calloc(1,sizeof(ProvisioningStruct)); ProvisioningStruct_ptr_type T(new (calloc_ptr) ProvisioningStruct()); +#define MakeProvisioningSettings(T) void * calloc_ptr = special_calloc(1,sizeof(ProvisioningStruct)); UP_ProvisioningStruct T(new (calloc_ptr) ProvisioningStruct()); diff --git a/src/src/DataStructs/RTCStruct.h b/src/src/DataStructs/RTCStruct.h index a0bb75510..4d0757549 100644 --- a/src/src/DataStructs/RTCStruct.h +++ b/src/src/DataStructs/RTCStruct.h @@ -9,12 +9,12 @@ #define RTC_BASE_CACHE 124 #ifdef ESP8266 -# define RTC_CACHE_DATA_SIZE 240 // 10 elements, limited by RTC memory +# define RTC_CACHE_DATA_SIZE 240 // 10 elements, limited by RTC memory # ifdef ESP8266_16M14M # ifdef USE_LITTLEFS # define CACHE_FILE_MAX_SIZE 262144 // LittleFS can handle larger files just fine. # else // ifdef USE_LITTLEFS -# define CACHE_FILE_MAX_SIZE 48000 // Try to reduce the nr. of files, but SPIFFS still slows down on lager files. +# define CACHE_FILE_MAX_SIZE 48000 // Try to reduce the nr. of files, but SPIFFS still slows down on lager files. # endif // ifdef USE_LITTLEFS # else // ifdef ESP8266_16M14M # define CACHE_FILE_MAX_SIZE 24000 @@ -53,21 +53,22 @@ struct RTCStruct bool lastWiFi_set() const; - uint8_t ID1 = 0; - uint8_t ID2 = 0; - uint8_t lastWiFiChannel = 0; - uint8_t factoryResetCounter = 0; - uint8_t deepSleepState = 0; - uint8_t bootFailedCount = 0; - uint8_t flashDayCounter = 0; - uint8_t lastWiFiSettingsIndex = 0; - unsigned long flashCounter = 0; - unsigned long bootCounter = 0; - unsigned long lastMixedSchedulerId = 0; - uint8_t lastBSSID[6] = { 0 }; - uint8_t unused1 = 0; // Force alignment to 4 bytes - uint8_t unused2 = 0; - unsigned long lastSysTime = 0; + uint8_t ID1 = 0; + uint8_t ID2 = 0; + uint8_t lastWiFiChannel = 0; + uint8_t factoryResetCounter = 0; + uint8_t deepSleepState = 0; + uint8_t bootFailedCount = 0; + uint8_t flashDayCounter = 0; + uint8_t lastWiFiSettingsIndex = 0; + uint32_t flashCounter = 0; + uint32_t bootCounter = 0; + uint32_t lastMixedSchedulerId = 0; + uint8_t lastBSSID[6] = { 0 }; + uint8_t unused1 = 0; // Force alignment to 4 bytes + uint8_t unused2 = 0; + uint32_t lastSysTime = 0; + }; diff --git a/src/src/DataStructs/RTC_cache_handler_struct.cpp b/src/src/DataStructs/RTC_cache_handler_struct.cpp index 28027fc83..89fc1989e 100644 --- a/src/src/DataStructs/RTC_cache_handler_struct.cpp +++ b/src/src/DataStructs/RTC_cache_handler_struct.cpp @@ -65,8 +65,8 @@ void RTC_cache_handler_struct::resetpeek() { _peekreadpos = 0; } -bool RTC_cache_handler_struct::peekDataAvailable() const { - if (fp) { +bool RTC_cache_handler_struct::peekDataAvailable() { + if (openPeekFile()) { if ((_peekreadpos + 1) < fp.size()) { return true; } } if (_peekfilenr < RTC_cache.writeFileNr) { @@ -76,7 +76,7 @@ bool RTC_cache_handler_struct::peekDataAvailable() const { if (_peekfilenr == RTC_cache.writeFileNr) { if (fw) { constexpr size_t errorcode = (size_t)-1; - size_t pos = fp.position(); + size_t pos = fw.position(); if (pos == errorcode) { pos = 0; } @@ -90,7 +90,7 @@ bool RTC_cache_handler_struct::peekDataAvailable() const { int RTC_cache_handler_struct::getPeekFilePos(int& peekFileNr) { peekFileNr = _peekfilenr; constexpr size_t errorcode = (size_t)-1; - if (fp) { + if (openPeekFile()) { size_t pos = fp.position(); if (pos == errorcode) { _peekreadpos = 0; @@ -114,7 +114,7 @@ int RTC_cache_handler_struct::getPeekFileSize(int peekFileNr) const { void RTC_cache_handler_struct::setPeekFilePos(int newPeekFileNr, int newPeekReadPos) { validateFilePos(newPeekFileNr, newPeekReadPos); - if (fp) { + if (openPeekFile(newPeekFileNr)) { constexpr size_t errorcode = (size_t)-1; size_t pos = fp.position(); if (pos == errorcode) { @@ -138,16 +138,7 @@ void RTC_cache_handler_struct::setPeekFilePos(int newPeekFileNr, int newPeekRead } } - - if (!fp) { - String fname = createCacheFilename(newPeekFileNr); - - if (fname.isEmpty()) { return; } - - fp = tryOpenFile(fname, "r"); - } - - if (fp) { + if (openPeekFile(newPeekFileNr)) { _peekfilenr = newPeekFileNr; if (newPeekReadPos > 0) { @@ -258,8 +249,8 @@ bool RTC_cache_handler_struct::write(const uint8_t *data, unsigned int size) { // Mark all content as being processed and empty buffer. bool RTC_cache_handler_struct::flush() { - if (prepareFileForWrite()) { - if (RTC_cache.writePos > 0) { + if (RTC_cache.writePos > 0) { + if (prepareFileForWrite()) { #ifdef RTC_STRUCT_DEBUG size_t filesize = fw.size(); #endif // ifdef RTC_STRUCT_DEBUG @@ -375,6 +366,34 @@ void RTC_cache_handler_struct::closeOpenFiles() if (fp) { fp.close(); } + if (fw) { + fw.close(); + } +} + +bool RTC_cache_handler_struct::openPeekFile(int newPeekFileNr) +{ + if (fp) { + const String fname = createCacheFilename(newPeekFileNr); + if (!fname.endsWith(fp.name())) { + fp.close(); + } + } + if (!fp) { + int filepos = _peekreadpos; + validateFilePos(newPeekFileNr, filepos); + _peekfilenr = newPeekFileNr; + _peekreadpos = filepos; + const String fname = createCacheFilename(_peekfilenr); + if (fname.isEmpty()) { return false; } + fp = tryOpenFile(fname, "r"); + } + return !!fp; // cast to bool and force using operator::bool() +} + +bool RTC_cache_handler_struct::openPeekFile() +{ + return openPeekFile(_peekfilenr); } bool RTC_cache_handler_struct::deleteAllCacheBlocks() @@ -384,6 +403,7 @@ bool RTC_cache_handler_struct::deleteAllCacheBlocks() if (RTC_cache.readFileNr < RTC_cache.writeFileNr) { bool fileDeleted = false; int count = 0; + closeOpenFiles(); for (int fileNr = RTC_cache.readFileNr; count < 25 && fileNr < RTC_cache.writeFileNr; ++fileNr) { @@ -423,13 +443,7 @@ bool RTC_cache_handler_struct::deleteCacheBlock(int fileNr) writeError = false; // Make sure the read and peek file handles cannot be used on possibly deleted files. - if (fr) { - fr.close(); - } - - if (fp) { - fp.close(); - } + closeOpenFiles(); if (tryDeleteFile(fname)) { fileDeleted = true; @@ -650,17 +664,16 @@ bool RTC_cache_handler_struct::prepareFileForWrite() { } void RTC_cache_handler_struct::validateFilePos(int& fileNr, int& readPos) { - { - // Check to see if we try to set it to a no longer existing file - if (fileNr < RTC_cache.readFileNr) { - fileNr = RTC_cache.readFileNr; - readPos = 0; - } + // Check to see if we try to set it to a no longer existing file + if (fileNr < RTC_cache.readFileNr) { + fileNr = RTC_cache.readFileNr; + readPos = 0; } if (fileNr > RTC_cache.writeFileNr) { // We're trying to set it to a not yet existing file fileNr = RTC_cache.writeFileNr; + readPos = 0; } } diff --git a/src/src/DataStructs/RTC_cache_handler_struct.h b/src/src/DataStructs/RTC_cache_handler_struct.h index 07367bb40..a7f525417 100644 --- a/src/src/DataStructs/RTC_cache_handler_struct.h +++ b/src/src/DataStructs/RTC_cache_handler_struct.h @@ -42,7 +42,7 @@ struct RTC_cache_handler_struct void resetpeek(); - bool peekDataAvailable() const; + bool peekDataAvailable(); int getPeekFilePos(int& peekFileNr); @@ -77,6 +77,9 @@ struct RTC_cache_handler_struct private: + bool openPeekFile(int newPeekFileNr); + bool openPeekFile(); + bool loadMetaData(); bool loadData(); diff --git a/src/src/DataStructs/Scheduler_NWPluginTimerID.cpp b/src/src/DataStructs/Scheduler_NWPluginTimerID.cpp new file mode 100644 index 000000000..90522dda8 --- /dev/null +++ b/src/src/DataStructs/Scheduler_NWPluginTimerID.cpp @@ -0,0 +1,52 @@ +#include "../DataStructs/Scheduler_NWPluginTimerID.h" + +#include "../Globals/Plugins.h" +#include "../Helpers/Misc.h" + +NWPluginTimerID::NWPluginTimerID(ESPEasy::net::networkIndex_t networkIndex, + int Par1, + NWPlugin::Function function) : + SchedulerTimerID(SchedulerTimerType_e::NWPLUGIN_TIMER_IN_e) +{ + constexpr unsigned nrBitsNetworkIndex = NR_BITS(NETWORK_MAX); + constexpr unsigned mask_networkIndex = MASK_BITS(nrBitsNetworkIndex); + constexpr unsigned nrBitsPluginFunction = NrBitsNWPluginFunctions; + constexpr unsigned mask_function = MASK_BITS(nrBitsPluginFunction); + + if (validTaskIndex(networkIndex)) { + setId((networkIndex & mask_networkIndex) | + ((static_cast(function) & mask_function) << nrBitsNetworkIndex) | + (Par1 << (nrBitsNetworkIndex + nrBitsPluginFunction))); + } +} + +ESPEasy::net::networkIndex_t NWPluginTimerID::getNetworkIndex() const +{ + constexpr unsigned nrBitsNetworkIndex = NR_BITS(NETWORK_MAX); + constexpr unsigned mask_networkIndex = MASK_BITS(nrBitsNetworkIndex); + + return static_cast(getId() & mask_networkIndex); +} + +NWPlugin::Function NWPluginTimerID::getFunction() const +{ + constexpr unsigned nrBitsNetworkIndex = NR_BITS(NETWORK_MAX); + constexpr unsigned nrBitsPluginFunction = NrBitsNWPluginFunctions; + constexpr unsigned mask_function = MASK_BITS(nrBitsPluginFunction); + + return static_cast((getId() >> nrBitsNetworkIndex) & mask_function); +} + +#ifndef BUILD_NO_DEBUG + +String NWPluginTimerID::decode() const +{ + const ESPEasy::net::networkIndex_t networkIndex = getNetworkIndex(); + + if (validTaskIndex(networkIndex)) { + return getTaskDeviceName(networkIndex); + } + return String(getId()); +} + +#endif // ifndef BUILD_NO_DEBUG diff --git a/src/src/DataStructs/Scheduler_NWPluginTimerID.h b/src/src/DataStructs/Scheduler_NWPluginTimerID.h new file mode 100644 index 000000000..6f7dff998 --- /dev/null +++ b/src/src/DataStructs/Scheduler_NWPluginTimerID.h @@ -0,0 +1,27 @@ +#pragma once + +#include "../DataStructs/SchedulerTimerID.h" + +#include "../DataTypes/ESPEasy_plugin_functions.h" +#include "../../ESPEasy/net/DataTypes/NetworkIndex.h" + + +/*********************************************************************************************\ +* Plugin Task Timer (PLUGIN_TASKTIMER_IN) +* Can be scheduled per combo taskIndex & Par1 (20 least significant bits) +\*********************************************************************************************/ +struct NWPluginTimerID : SchedulerTimerID { + // taskIndex and par1 form a unique key that can be used to restart a timer + NWPluginTimerID(ESPEasy::net::networkIndex_t taskIndex, + int Par1, + NWPlugin::Function function = NWPlugin::Function::NWPLUGIN_TIMER_IN); + + ESPEasy::net::networkIndex_t getNetworkIndex() const; + + NWPlugin::Function getFunction() const; + +#ifndef BUILD_NO_DEBUG + String decode() const; +#endif // ifndef BUILD_NO_DEBUG + +}; diff --git a/src/src/DataStructs/Scheduler_SystemEventQueueTimerID.cpp b/src/src/DataStructs/Scheduler_SystemEventQueueTimerID.cpp index a241920c4..564022ad5 100644 --- a/src/src/DataStructs/Scheduler_SystemEventQueueTimerID.cpp +++ b/src/src/DataStructs/Scheduler_SystemEventQueueTimerID.cpp @@ -24,10 +24,9 @@ String SystemEventQueueTimerID::decode() const result += ','; if (ptr_type == SchedulerPluginPtrType_e::ControllerPlugin) { - result += getCPluginNameFromProtocolIndex(index); + result += getCPluginID_from_ControllerIndex(index); } else if (ptr_type == SchedulerPluginPtrType_e::TaskPlugin) { - const deviceIndex_t dev_index = deviceIndex_t::toDeviceIndex(index); - result += getPluginNameFromDeviceIndex(dev_index); + result += getPluginNameFromPluginID(getPluginID_from_TaskIndex(index)); } else { result += (index + 1); } diff --git a/src/src/DataStructs/SecurityStruct.cpp b/src/src/DataStructs/SecurityStruct.cpp index 42510b2ee..a789960a0 100644 --- a/src/src/DataStructs/SecurityStruct.cpp +++ b/src/src/DataStructs/SecurityStruct.cpp @@ -5,6 +5,9 @@ #include "../ESPEasyCore/ESPEasy_Log.h" #include "../Globals/CPlugins.h" +#include "../Globals/SecuritySettings.h" + + SecurityStruct::SecurityStruct() { ZERO_FILL(WifiSSID); ZERO_FILL(WifiKey); @@ -63,7 +66,7 @@ void SecurityStruct::clearWiFiCredentials() { ZERO_FILL(WifiKey); ZERO_FILL(WifiSSID2); ZERO_FILL(WifiKey2); - #ifndef BUILD_MINIMAL_OTA + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("WiFi : Clear WiFi credentials from settings")); #endif } @@ -78,7 +81,12 @@ void SecurityStruct::clearWiFiCredentials(SecurityStruct::WiFiCredentialsSlot sl } } +//TODO TD-er: Let SecurityStruct load/save/handle the device specific security settings + bool SecurityStruct::hasWiFiCredentials() const { +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + if (SecuritySettings_deviceSpecific.hasWiFiCredentials()) return true; +#endif return hasWiFiCredentials(SecurityStruct::WiFiCredentialsSlot::first) || hasWiFiCredentials(SecurityStruct::WiFiCredentialsSlot::second); } @@ -92,6 +100,17 @@ bool SecurityStruct::hasWiFiCredentials(SecurityStruct::WiFiCredentialsSlot slot return false; } +String SecurityStruct::getSSID(WiFiCredentialsSlot slot) const +{ + if (hasWiFiCredentials(slot)) { + if (slot == SecurityStruct::WiFiCredentialsSlot::first) + return WifiSSID; + if (slot == SecurityStruct::WiFiCredentialsSlot::second) + return WifiSSID2; + } + return EMPTY_STRING; +} + String SecurityStruct::getPassword() const { String res; const size_t passLength = strnlen(Password, sizeof(Password)); diff --git a/src/src/DataStructs/SecurityStruct.h b/src/src/DataStructs/SecurityStruct.h index 68bdb12c7..06d6126d7 100644 --- a/src/src/DataStructs/SecurityStruct.h +++ b/src/src/DataStructs/SecurityStruct.h @@ -40,6 +40,8 @@ struct SecurityStruct bool hasWiFiCredentials(WiFiCredentialsSlot slot) const; + String getSSID(WiFiCredentialsSlot slot) const; + String getPassword() const; char WifiSSID[32]; @@ -57,6 +59,7 @@ struct SecurityStruct //its safe to extend this struct, up to 4096 bytes, default values in config are 0. Make sure crc is last uint8_t ProgmemMd5[16] = {0}; // crc of the binary that last saved the struct to file. uint8_t md5[16] = {0}; + }; diff --git a/src/src/DataStructs/SecurityStruct_deviceSpecific.cpp b/src/src/DataStructs/SecurityStruct_deviceSpecific.cpp new file mode 100644 index 000000000..21fedf9e0 --- /dev/null +++ b/src/src/DataStructs/SecurityStruct_deviceSpecific.cpp @@ -0,0 +1,140 @@ +#include "../DataStructs/SecurityStruct_deviceSpecific.h" + +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + +int SecurityStruct_deviceSpecific::maxLength(KeyType keyType) +{ + switch (keyType) + { + case KeyType::Controller_User: return 128; + case KeyType::Controller_Password: return 128; + case KeyType::WiFi_SSID: return 31; + case KeyType::WiFi_Password: return 63; + case KeyType::Pin: return 4; + case KeyType::APN: return 64; + } + return 0; +} + +bool SecurityStruct_deviceSpecific::isPassword(KeyType keyType) +{ + switch (keyType) + { + case KeyType::Controller_Password: + case KeyType::WiFi_Password: + case KeyType::Pin: + return true; + default: break; + } + return false; +} + +const __FlashStringHelper * SecurityStruct_deviceSpecific::toString(KeyType keyType) +{ + switch (keyType) + { + case KeyType::Controller_User: return F("Controller User"); + case KeyType::Controller_Password: return F("Controller Password"); + case KeyType::WiFi_SSID: return F("SSID"); + case KeyType::WiFi_Password: return F("WPA Key"); + case KeyType::Pin: return F("Pin"); + case KeyType::APN: return F("APN"); + } + return F(""); +} + +bool SecurityStruct_deviceSpecific::load() +{ + return _kvs.load( + SettingsType::Enum::DeviceSpecificCredentials_type, + 0, + 0, + 0); +} + +bool SecurityStruct_deviceSpecific::save() +{ + return _kvs.store( + SettingsType::Enum::DeviceSpecificCredentials_type, + 0, + 0, + 0); +} + +void SecurityStruct_deviceSpecific::clear() { _kvs.clear(); } + +void SecurityStruct_deviceSpecific::clearWiFiCredentials() +{ + for (uint8_t i = 0; i < MAX_EXTRA_WIFI_CREDENTIALS_SEPARATE_FILE; ++i) + { + setWiFiCredentials(i, EMPTY_STRING, EMPTY_STRING); + } +} + +bool SecurityStruct_deviceSpecific::hasWiFiCredentials() const +{ + for (uint8_t i = 0; i < MAX_EXTRA_WIFI_CREDENTIALS_SEPARATE_FILE; ++i) + { + if (hasWiFiCredentials(i)) return true; + } + return false; +} + +bool SecurityStruct_deviceSpecific::hasWiFiCredentials(uint8_t index) const +{ + return + (index < MAX_EXTRA_WIFI_CREDENTIALS_SEPARATE_FILE) && + _kvs.hasKey(KVS_StorageType::Enum::string_type, + createKey(KeyType::WiFi_SSID, index)) && + _kvs.hasKey(KVS_StorageType::Enum::string_type, + createKey(KeyType::WiFi_Password, index)); +} + +/* + void SecurityStruct_deviceSpecific::freeUpFirstWiFiCredentials(uint8_t index) + { + + if (hasWiFiCredentials(index)) freeUpFirstWiFiCredentials(index + 1); + else { + + } + } + */ +bool SecurityStruct_deviceSpecific::getValue( + KeyType keytype, uint8_t index, String& value) const +{ + return _kvs.getValue(createKey(keytype, index), value); +} + +void SecurityStruct_deviceSpecific::setValue(KeyType keytype, uint8_t index, const String& value) +{ + _kvs.setValue(createKey(keytype, index), value); +} + +uint32_t SecurityStruct_deviceSpecific::createKey(KeyType keytype, uint16_t index) +{ + return (static_cast(keytype) & 0xFF) + (static_cast(index) << 8); +} + +bool SecurityStruct_deviceSpecific::getCredentials(KeyType keytype_key, + KeyType keytype_secret, + uint8_t index, + String& key, + String& secret) const +{ + return + _kvs.getValue(createKey(keytype_key, index), key) && + _kvs.getValue(createKey(keytype_secret, index), secret); +} + +void SecurityStruct_deviceSpecific::setCredentials(KeyType keytype_key, + KeyType keytype_secret, + uint8_t index, + const String& key, + const String& secret) +{ + _kvs.setValue(createKey(keytype_key, index), key); + _kvs.setValue(createKey(keytype_secret, index), secret); +} + +#endif // if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE diff --git a/src/src/DataStructs/SecurityStruct_deviceSpecific.h b/src/src/DataStructs/SecurityStruct_deviceSpecific.h new file mode 100644 index 000000000..2a4c05ec5 --- /dev/null +++ b/src/src/DataStructs/SecurityStruct_deviceSpecific.h @@ -0,0 +1,141 @@ +#pragma once + +#include "../../ESPEasy_common.h" + +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + + +# define MAX_EXTRA_WIFI_CREDENTIALS_SEPARATE_FILE 3 + +# include "../Helpers/_ESPEasy_key_value_store.h" + +class SecurityStruct_deviceSpecific +{ +public: + + // Indices used to create key in storage, so do not change values + enum class KeyType { + Controller_User = 0, + Controller_Password = 1, + + WiFi_SSID = 8, + WiFi_Password = 9, + + // Left some gap in numbers, so we can later add other WiFi specifics like for enterprise wifi + // Having a block, makes it easier later to implement a faster check using a bitmask + + + Pin = 32, + APN = 33 + + }; + + static int maxLength(KeyType keyType); + + static bool isPassword(KeyType keyType); + + static const __FlashStringHelper* toString(KeyType keyType); + + + SecurityStruct_deviceSpecific() = default; + + bool load(); + + bool save(); + + void clear(); + + void clearWiFiCredentials(); + + bool hasWiFiCredentials() const; + + bool hasWiFiCredentials(uint8_t index) const; + + // Move all set WiFi credentials to free up first entry + // Typically used for setup page. +// void freeUpFirstWiFiCredentials(uint8_t index = 0); + + bool getWiFiCredentials(uint8_t index, + String& ssid, + String& passwd) const + { + return + index < MAX_EXTRA_WIFI_CREDENTIALS_SEPARATE_FILE && + getCredentials(KeyType::WiFi_SSID, KeyType::WiFi_Password, index, ssid, passwd); + } + + void setWiFiCredentials( + uint8_t index, + const String& ssid, + const String& passwd) + { + if (index < MAX_EXTRA_WIFI_CREDENTIALS_SEPARATE_FILE) + { + setCredentials( + KeyType::WiFi_SSID, + KeyType::WiFi_Password, + index, + ssid, + passwd); + } + } + + bool getControllerCredentials(uint8_t index, + String& user, + String& passwd) const + { + return + (index < CONTROLLER_MAX) && getCredentials( + KeyType::Controller_User, + KeyType::Controller_Password, + index, + user, + passwd); + } + + void setControllerCredentials(uint8_t index, + const String& user, + const String& passwd) + { + if (index < CONTROLLER_MAX) { + setCredentials( + KeyType::Controller_User, + KeyType::Controller_Password, + index, + user, + passwd); + } + } + + bool getValue(KeyType keytype, + uint8_t index, + String& value) const; + void setValue(KeyType keytype, + uint8_t index, + const String& value); + + // Used for ESPEasy_key_value_store_webform + ESPEasy_key_value_store* _getKVS() { return &_kvs; } + + static uint32_t createKey(KeyType keytype, + uint16_t index); + +private: + + bool getCredentials(KeyType keytype_key, + KeyType keytype_secret, + uint8_t index, + String& key, + String& secret) const; + void setCredentials(KeyType keytype_key, + KeyType keytype_secret, + uint8_t index, + const String& key, + const String& secret); + + + ESPEasy_key_value_store _kvs; + +}; // class SecurityStruct_deviceSpecific + +#endif // if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE diff --git a/src/src/DataStructs/SettingsStruct.h b/src/src/DataStructs/SettingsStruct.h index c3507bf91..dc6ac7838 100644 --- a/src/src/DataStructs/SettingsStruct.h +++ b/src/src/DataStructs/SettingsStruct.h @@ -8,13 +8,20 @@ #include "../DataStructs/ChecksumType.h" #include "../DataStructs/DeviceStruct.h" #include "../DataTypes/ControllerIndex.h" -#include "../DataTypes/EthernetParameters.h" -#include "../DataTypes/NetworkMedium.h" +#include "../ESPEasy/net/DataTypes/NWPluginID.h" +#include "../ESPEasy/net/DataTypes/NetworkIndex.h" #include "../DataTypes/NPluginID.h" #include "../DataTypes/PluginID.h" //#include "../DataTypes/TaskEnabledState.h" #include "../DataTypes/TimeSource.h" #include "../Globals/Plugins.h" +#include "../ESPEasy/net/Globals/NWPlugins.h" + +#include "../../ESPEasy/net/DataTypes/NetworkMedium.h" + +#if FEATURE_ETHERNET +#include "../../ESPEasy/net/DataTypes/EthernetParameters.h" +#endif #ifdef ESP32 #include @@ -58,7 +65,7 @@ enum class PinBootState { /*********************************************************************************************\ * SettingsStruct \*********************************************************************************************/ -template +template class SettingsStruct_tmpl { public: @@ -66,158 +73,177 @@ class SettingsStruct_tmpl // SettingsStruct_tmpl() = default; // VariousBits1 defaults to 0, keep in mind when adding bit lookups. - bool appendUnitToHostname() const { return !VariousBits_1.appendUnitToHostname; } - void appendUnitToHostname(bool value) { VariousBits_1.appendUnitToHostname = !value;} + inline bool appendUnitToHostname() const { return !VariousBits_1.appendUnitToHostname; } + inline void appendUnitToHostname(bool value) { VariousBits_1.appendUnitToHostname = !value;} - bool uniqueMQTTclientIdReconnect_unused() const { return VariousBits_1.unused_02; } - void uniqueMQTTclientIdReconnect_unused(bool value) { VariousBits_1.unused_02 = value; } + inline bool uniqueMQTTclientIdReconnect_unused() const { return VariousBits_1.unused_02; } + inline void uniqueMQTTclientIdReconnect_unused(bool value) { VariousBits_1.unused_02 = value; } - bool OldRulesEngine() const { + inline bool OldRulesEngine() const { #ifdef WEBSERVER_NEW_RULES return !VariousBits_1.OldRulesEngine; #else return true; #endif } - void OldRulesEngine(bool value) { VariousBits_1.OldRulesEngine = !value; } + inline void OldRulesEngine(bool value) { VariousBits_1.OldRulesEngine = !value; } - bool ForceWiFi_bg_mode() const { return VariousBits_1.ForceWiFi_bg_mode; } - void ForceWiFi_bg_mode(bool value) { VariousBits_1.ForceWiFi_bg_mode = value; } + inline bool ForceWiFi_bg_mode() const { return VariousBits_1.ForceWiFi_bg_mode; } + inline void ForceWiFi_bg_mode(bool value) { VariousBits_1.ForceWiFi_bg_mode = value; } - bool WiFiRestart_connection_lost() const { return VariousBits_1.WiFiRestart_connection_lost; } - void WiFiRestart_connection_lost(bool value) { VariousBits_1.WiFiRestart_connection_lost = value; } + inline bool WiFiRestart_connection_lost() const { return VariousBits_1.WiFiRestart_connection_lost; } + inline void WiFiRestart_connection_lost(bool value) { VariousBits_1.WiFiRestart_connection_lost = value; } - bool EcoPowerMode() const { return VariousBits_1.EcoPowerMode; } - void EcoPowerMode(bool value) { VariousBits_1.EcoPowerMode = value; } + inline bool EcoPowerMode() const { return VariousBits_1.EcoPowerMode; } + inline void EcoPowerMode(bool value) { VariousBits_1.EcoPowerMode = value; } - bool WifiNoneSleep() const { return VariousBits_1.WifiNoneSleep; } - void WifiNoneSleep(bool value) { VariousBits_1.WifiNoneSleep = value; } + inline bool WifiNoneSleep() const { return VariousBits_1.WifiNoneSleep; } + inline void WifiNoneSleep(bool value) { VariousBits_1.WifiNoneSleep = value; } // Enable send gratuitous ARP by default, so invert the values (default = 0) - bool gratuitousARP() const { return !VariousBits_1.gratuitousARP; } - void gratuitousARP(bool value) { VariousBits_1.gratuitousARP = !value; } + inline bool gratuitousARP() const { return !VariousBits_1.gratuitousARP; } + inline void gratuitousARP(bool value) { VariousBits_1.gratuitousARP = !value; } // Be a bit more tolerant when parsing the last argument of a command. // See: https://github.com/letscontrolit/ESPEasy/issues/2724 - bool TolerantLastArgParse() const { return VariousBits_1.TolerantLastArgParse; } - void TolerantLastArgParse(bool value) { VariousBits_1.TolerantLastArgParse = value; } + inline bool TolerantLastArgParse() const { return VariousBits_1.TolerantLastArgParse; } + inline void TolerantLastArgParse(bool value) { VariousBits_1.TolerantLastArgParse = value; } // SendToHttp command does not wait for ack, with this flag it does wait. - bool SendToHttp_ack() const { return VariousBits_1.SendToHttp_ack; } - void SendToHttp_ack(bool value) { VariousBits_1.SendToHttp_ack = value; } + inline bool SendToHttp_ack() const { return VariousBits_1.SendToHttp_ack; } + inline void SendToHttp_ack(bool value) { VariousBits_1.SendToHttp_ack = value; } // Enable/disable ESPEasyNow protocol - bool UseESPEasyNow() const { + inline bool UseESPEasyNow() const { #ifdef USES_ESPEASY_NOW return VariousBits_1.UseESPEasyNow; #else return false; #endif } - void UseESPEasyNow(bool value) { + inline void UseESPEasyNow(bool value) { #ifdef USES_ESPEASY_NOW VariousBits_1.UseESPEasyNow = value; #endif } // Whether to try to connect to a hidden SSID network - bool IncludeHiddenSSID() const { return VariousBits_1.IncludeHiddenSSID; } - void IncludeHiddenSSID(bool value) { VariousBits_1.IncludeHiddenSSID = value; } + inline bool IncludeHiddenSSID() const { return VariousBits_1.IncludeHiddenSSID; } + inline void IncludeHiddenSSID(bool value) { VariousBits_1.IncludeHiddenSSID = value; } // When sending, the TX power may be boosted to max TX power. - bool UseMaxTXpowerForSending() const { return VariousBits_1.UseMaxTXpowerForSending; } - void UseMaxTXpowerForSending(bool value) { VariousBits_1.UseMaxTXpowerForSending = value; } + inline bool UseMaxTXpowerForSending() const { return VariousBits_1.UseMaxTXpowerForSending; } + inline void UseMaxTXpowerForSending(bool value) { VariousBits_1.UseMaxTXpowerForSending = value; } - // When set you can use the Sensor in AP-Mode without beeing forced to /setup - bool ApDontForceSetup() const { return VariousBits_1.ApDontForceSetup; } - void ApDontForceSetup(bool value) { VariousBits_1.ApDontForceSetup = value; } + // When set, user will be redirected to /setup or root page when connecting to this AP + inline bool ApCaptivePortal() const { return !VariousBits_1.ApCaptivePortal; } + inline void ApCaptivePortal(bool value) { VariousBits_1.ApCaptivePortal = !value; } // When outputting JSON bools use quoted values (on, backward compatible) or use official JSON true/false unquoted - bool JSONBoolWithoutQuotes() const { return VariousBits_1.JSONBoolWithoutQuotes; } - void JSONBoolWithoutQuotes(bool value) { VariousBits_1.JSONBoolWithoutQuotes = value; } + inline bool JSONBoolWithoutQuotes() const { return VariousBits_1.JSONBoolWithoutQuotes; } + inline void JSONBoolWithoutQuotes(bool value) { VariousBits_1.JSONBoolWithoutQuotes = value; } // Enable timing statistics (may consume a few kB of RAM) - bool EnableTimingStats() const { return VariousBits_1.EnableTimingStats; } - void EnableTimingStats(bool value) { VariousBits_1.EnableTimingStats = value; } + inline bool EnableTimingStats() const { return VariousBits_1.EnableTimingStats; } + inline void EnableTimingStats(bool value) { VariousBits_1.EnableTimingStats = value; } // Allow to actively reset I2C bus if it appears to be hanging. - bool EnableClearHangingI2Cbus() const { + inline bool EnableClearHangingI2Cbus() const { #if FEATURE_CLEAR_I2C_STUCK return VariousBits_1.EnableClearHangingI2Cbus; #else return false; #endif } - void EnableClearHangingI2Cbus(bool value) { VariousBits_1.EnableClearHangingI2Cbus = value; } + inline void EnableClearHangingI2Cbus(bool value) { VariousBits_1.EnableClearHangingI2Cbus = value; } // Enable RAM Tracking (may consume a few kB of RAM and cause some performance hit) - bool EnableRAMTracking() const { return VariousBits_1.EnableRAMTracking; } - void EnableRAMTracking(bool value) { VariousBits_1.EnableRAMTracking = value; } + inline bool EnableRAMTracking() const { return VariousBits_1.EnableRAMTracking; } + inline void EnableRAMTracking(bool value) { VariousBits_1.EnableRAMTracking = value; } // Enable caching of rules, to speed up rules processing - bool EnableRulesCaching() const { return !VariousBits_1.EnableRulesCaching; } - void EnableRulesCaching(bool value) { VariousBits_1.EnableRulesCaching = !value; } + inline bool EnableRulesCaching() const { return !VariousBits_1.EnableRulesCaching; } + inline void EnableRulesCaching(bool value) { VariousBits_1.EnableRulesCaching = !value; } // Allow the cached event entries to be sorted based on how frequent they occur. // This may speed up rules processing, especially on large rule sets with lots of rules blocks. - bool EnableRulesEventReorder() const { return !VariousBits_1.EnableRulesEventReorder; } - void EnableRulesEventReorder(bool value) { VariousBits_1.EnableRulesEventReorder = !value; } + inline bool EnableRulesEventReorder() const { return !VariousBits_1.EnableRulesEventReorder; } + inline void EnableRulesEventReorder(bool value) { VariousBits_1.EnableRulesEventReorder = !value; } // Allow OTA to use 'unlimited' bin sized files, possibly overwriting the file-system, and trashing files // Can be used if the configuration is later retrieved/restored manually - bool AllowOTAUnlimited() const { return VariousBits_1.AllowOTAUnlimited; } - void AllowOTAUnlimited(bool value) { VariousBits_1.AllowOTAUnlimited = value; } + inline bool AllowOTAUnlimited() const { return VariousBits_1.AllowOTAUnlimited; } + inline void AllowOTAUnlimited(bool value) { VariousBits_1.AllowOTAUnlimited = value; } // Default behavior is to not allow following redirects - bool SendToHTTP_follow_redirects() const { return VariousBits_1.SendToHTTP_follow_redirects; } - void SendToHTTP_follow_redirects(bool value) { VariousBits_1.SendToHTTP_follow_redirects = value; } + inline bool SendToHTTP_follow_redirects() const { return VariousBits_1.SendToHTTP_follow_redirects; } + inline void SendToHTTP_follow_redirects(bool value) { VariousBits_1.SendToHTTP_follow_redirects = value; } #if FEATURE_I2C_DEVICE_CHECK // Check if an I2C device is found at configured address at plugin_INIT and plugin_READ - bool CheckI2Cdevice() const { return !VariousBits_1.CheckI2Cdevice; } - void CheckI2Cdevice(bool value) { VariousBits_1.CheckI2Cdevice = !value; } + inline bool CheckI2Cdevice() const { return !VariousBits_1.CheckI2Cdevice; } + inline void CheckI2Cdevice(bool value) { VariousBits_1.CheckI2Cdevice = !value; } #endif // if FEATURE_I2C_DEVICE_CHECK // Wait for a second after calling WiFi.begin() // Especially useful for some FritzBox routers. - bool WaitWiFiConnect() const { return VariousBits_2.WaitWiFiConnect; } - void WaitWiFiConnect(bool value) { VariousBits_2.WaitWiFiConnect = value; } + inline bool WaitWiFiConnect() const { return VariousBits_2.WaitWiFiConnect; } + inline void WaitWiFiConnect(bool value) { VariousBits_2.WaitWiFiConnect = value; } #ifdef ESP32 // Toggle between passive/active WiFi scan. - bool PassiveWiFiScan() const { return !VariousBits_2.PassiveWiFiScan; } - void PassiveWiFiScan(bool value) { VariousBits_2.PassiveWiFiScan = !value; } + inline bool PassiveWiFiScan() const { return !VariousBits_2.PassiveWiFiScan; } + inline void PassiveWiFiScan(bool value) { VariousBits_2.PassiveWiFiScan = !value; } #endif // Connect to Hidden SSID using channel and BSSID // This is much slower, but appears to be needed for some access points // like MikroTik. - bool HiddenSSID_SlowConnectPerBSSID() const { return !VariousBits_2.HiddenSSID_SlowConnectPerBSSID; } - void HiddenSSID_SlowConnectPerBSSID(bool value) { VariousBits_2.HiddenSSID_SlowConnectPerBSSID = !value; } + inline bool HiddenSSID_SlowConnectPerBSSID() const { return !VariousBits_2.HiddenSSID_SlowConnectPerBSSID; } + inline void HiddenSSID_SlowConnectPerBSSID(bool value) { VariousBits_2.HiddenSSID_SlowConnectPerBSSID = !value; } - bool EnableIPv6() const { return !VariousBits_2.EnableIPv6; } - void EnableIPv6(bool value) { VariousBits_2.EnableIPv6 = !value; } + inline bool EnableIPv6() const { return !VariousBits_2.EnableIPv6; } + inline void EnableIPv6(bool value) { VariousBits_2.EnableIPv6 = !value; } // Use Espressif's auto reconnect. - bool SDK_WiFi_autoreconnect() const { return VariousBits_2.SDK_WiFi_autoreconnect; } - void SDK_WiFi_autoreconnect(bool value) { VariousBits_2.SDK_WiFi_autoreconnect = value; } + inline bool SDK_WiFi_autoreconnect() const { return VariousBits_2.SDK_WiFi_autoreconnect; } + inline void SDK_WiFi_autoreconnect(bool value) { VariousBits_2.SDK_WiFi_autoreconnect = value; } #if FEATURE_RULES_EASY_COLOR_CODE // Inhibit RulesCodeCompletion - bool DisableRulesCodeCompletion() const { return VariousBits_2.DisableRulesCodeCompletion; } - void DisableRulesCodeCompletion(bool value) { VariousBits_2.DisableRulesCodeCompletion = value; } + inline bool DisableRulesCodeCompletion() const { return VariousBits_2.DisableRulesCodeCompletion; } + inline void DisableRulesCodeCompletion(bool value) { VariousBits_2.DisableRulesCodeCompletion = value; } #endif // if FEATURE_RULES_EASY_COLOR_CODE #if FEATURE_TARSTREAM_SUPPORT - bool DisableSaveConfigAsTar() const { return VariousBits_2.DisableSaveConfigAsTar; } - void DisableSaveConfigAsTar(bool value) { VariousBits_2.DisableSaveConfigAsTar = value; } + inline bool DisableSaveConfigAsTar() const { return VariousBits_2.DisableSaveConfigAsTar; } + inline void DisableSaveConfigAsTar(bool value) { VariousBits_2.DisableSaveConfigAsTar = value; } #endif // if FEATURE_TARSTREAM_SUPPORT #if FEATURE_TASKVALUE_UNIT_OF_MEASURE - bool ShowUnitOfMeasureOnDevicesPage() const { return !VariousBits_2.ShowUnitOfMeasureOnDevicesPage; } - void ShowUnitOfMeasureOnDevicesPage(bool value) { VariousBits_2.ShowUnitOfMeasureOnDevicesPage = !value; } + inline bool ShowUnitOfMeasureOnDevicesPage() const { return !VariousBits_2.ShowUnitOfMeasureOnDevicesPage; } + inline void ShowUnitOfMeasureOnDevicesPage(bool value) { VariousBits_2.ShowUnitOfMeasureOnDevicesPage = !value; } #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE +#if CONFIG_SOC_WIFI_SUPPORT_5G + wifi_band_mode_t WiFi_band_mode() const { + if (VariousBits_2.WiFi_band_mode == 0) return WIFI_BAND_MODE_AUTO; + return static_cast(VariousBits_2.WiFi_band_mode); + } + inline void WiFi_band_mode(wifi_band_mode_t value) { VariousBits_2.WiFi_band_mode = value; } +#endif + +#ifdef ESP32 + inline bool WiFi_AP_enable_NAPT() const { return !VariousBits_2.WiFi_AP_enable_NAPT; } + inline void WiFi_AP_enable_NAPT(bool enable) { VariousBits_2.WiFi_AP_enable_NAPT = !enable; } +#endif + + + #if FEATURE_MQTT_CONNECT_BACKGROUND + inline bool MQTTConnectInBackground() const { return !VariousBits_2.MQTTConnectInBackground; } // Inverted! + inline void MQTTConnectInBackground(bool value) { VariousBits_2.MQTTConnectInBackground = !value; } + #endif // if FEATURE_MQTT_CONNECT_BACKGROUND + // Flag indicating whether all task values should be sent in a single event or one event per task value (default behavior) bool CombineTaskValues_SingleEvent(taskIndex_t taskIndex) const; void CombineTaskValues_SingleEvent(taskIndex_t taskIndex, bool value); @@ -231,14 +257,35 @@ class SettingsStruct_tmpl void SendDerivedTaskValues(taskIndex_t taskIndex, controllerIndex_t controllerIndex, bool value); #endif // if FEATURE_STRING_VARIABLES - bool DoNotStartAP() const { return VariousBits_1.DoNotStartAP; } - void DoNotStartAP(bool value) { VariousBits_1.DoNotStartAP = value; } + inline bool StartAPfallback_NoCredentials() const { return !VariousBits_2.StartAPfallback_NoCredentials; } + inline void StartAPfallback_NoCredentials(bool value) { VariousBits_2.StartAPfallback_NoCredentials = !value; } - bool UseAlternativeDeepSleep() const { return VariousBits_1.UseAlternativeDeepSleep; } - void UseAlternativeDeepSleep(bool value) { VariousBits_1.UseAlternativeDeepSleep = value; } + inline bool StartAP_on_NW002_init() const { return VariousBits_2.StartAP_on_NW002_init; } + inline void StartAP_on_NW002_init(bool value) { VariousBits_2.StartAP_on_NW002_init = value; } - bool UseLastWiFiFromRTC() const { return VariousBits_1.UseLastWiFiFromRTC; } - void UseLastWiFiFromRTC(bool value) { VariousBits_1.UseLastWiFiFromRTC = value; } + inline bool DoNotStartAPfallback_ConnectFail() const { return VariousBits_1.DoNotStartAPfallback_ConnectFail; } + inline void DoNotStartAPfallback_ConnectFail(bool value) { VariousBits_1.DoNotStartAPfallback_ConnectFail = value; } + + inline uint8_t APfallback_autostart_max_uptime_m() const { return VariousBits_2.APfallback_autostart_max_uptime_m; } + inline void APfallback_autostart_max_uptime_m(uint8_t count) { VariousBits_2.APfallback_autostart_max_uptime_m = count; } + + inline uint8_t APfallback_minimal_on_time_sec() const { + if (VariousBits_2.APfallback_minimal_on_time_sec == 0) { + return DEFAULT_AP_FALLBACK_MINIMAL_ON_TIME_SEC; + } + return VariousBits_2.APfallback_minimal_on_time_sec; + } + inline void APfallback_minimal_on_time_sec(uint8_t count) { + VariousBits_2.APfallback_minimal_on_time_sec = + (count == DEFAULT_AP_FALLBACK_MINIMAL_ON_TIME_SEC) + ? 0 : count; + } + + inline bool UseAlternativeDeepSleep() const { return VariousBits_1.UseAlternativeDeepSleep; } + inline void UseAlternativeDeepSleep(bool value) { VariousBits_1.UseAlternativeDeepSleep = value; } + + inline bool UseLastWiFiFromRTC() const { return VariousBits_1.UseLastWiFiFromRTC; } + inline void UseLastWiFiFromRTC(bool value) { VariousBits_1.UseLastWiFiFromRTC = value; } ExtTimeSource_e ExtTimeSource() const; void ExtTimeSource(ExtTimeSource_e value); @@ -246,12 +293,12 @@ class SettingsStruct_tmpl bool UseNTP() const; void UseNTP(bool value); - bool AllowTaskValueSetAllPlugins() const { return VariousBits_1.AllowTaskValueSetAllPlugins; } - void AllowTaskValueSetAllPlugins(bool value) { VariousBits_1.AllowTaskValueSetAllPlugins = value; } + inline bool AllowTaskValueSetAllPlugins() const { return VariousBits_1.AllowTaskValueSetAllPlugins; } + inline void AllowTaskValueSetAllPlugins(bool value) { VariousBits_1.AllowTaskValueSetAllPlugins = value; } #if FEATURE_AUTO_DARK_MODE - uint8_t getCssMode() const { return VariousBits_1.CssMode; } - void setCssMode(uint8_t value) { VariousBits_1.CssMode = value; } + inline uint8_t getCssMode() const { return VariousBits_1.CssMode; } + inline void setCssMode(uint8_t value) { VariousBits_1.CssMode = value; } #endif // FEATURE_AUTO_DARK_MODE bool isTaskEnableReadonly(taskIndex_t taskIndex) const; @@ -393,8 +440,35 @@ public: memcpy(&VariousBits_2, &value, sizeof(VariousBits_2)); } + bool getNetworkEnabled(ESPEasy::net::networkIndex_t index) const; - unsigned long PID = 0; + void setNetworkEnabled(ESPEasy::net::networkIndex_t index, bool enabled); + + bool getNetworkInterfaceSubnetBlockClientIP(ESPEasy::net::networkIndex_t index) const; + + void setNetworkInterfaceSubnetBlockClientIP(ESPEasy::net::networkIndex_t index, bool enabled); + +#if FEATURE_USE_IPV6 + bool getNetworkEnabled_IPv6(ESPEasy::net::networkIndex_t index) const; + + void setNetworkEnabled_IPv6(ESPEasy::net::networkIndex_t index, bool enabled); +#endif + + ESPEasy::net::nwpluginID_t getNWPluginID_for_network(ESPEasy::net::networkIndex_t index) const; + + void setNWPluginID_for_network(ESPEasy::net::networkIndex_t index, ESPEasy::net::nwpluginID_t id); + +#ifdef ESP32 + uint8_t getRoutePrio_for_network(ESPEasy::net::networkIndex_t index) const; + + void setRoutePrio_for_network(ESPEasy::net::networkIndex_t index, uint8_t prio); +#endif + + uint32_t getNetworkInterfaceStartupDelayAtBoot(ESPEasy::net::networkIndex_t index) const; + + void setNetworkInterfaceStartupDelayAtBoot(ESPEasy::net::networkIndex_t index, uint32_t delay_ms); + + uint32_t PID = 0; int Version = 0; int16_t Build = 0; uint8_t IP[4] = {0}; @@ -406,20 +480,20 @@ public: char Name[26] = {0}; char NTPHost[64] = {0}; // FIXME TD-er: Issue #2690 - unsigned long Delay = 0; // Sleep time in seconds + uint32_t Delay = 0; // Sleep time in seconds int8_t Pin_i2c_sda = DEFAULT_PIN_I2C_SDA; int8_t Pin_i2c_scl = DEFAULT_PIN_I2C_SCL; int8_t Pin_status_led = DEFAULT_PIN_STATUS_LED; int8_t Pin_sd_cs = -1; int8_t PinBootStates[17] = {0}; // Only use getPinBootState and setPinBootState as multiple pins are packed for ESP32 uint8_t Syslog_IP[4] = {0}; - unsigned int UDPPort = 8266; + uint32_t UDPPort = 8266; uint8_t SyslogLevel = 0; - uint8_t SerialLogLevel = 0; + uint8_t SerialLogLevel = 2; uint8_t WebLogLevel = 0; uint8_t SDLogLevel = 0; - unsigned long BaudRate = 115200; - unsigned long MessageDelay_unused = 0; // MQTT settings now moved to the controller settings. + uint32_t BaudRate = 115200; + uint32_t MessageDelay_unused = 0; // MQTT settings now moved to the controller settings. uint8_t deepSleep_wakeTime = 0; // 0 = Sleep Disabled, else time awake from sleep in seconds boolean CustomCSS = false; boolean DST = false; @@ -428,9 +502,46 @@ public: boolean UseSerial = false; boolean UseSSDP = false; uint8_t ExternalTimeSource = 0; - unsigned long WireClockStretchLimit = 0; - boolean GlobalSync = false; - unsigned long ConnectionFailuresThreshold = 0; + uint32_t WireClockStretchLimit = 0; + union { + struct { + uint32_t unused_00 : 1; // Bit 0 + uint32_t unused_01 : 1; // Bit 1 + uint32_t unused_02 : 1; // Bit 2 + uint32_t unused_03 : 1; // Bit 3 + uint32_t unused_04 : 1; // Bit 4 + uint32_t unused_05 : 1; // Bit 5 + uint32_t unused_06 : 1; // Bit 6 + uint32_t unused_07 : 1; // Bit 7 + uint32_t unused_08 : 1; // Bit 8 + uint32_t unused_09 : 1; // Bit 9 + uint32_t unused_10 : 1; // Bit 10 + uint32_t unused_11 : 1; // Bit 11 + uint32_t unused_12 : 1; // Bit 12 + uint32_t unused_13 : 1; // Bit 13 + uint32_t unused_14 : 1; // Bit 14 + uint32_t unused_15 : 1; // Bit 15 + uint32_t unused_16 : 1; // Bit 16 + uint32_t unused_17 : 1; // Bit 17 + uint32_t unused_18 : 1; // Bit 18 + uint32_t unused_19 : 1; // Bit 19 + uint32_t unused_20 : 1; // Bit 20 + uint32_t unused_21 : 1; // Bit 21 + uint32_t unused_22 : 1; // Bit 22 + uint32_t unused_23 : 1; // Bit 23 + uint32_t unused_24 : 1; // Bit 24 + uint32_t unused_25 : 1; // Bit 25 + uint32_t unused_26 : 1; // Bit 26 + uint32_t unused_27 : 1; // Bit 27 + uint32_t unused_28 : 1; // Bit 28 + uint32_t unused_29 : 1; // Bit 29 + uint32_t unused_30 : 1; // Bit 30 + uint32_t unused_31 : 1; // Bit 31 + }; + uint32_t _all_bits{}; + } VariousBits_3; //-V730 + + uint32_t ConnectionFailuresThreshold = 0; int16_t TimeZone = 0; boolean MQTTRetainFlag_unused = false; uint8_t InitSPI = 0; //0 = disabled, 1= enabled but for ESP32 there is option 2= SPI2 9 = User defined, see src/src/WebServer/HardwarePage.h enum SPI_Options_e @@ -454,7 +565,7 @@ public: int8_t I2C3_Multiplexer_Type = I2C_MULTIPLEXER_NONE; int8_t I2C3_Multiplexer_Addr = -1; int8_t I2C3_Multiplexer_ResetPin = -1; - unsigned int OLD_TaskDeviceID[N_TASKS - 7] = {0}; //UNUSED: this can be reused + uint32_t OLD_TaskDeviceID[N_TASKS - 7] = {0}; //UNUSED: this can be reused // FIXME TD-er: When used on ESP8266, this conversion union may not work // It might work as it is 32-bit in size. @@ -481,11 +592,11 @@ public: uint8_t TaskDeviceSendDataFlags[N_TASKS] = {0}; uint8_t VariousTaskBits[N_TASKS] = {0}; uint8_t TaskDeviceDataFeed[N_TASKS] = {0}; // When set to 0, only read local connected sensorsfeeds - unsigned long TaskDeviceTimer[N_TASKS] = {0}; + uint32_t TaskDeviceTimer[N_TASKS] = {0}; boolean TaskDeviceEnabled[N_TASKS] = {0}; boolean ControllerEnabled[CONTROLLER_MAX] = {0}; boolean NotificationEnabled[NOTIFICATION_MAX] = {0}; - unsigned int TaskDeviceID[CONTROLLER_MAX][N_TASKS]{}; // IDX number (mainly used by Domoticz) + uint32_t TaskDeviceID[CONTROLLER_MAX][N_TASKS]{}; // IDX number (mainly used by Domoticz) boolean TaskDeviceSendData[CONTROLLER_MAX][N_TASKS]{}; boolean Pin_status_led_Inversed = false; boolean deepSleepOnFail = false; @@ -521,10 +632,10 @@ public: uint32_t UseESPEasyNow : 1; // Bit 11 uint32_t IncludeHiddenSSID : 1; // Bit 12 uint32_t UseMaxTXpowerForSending : 1; // Bit 13 - uint32_t ApDontForceSetup : 1; // Bit 14 + uint32_t ApCaptivePortal : 1; // Bit 14 Inverted uint32_t unused_15 : 1; // Bit 15 was used by PeriodicalScanWiFi uint32_t JSONBoolWithoutQuotes : 1; // Bit 16 - uint32_t DoNotStartAP : 1; // Bit 17 + uint32_t DoNotStartAPfallback_ConnectFail : 1; // Bit 17 uint32_t UseAlternativeDeepSleep : 1; // Bit 18 uint32_t UseLastWiFiFromRTC : 1; // Bit 19 uint32_t EnableTimingStats : 1; // Bit 20 @@ -551,13 +662,22 @@ public: int8_t ETH_Pin_mdc_cs = -1; int8_t ETH_Pin_mdio_irq = -1; int8_t ETH_Pin_power_rst = -1; - EthPhyType_t ETH_Phy_Type = EthPhyType_t::notSet; - EthClockMode_t ETH_Clock_Mode = EthClockMode_t::Ext_crystal_osc; +#if FEATURE_ETHERNET + ESPEasy::net::EthPhyType_t ETH_Phy_Type = ESPEasy::net::EthPhyType_t::notSet; +# if CONFIG_ETH_USE_ESP32_EMAC && FEATURE_ETHERNET + ESPEasy::net::EthClockMode_t ETH_Clock_Mode = static_cast(0); +#else + uint8_t ETH_Clock_Mode{}; +#endif +#else + uint8_t ETH_Phy_Type{}; + uint8_t ETH_Clock_Mode{}; +#endif uint8_t ETH_IP[4] = {0}; uint8_t ETH_Gateway[4] = {0}; uint8_t ETH_Subnet[4] = {0}; uint8_t ETH_DNS[4] = {0}; - NetworkMedium_t NetworkMedium = NetworkMedium_t::WIFI; + ESPEasy::net::NetworkMedium_t NetworkMedium = ESPEasy::net::NetworkMedium_t::WIFI; int8_t I2C_Multiplexer_Type = I2C_MULTIPLEXER_NONE; int8_t I2C_Multiplexer_Addr = -1; int8_t I2C_Multiplexer_Channel[N_TASKS]{}; @@ -569,52 +689,40 @@ public: int8_t PinBootStates_ESP32[24] = {0}; // pins 17 ... 39 #endif uint8_t WiFi_TX_power = 70; // 70 = 17.5dBm. unit: 0.25 dBm - int8_t WiFi_sensitivity_margin = 3; // Margin in dBm on top of sensitivity. - uint8_t NumberExtraWiFiScans = 0; + int8_t WiFi_sensitivity_margin = 5; // Margin in dBm on top of sensitivity. + uint8_t ConnectFailRetryCount = 0; int8_t SPI_SCLK_pin = -1; int8_t SPI_MISO_pin = -1; int8_t SPI_MOSI_pin = -1; - int8_t ForceESPEasyNOWchannel = 0; + uint8_t WiFiAP_channel = 0; // Do not rename or move this checksum. // Checksum calculation will work "around" this uint8_t md5[16]{}; // Store checksum of the settings. // VariousBits_2 defaults to 0, keep in mind when adding bit lookups. - struct { - uint32_t WaitWiFiConnect : 1; // Bit 00 - uint32_t SDK_WiFi_autoreconnect : 1; // Bit 01 - uint32_t DisableRulesCodeCompletion : 1; // Bit 02 - uint32_t HiddenSSID_SlowConnectPerBSSID : 1; // Bit 03 // inverted - uint32_t EnableIPv6 : 1; // Bit 04 // inverted - uint32_t DisableSaveConfigAsTar : 1; // Bit 05 - uint32_t PassiveWiFiScan : 1; // Bit 06 // inverted - uint32_t ShowUnitOfMeasureOnDevicesPage : 1; // Bit 07 // inverted - uint32_t unused_08 : 1; // Bit 08 - uint32_t unused_09 : 1; // Bit 09 - uint32_t unused_10 : 1; // Bit 10 - uint32_t unused_11 : 1; // Bit 11 - uint32_t unused_12 : 1; // Bit 12 - uint32_t unused_13 : 1; // Bit 13 - uint32_t unused_14 : 1; // Bit 14 - uint32_t unused_15 : 1; // Bit 15 - uint32_t unused_16 : 1; // Bit 16 - uint32_t unused_17 : 1; // Bit 17 - uint32_t unused_18 : 1; // Bit 18 - uint32_t unused_19 : 1; // Bit 19 - uint32_t unused_20 : 1; // Bit 20 - uint32_t unused_21 : 1; // Bit 21 - uint32_t unused_22 : 1; // Bit 22 - uint32_t unused_23 : 1; // Bit 23 - uint32_t unused_24 : 1; // Bit 24 - uint32_t unused_25 : 1; // Bit 25 - uint32_t unused_26 : 1; // Bit 26 - uint32_t unused_27 : 1; // Bit 27 - uint32_t unused_28 : 1; // Bit 28 - uint32_t unused_29 : 1; // Bit 29 - uint32_t unused_30 : 1; // Bit 30 - uint32_t unused_31 : 1; // Bit 31 + union { + struct { + uint32_t WaitWiFiConnect : 1; // Bit 00 + uint32_t SDK_WiFi_autoreconnect : 1; // Bit 01 + uint32_t DisableRulesCodeCompletion : 1; // Bit 02 + uint32_t HiddenSSID_SlowConnectPerBSSID : 1; // Bit 03 // inverted + uint32_t EnableIPv6 : 1; // Bit 04 // inverted + uint32_t DisableSaveConfigAsTar : 1; // Bit 05 + uint32_t PassiveWiFiScan : 1; // Bit 06 // inverted + uint32_t ShowUnitOfMeasureOnDevicesPage : 1; // Bit 07 // inverted + uint32_t WiFi_band_mode : 2; // Bit 08 & 09 + uint32_t WiFi_AP_enable_NAPT : 1; // Bit 10 // inverted + uint32_t RestoreUserVarsFromEEPROMOnColdBoot : 1; // Bit 11 + uint32_t RestoreUserVarsFromEEPROMOnWarmBoot : 1; // Bit 12 + uint32_t MQTTConnectInBackground : 1; // Bit 13 // inverted + uint32_t StartAPfallback_NoCredentials : 1; // Bit 14 // inverted + uint32_t StartAP_on_NW002_init : 1; // Bit 15 + uint32_t APfallback_minimal_on_time_sec : 8; // Bit 16 - 23 + uint32_t APfallback_autostart_max_uptime_m : 8; // Bit 23 - 31 '0' == disabled + }; + uint32_t _all_bits{}; } VariousBits_2; //-V730 uint8_t console_serial_port = DEFAULT_CONSOLE_PORT; @@ -622,7 +730,25 @@ public: int8_t console_serial_txpin = DEFAULT_CONSOLE_PORT_TXPIN; uint8_t console_serial0_fallback = DEFAULT_CONSOLE_SER0_FALLBACK; + + // ******************************************************************************** + // NWPlugin (Network) settings + // ******************************************************************************** + // FIXME TD-er: Must change to nwpluginID_t, but then also another check must be added since changing the pluginID_t will also render settings incompatible + uint8_t NWPluginID[NETWORK_MAX] = {0}; + uint8_t NetworkEnabled_bits{}; + uint8_t NetworkInterfaceSubnetBlockClientIP_bits{}; // Client IP Block Level. Allow from subnet of this interface + uint8_t NetworkEnabled_ipv6_bits{}; // Whether or not to use IPv6 for the given interface (Settings.EnableIPv6() is the global on/off for IPv6) + uint8_t NetworkUnused_3{}; +#ifdef ESP32 + uint8_t NetworkRoutePrio[NETWORK_MAX] = {0}; +#endif + // TODO TD-er: For ESP8266 we may likely ever use upto 2 or 3 network interfaces, so maybe re-use the rest later? + uint16_t NetworkInterfaceStartupDelayAtBoot[NETWORK_MAX]{}; + + // Try to extend settings to make the checksum 4-uint8_t aligned. + }; /* diff --git a/src/src/DataStructs/SystemTimerStruct.cpp b/src/src/DataStructs/SystemTimerStruct.cpp index 4b5555b4d..db009158b 100644 --- a/src/src/DataStructs/SystemTimerStruct.cpp +++ b/src/src/DataStructs/SystemTimerStruct.cpp @@ -19,7 +19,7 @@ systemTimerStruct::systemTimerStruct(int recurringCount, unsigned long msecFromN // Create a new timer which should be "scheduled" now to clear up any data _recurringCount = 0; // Do not reschedule _loopCount = 0; // Do not execute - #ifndef BUILD_MINIMAL_OTA + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("TIMER: disable timer")); #endif } @@ -35,7 +35,7 @@ systemTimerStruct::systemTimerStruct(int recurringCount, unsigned long msecFromN } struct EventStruct systemTimerStruct::toEvent() const { - struct EventStruct TempEvent(TaskIndex); + struct EventStruct TempEvent(static_cast(_index)); TempEvent.Par1 = _recurringCount; TempEvent.Par2 = _interval; @@ -45,14 +45,28 @@ struct EventStruct systemTimerStruct::toEvent() const { return TempEvent; } -void systemTimerStruct::fromEvent(taskIndex_t taskIndex, +struct EventStruct systemTimerStruct::toNetworkEvent() const { + struct EventStruct TempEvent; + + TempEvent.NetworkIndex = _index; + + TempEvent.Par1 = _recurringCount; + TempEvent.Par2 = _interval; + TempEvent.Par3 = _timerIndex; + TempEvent.Par4 = _remainder; + TempEvent.Par5 = _loopCount; + return TempEvent; + +} + +void systemTimerStruct::fromEvent(int Index, int Par1, int Par2, int Par3, int Par4, int Par5) { - TaskIndex = taskIndex; + _index = Index; _recurringCount = Par1; _interval = Par2; _timerIndex = Par3; diff --git a/src/src/DataStructs/SystemTimerStruct.h b/src/src/DataStructs/SystemTimerStruct.h index 06a10750c..1265f1df4 100644 --- a/src/src/DataStructs/SystemTimerStruct.h +++ b/src/src/DataStructs/SystemTimerStruct.h @@ -20,7 +20,7 @@ private: int _remainder = 0; int _loopCount = 0; int _alternateInterval = 0; - taskIndex_t TaskIndex = INVALID_TASK_INDEX; + int _index = -1; bool _alternateState = false; public: @@ -35,7 +35,9 @@ public: struct EventStruct toEvent() const; - void fromEvent(taskIndex_t TaskIndex, + struct EventStruct toNetworkEvent() const; + + void fromEvent(int Index, int Par1, int Par2, int Par3, diff --git a/src/src/DataStructs/TimingStats.cpp b/src/src/DataStructs/TimingStats.cpp index d74f1bd24..22e323278 100644 --- a/src/src/DataStructs/TimingStats.cpp +++ b/src/src/DataStructs/TimingStats.cpp @@ -9,6 +9,7 @@ std::map pluginStats; std::map controllerStats; +std::map networkStats; std::map miscStats; unsigned long timingstats_last_reset(0); @@ -154,7 +155,6 @@ const __FlashStringHelper* getCPluginCFunctionName(CPlugin::Function function) { case CPlugin::Function::CPLUGIN_FLUSH: return F("CPLUGIN_FLUSH"); case CPlugin::Function::CPLUGIN_TEN_PER_SECOND: return F("CPLUGIN_TEN_PER_SECOND"); case CPlugin::Function::CPLUGIN_FIFTY_PER_SECOND: return F("CPLUGIN_FIFTY_PER_SECOND"); - case CPlugin::Function::CPLUGIN_INIT_ALL: return F("CPLUGIN_INIT_ALL"); case CPlugin::Function::CPLUGIN_EXIT: return F("CPLUGIN_EXIT"); case CPlugin::Function::CPLUGIN_WRITE: return F("CPLUGIN_WRITE"); @@ -163,6 +163,9 @@ const __FlashStringHelper* getCPluginCFunctionName(CPlugin::Function function) { case CPlugin::Function::CPLUGIN_INTERVAL: case CPlugin::Function::CPLUGIN_ACKNOWLEDGE: case CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG: + case CPlugin::Function::CPLUGIN_INIT_ALL: + case CPlugin::Function::CPLUGIN_EXIT_ALL: + break; } return F("Unknown"); @@ -181,14 +184,12 @@ bool mustLogCFunction(CPlugin::Function function) { case CPlugin::Function::CPLUGIN_GET_DEVICENAME: return false; case CPlugin::Function::CPLUGIN_WEBFORM_SAVE: return false; case CPlugin::Function::CPLUGIN_WEBFORM_LOAD: return false; - case CPlugin::Function::CPLUGIN_GET_PROTOCOL_DISPLAY_NAME: return false; case CPlugin::Function::CPLUGIN_TASK_CHANGE_NOTIFICATION: return false; case CPlugin::Function::CPLUGIN_INIT: return false; case CPlugin::Function::CPLUGIN_UDP_IN: return true; case CPlugin::Function::CPLUGIN_FLUSH: return false; case CPlugin::Function::CPLUGIN_TEN_PER_SECOND: return true; case CPlugin::Function::CPLUGIN_FIFTY_PER_SECOND: return true; - case CPlugin::Function::CPLUGIN_INIT_ALL: return false; case CPlugin::Function::CPLUGIN_EXIT: return false; case CPlugin::Function::CPLUGIN_WRITE: return true; @@ -197,11 +198,22 @@ bool mustLogCFunction(CPlugin::Function function) { case CPlugin::Function::CPLUGIN_INTERVAL: case CPlugin::Function::CPLUGIN_ACKNOWLEDGE: case CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG: + case CPlugin::Function::CPLUGIN_GET_PROTOCOL_DISPLAY_NAME: + case CPlugin::Function::CPLUGIN_INIT_ALL: + case CPlugin::Function::CPLUGIN_EXIT_ALL: break; } return false; } +bool mustLogNWFunction(NWPlugin::Function function) +{ + if (!Settings.EnableTimingStats()) { return false; } + + return true; +} + + // Return flash string type to reduce bin size const __FlashStringHelper* getMiscStatsName_F(TimingStatsElements stat) { switch (stat) { @@ -214,6 +226,9 @@ const __FlashStringHelper* getMiscStatsName_F(TimingStatsElements stat) { case TimingStatsElements::PLUGIN_CALL_1PS: return F("Plugin call 1 p/s"); case TimingStatsElements::CPLUGIN_CALL_50PS: return F("CPlugin call 50 p/s"); case TimingStatsElements::CPLUGIN_CALL_10PS: return F("CPlugin call 10 p/s"); + case TimingStatsElements::NWPLUGIN_CALL_50PS: return F("NWPlugin call 50 p/s"); + case TimingStatsElements::NWPLUGIN_CALL_10PS: return F("NWPlugin call 10 p/s"); + case TimingStatsElements::NWPLUGIN_PROCESS_NETWORK_EVENTS: return F("NWPlugin process events"); case TimingStatsElements::SENSOR_SEND_TASK: return F("SensorSendTask()"); case TimingStatsElements::COMMAND_EXEC_INTERNAL: return F("Exec Internal Command"); case TimingStatsElements::COMMAND_DECODE_INTERNAL: return F("Decode Internal Command"); @@ -260,6 +275,9 @@ const __FlashStringHelper* getMiscStatsName_F(TimingStatsElements stat) { case TimingStatsElements::IS_NUMERICAL: return F("isNumerical()"); case TimingStatsElements::HANDLE_SCHEDULER_IDLE: return F("handle_schedule() idle"); case TimingStatsElements::HANDLE_SCHEDULER_TASK: return F("handle_schedule() task"); +#if FEATURE_MQTT + case TimingStatsElements::PERIODICAL_MQTT: return F("Periodical MQTT"); +#endif case TimingStatsElements::PARSE_TEMPLATE_PADDED: return F("parseTemplate_padded()"); case TimingStatsElements::PARSE_SYSVAR: return F("parseSystemVariables()"); case TimingStatsElements::PARSE_SYSVAR_NOCHANGE: return F("parseSystemVariables() No change"); @@ -271,6 +289,7 @@ const __FlashStringHelper* getMiscStatsName_F(TimingStatsElements stat) { case TimingStatsElements::NTP_FAIL: return F("NTP Fail"); case TimingStatsElements::SYSTIME_UPDATED: return F("Systime Set"); case TimingStatsElements::C018_AIR_TIME: return F("C018 LoRa TTN - Air Time"); + case TimingStatsElements::C023_AIR_TIME: return F("C023 LoRa TTN - Air Time"); #ifdef LIMIT_BUILD_SIZE default: break; #else @@ -327,6 +346,12 @@ void stopTimerController(protocolIndex_t T, CPlugin::Function F, uint32_t statis if (mustLogCFunction(F)) { controllerStats[static_cast(T) * 256 + static_cast(F)].add(usecPassedSince_fast(statisticsTimerStart)); } } +void stopTimerNetwork(ESPEasy::net::networkDriverIndex_t T, NWPlugin::Function F, uint32_t statisticsTimerStart) +{ + if (mustLogNWFunction(F)) { networkStats[static_cast(T.value) * 256 + static_cast(F)].add(usecPassedSince_fast(statisticsTimerStart)); } +} + + void stopTimer(TimingStatsElements L, uint32_t statisticsTimerStart) { if (Settings.EnableTimingStats()) { miscStats[L].add(usecPassedSince_fast(statisticsTimerStart)); } diff --git a/src/src/DataStructs/TimingStats.h b/src/src/DataStructs/TimingStats.h index 5d70d5dd5..354140a7a 100644 --- a/src/src/DataStructs/TimingStats.h +++ b/src/src/DataStructs/TimingStats.h @@ -7,6 +7,7 @@ # include "../DataTypes/DeviceIndex.h" # include "../DataTypes/ESPEasy_plugin_functions.h" +# include "../../ESPEasy/net/DataTypes/NetworkDriverIndex.h" # include "../DataTypes/ProtocolIndex.h" # include "../Globals/Settings.h" # include "../Helpers/ESPEasy_time_calc.h" @@ -56,6 +57,7 @@ enum class TimingStatsElements { // Controller specific timing stats C018_AIR_TIME, + C023_AIR_TIME, // Related to Task runs & sending data + rules @@ -65,6 +67,9 @@ enum class TimingStatsElements { PLUGIN_CALL_1PS, CPLUGIN_CALL_10PS, CPLUGIN_CALL_50PS, + NWPLUGIN_CALL_10PS, + NWPLUGIN_CALL_50PS, + NWPLUGIN_PROCESS_NETWORK_EVENTS, SENSOR_SEND_TASK, SEND_DATA_STATS, COMPUTE_FORMULA_STATS, @@ -106,6 +111,9 @@ enum class TimingStatsElements { HANDLE_SCHEDULER_TASK, HANDLE_SCHEDULER_IDLE, BACKGROUND_TASKS, +#if FEATURE_MQTT + PERIODICAL_MQTT, +#endif CHECK_UDP, C013_SEND_UDP, C013_SEND_UDP_FAIL, @@ -165,6 +173,7 @@ const __FlashStringHelper* getPluginFunctionName(int function); bool mustLogFunction(int function); const __FlashStringHelper* getCPluginCFunctionName(CPlugin::Function function); bool mustLogCFunction(CPlugin::Function function); +bool mustLogNWFunction(NWPlugin::Function function); String getMiscStatsName(TimingStatsElements stat); void stopTimerTask(deviceIndex_t T, @@ -173,6 +182,9 @@ void stopTimerTask(deviceIndex_t T, void stopTimerController(protocolIndex_t T, CPlugin::Function F, uint32_t statisticsTimerStart); +void stopTimerNetwork(ESPEasy::net::networkDriverIndex_t T, + NWPlugin::Function F, + uint32_t statisticsTimerStart); void stopTimer(TimingStatsElements L, uint32_t statisticsTimerStart); void addMiscTimerStat(TimingStatsElements L, @@ -180,12 +192,14 @@ void addMiscTimerStat(TimingStatsElements L, extern std::map pluginStats; extern std::map controllerStats; +extern std::map networkStats; extern std::map miscStats; extern unsigned long timingstats_last_reset; # define START_TIMER const uint32_t statisticsTimerStart(micros()); # define STOP_TIMER_TASK(T, F) stopTimerTask(T, F, statisticsTimerStart); # define STOP_TIMER_CONTROLLER(T, F) stopTimerController(T, F, statisticsTimerStart); +# define STOP_TIMER_NETWORK(T, F) stopTimerNetwork(T, F, statisticsTimerStart); // #define STOP_TIMER_LOADFILE miscStats[LOADFILE_STATS].add(usecPassedSince_fast(statisticsTimerStart)); # define STOP_TIMER(L) stopTimer(TimingStatsElements::L, statisticsTimerStart); @@ -199,6 +213,7 @@ extern unsigned long timingstats_last_reset; # define START_TIMER ; # define STOP_TIMER_TASK(T, F) ; # define STOP_TIMER_CONTROLLER(T, F) ; +# define STOP_TIMER_NETWORK(T, F) ; # define STOP_TIMER(L) ; # define ADD_TIMER_STAT(L, T) ; diff --git a/src/src/DataStructs/ValueStruct.cpp b/src/src/DataStructs/ValueStruct.cpp new file mode 100644 index 000000000..91b657b2a --- /dev/null +++ b/src/src/DataStructs/ValueStruct.cpp @@ -0,0 +1,363 @@ +#include "../DataStructs/ValueStruct.h" + +#include "../Helpers/Memory.h" +#include "../Helpers/PrintToString.h" +#include "../Helpers/StringConverter_Numerical.h" + +#define VALUE_STRUCT_SSO_FIRST_CHAR_INDEX 1 +#define VALUE_STRUCT_SSO_FIRST_CHAR bytes_all[VALUE_STRUCT_SSO_FIRST_CHAR_INDEX] +#define VALUE_STRUCT_SSO_MAX_SIZE 14 + +// ******************************************************************************** +// ValueStruct +// ******************************************************************************** +ValueStruct::~ValueStruct() +{ + const auto vtype = static_cast(_valueType); + + if (!_isSSO && (vtype == ValueStruct::ValueType::String)) + { + if (str_val != nullptr) { + free(str_val); + } + } +} + +ValueStruct::ValueStruct(ValueStruct&& rhs) +{ + memcpy(bytes_all, rhs.bytes_all, sizeof(bytes_all)); + memset(rhs.bytes_all, 0, sizeof(bytes_all)); +} + +ValueStruct& ValueStruct::operator=(ValueStruct&& rhs) +{ + memcpy(bytes_all, rhs.bytes_all, sizeof(bytes_all)); + memset(rhs.bytes_all, 0, sizeof(bytes_all)); + return *this; +} + +ValueStruct::ValueStruct(const bool& val) : + _isSSO(0), + _valueType((uint64_t)ValueStruct::ValueType::Bool), + _size(1), + u64_val(val ? 1ull : 0ull) +{} + +ValueStruct::ValueStruct(int val) : + _isSSO(0), + _valueType((uint64_t)ValueStruct::ValueType::Int), + _size(sizeof(val) * 8), + i64_val(val) +{} + +#if defined(ESP32) && !defined(__riscv) +ValueStruct::ValueStruct(int32_t val) : + _isSSO(0), + _valueType((uint64_t)ValueStruct::ValueType::Int), + _size(sizeof(val) * 8), + i64_val(val) +{} + +#endif // if defined(ESP32) && !defined(__riscv) +ValueStruct::ValueStruct(uint32_t val) : + _isSSO(0), + _valueType((uint64_t)ValueStruct::ValueType::UInt), + _size(sizeof(val) * 8), + u64_val(val) +{} + +#if defined(ESP32) && !defined(__riscv) +ValueStruct::ValueStruct(size_t val) : + _isSSO(0), + _valueType((uint64_t)ValueStruct::ValueType::UInt), + _size(sizeof(val) * 8), + u64_val(val) +{} + +#endif // if defined(ESP32) && !defined(__riscv) +ValueStruct::ValueStruct(const uint64_t& val) : + _isSSO(0), + _valueType((uint64_t)ValueStruct::ValueType::UInt), + _size(sizeof(val) * 8), + u64_val(val) +{} + +ValueStruct::ValueStruct(const int64_t& val) : + _isSSO(0), + _valueType((uint64_t)ValueStruct::ValueType::Int), + _size(sizeof(val) * 8), + i64_val(val) +{} + +ValueStruct::ValueStruct(const float& val, + uint8_t nrDecimals, + bool trimTrailingZeros) : + _isSSO(0), + _trimTrailingZeros((uint64_t)trimTrailingZeros), + _valueType((uint64_t)ValueStruct::ValueType::Float), + _nrDecimals((uint64_t)nrDecimals), + _size(sizeof(val) * 8), + f_val(val) +{} + +ValueStruct::ValueStruct(const double& val, + uint8_t nrDecimals, + bool trimTrailingZeros) : + _isSSO(0), + _trimTrailingZeros((uint64_t)trimTrailingZeros), + _valueType((uint64_t)ValueStruct::ValueType::Double), + _nrDecimals((uint64_t)nrDecimals), + _size(sizeof(val) * 8), + d_val(val) +{} + +ValueStruct::ValueStruct(const char*val) : + _isSSO(0), + _valueType((uint64_t)ValueStruct::ValueType::String), + _size(val ? strlen_P((const char *)(val)) : 0), + str_val(nullptr) +{ + if (_size <= VALUE_STRUCT_SSO_MAX_SIZE) { + _isSSO = true; + + // Make sure it is zero-terminated when nullptr is given + VALUE_STRUCT_SSO_FIRST_CHAR = 0; + + if (_size > 0) { + memcpy(&VALUE_STRUCT_SSO_FIRST_CHAR, (uint8_t *)val, _size + 1); + } + } else { + str_val = special_calloc(1, _size + 1); + + if (str_val) { + memcpy_P(str_val, val, _size); + } + } +} + +ValueStruct::ValueStruct(const String& val) : + _isSSO(0), + _valueType((uint64_t)ValueStruct::ValueType::String), + _size(val.length()), + str_val(nullptr) +{ + if (_size <= VALUE_STRUCT_SSO_MAX_SIZE) { + _isSSO = true; + + if (_size == 0) { + // Make sure it is zero-terminated + VALUE_STRUCT_SSO_FIRST_CHAR = 0; + } + else { + memcpy(&VALUE_STRUCT_SSO_FIRST_CHAR, (uint8_t *)val.c_str(), val.length() + 1); + } + } else { + str_val = special_calloc(1, _size + 1); + + if (str_val) { + memcpy_P(str_val, (uint8_t *)val.c_str(), val.length() + 1); + } + } +} + +ValueStruct::ValueStruct(String&& val) : + _isSSO(0), + _valueType((uint64_t)ValueStruct::ValueType::String), + _size(val.length()), + str_val(nullptr) +{ + if (_size <= VALUE_STRUCT_SSO_MAX_SIZE) { + _isSSO = true; + + if (_size == 0) { + // Make sure it is zero-terminated + VALUE_STRUCT_SSO_FIRST_CHAR = 0; + } + else { + memcpy(&VALUE_STRUCT_SSO_FIRST_CHAR, (uint8_t *)val.c_str(), val.length() + 1); + } + } else { + str_val = special_calloc(1, _size + 1); + + if (str_val) { + memcpy_P(str_val, (uint8_t *)val.c_str(), val.length() + 1); + } + } + + // We can't move the allocated memory from 'message'. + // Just use move so we make sure the memory is de-allocated after this call. + const String str = std::move(val); +} + +ValueStruct::ValueStruct(const __FlashStringHelper *val) : + _isSSO(0), + _valueType((uint64_t)ValueStruct::ValueType::FlashString), + _size(val ? strlen_P((const char *)(val)) : 0), + str_val((void *)(val)) +{} + +String ValueStruct::toString() const +{ + ValueType valueType; + + return toString(valueType); +} + +String ValueStruct::toString(ValueType& valueType) const +{ + PrintToString p; + + print(p, valueType); + String res(p.getMove()); + return res; +} + +int64_t ValueStruct::toInt() const +{ + switch(getValueType()) + { + case ValueStruct::ValueType::Bool: + { + return u64_val == 0 ? 0 : 1; + } + case ValueStruct::ValueType::Int: + { + return i64_val; + } + case ValueStruct::ValueType::UInt: + { + if (u64_val < std::numeric_limits::max()) { + return u64_val; + } + break; + } + case ValueStruct::ValueType::String: + case ValueStruct::ValueType::FlashString: + case ValueStruct::ValueType::Float: + case ValueStruct::ValueType::Double: + case ValueStruct::ValueType::Unset: + break; + } + return toString().toInt(); +} + +double ValueStruct::toFloat() const +{ + switch(getValueType()) + { + case ValueStruct::ValueType::Bool: + { + return u64_val == 0 ? 0 : 1; + } + case ValueStruct::ValueType::Int: + { + return i64_val; + } + case ValueStruct::ValueType::UInt: + { + return u64_val; + } + case ValueStruct::ValueType::Float: + { + return f_val; + } + case ValueStruct::ValueType::Double: + { + return d_val; + } + case ValueStruct::ValueType::String: + case ValueStruct::ValueType::FlashString: + case ValueStruct::ValueType::Unset: + break; + } + return 0.0; +} + +size_t ValueStruct::print(Print& out) const +{ + ValueStruct::ValueType v; + + return print(out, v); +} + +size_t ValueStruct::print(Print& out, ValueType& valueType) const +{ + valueType = getValueType(); + + if (_isSSO) { + return out.write((const char *)&VALUE_STRUCT_SSO_FIRST_CHAR); + } + + switch (valueType) + { + case ValueStruct::ValueType::Bool: + { + return out.print(u64_val == 0 ? 0 : 1); + } + case ValueStruct::ValueType::String: + { + if (str_val == nullptr) { return 0; } + return out.write((const uint8_t *)str_val, _size); + } + case ValueStruct::ValueType::FlashString: + { + if (str_val == nullptr) { return 0; } + return out.print((const __FlashStringHelper *)str_val); + } + case ValueStruct::ValueType::Float: + { + String res; + + if (!toValidString(res, f_val, _nrDecimals, _trimTrailingZeros)) + { + valueType = ValueStruct::ValueType::String; + } + return out.print(res); + } + case ValueStruct::ValueType::Double: + { + String res; + + if (!doubleToValidString(res, d_val, _nrDecimals, _trimTrailingZeros)) + { + valueType = ValueStruct::ValueType::String; + } + return out.print(res); + } + case ValueStruct::ValueType::Int: + { + if (_size == 8) { + return out.print(ll2String(i64_val)); + } + auto v = static_cast(i64_val); + return out.print(v); + } + case ValueStruct::ValueType::UInt: + { + if (_size == 8) { + return out.print(ull2String(u64_val)); + } + auto v = static_cast(u64_val); + return out.print(v); + } + case ValueStruct::ValueType::Unset: + break; + } + return 0; +} + +bool ValueStruct::isEmpty() const +{ + if (_isSSO) { return VALUE_STRUCT_SSO_FIRST_CHAR == 0; } + + switch (getValueType()) + { + case ValueStruct::ValueType::String: + case ValueStruct::ValueType::FlashString: + return _size == 0; + case ValueStruct::ValueType::Unset: + return true; + default: break; + } + return false; +} diff --git a/src/src/DataStructs/ValueStruct.h b/src/src/DataStructs/ValueStruct.h new file mode 100644 index 000000000..ef07fabaa --- /dev/null +++ b/src/src/DataStructs/ValueStruct.h @@ -0,0 +1,126 @@ +#pragma once + +#include +#include + +// ******************************************************************************** +// ValueStruct +// ******************************************************************************** + + +class ValueStruct +{ +public: + + enum class ValueType : uint8_t { + Unset = 0, + String, + FlashString, + Float, + Double, + Int, + UInt, + Bool + + }; + + ValueStruct() : + _isSSO(0), + _valueType((uint64_t)ValueStruct::ValueType::Unset), + str_val(nullptr) + {} + + ~ValueStruct(); + + ValueStruct(const ValueStruct& rhs) = delete; + ValueStruct(ValueStruct&& rhs); + + ValueStruct(const bool& val); + + ValueStruct(int val); +#if defined(ESP32) && !defined(__riscv) + ValueStruct(int32_t val); +#endif + ValueStruct(uint32_t val); +#if defined(ESP32) && !defined(__riscv) + ValueStruct(size_t val); +#endif + ValueStruct(const uint64_t& val); + + ValueStruct(const int64_t& val); + + + ValueStruct(const float& val, + uint8_t nrDecimals = 4, + bool trimTrailingZeros = false); + + ValueStruct(const double& val, + uint8_t nrDecimals = 4, + bool trimTrailingZeros = false); + + + ValueStruct(const char*val); + + ValueStruct(const String& val); + + ValueStruct(String&& val); + + ValueStruct(const __FlashStringHelper *val); + + ValueStruct::ValueType getValueType() const + { + if (_isSSO) { return ValueStruct::ValueType::String; } + return static_cast(_valueType); + } + + ValueStruct& operator=(ValueStruct&& rhs); + ValueStruct& operator=(const ValueStruct& rhs) = delete; + + String toString() const; + + String toString(ValueType& valueType) const; + + int64_t toInt() const; + + double toFloat() const; + + size_t print(Print& out) const; + + bool isEmpty() const; + + bool isSet() const { return getValueType() != ValueStruct::ValueType::Unset; } + +private: + + size_t print(Print & out, + ValueType& valueType) const; + + union { + struct { + uint64_t _isSSO : 1; + uint64_t _trimTrailingZeros : 1; + uint64_t _valueType : 6; + uint64_t _nrDecimals : 8; + uint64_t _size : 16; + + uint64_t unused : 32; + + union { + void *str_val; + float f_val; + double d_val; + int64_t i64_val; + uint64_t u64_val; + + }; + + }; + + // When _isSSO, the short string will be stored in bytes 1 ... 15 + // The short string will be zero-terminated. + uint8_t bytes_all[16] = {}; + + }; + + +}; // class ValueStruct diff --git a/src/src/DataStructs/Web_StreamingBuffer.cpp b/src/src/DataStructs/Web_StreamingBuffer.cpp index a98f70f9f..83f54732e 100644 --- a/src/src/DataStructs/Web_StreamingBuffer.cpp +++ b/src/src/DataStructs/Web_StreamingBuffer.cpp @@ -1,9 +1,9 @@ #include "../DataStructs/Web_StreamingBuffer.h" -#include "../DataStructs/tcp_cleanup.h" #include "../DataTypes/ESPEasyTimeSource.h" #include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../../ESPEasy/net/ESPEasyNetwork.h" +#include "../../ESPEasy/net/Helpers/tcp_cleanup_ESP8266.h" // FIXME TD-er: Should keep a pointer to the webserver as a member, not use the global defined one. #include "../Globals/Services.h" diff --git a/src/src/DataStructs/WiFiEventData.cpp b/src/src/DataStructs/WiFiEventData.cpp deleted file mode 100644 index a205c6a0c..000000000 --- a/src/src/DataStructs/WiFiEventData.cpp +++ /dev/null @@ -1,292 +0,0 @@ -#include "../DataStructs/WiFiEventData.h" - -#include "../ESPEasyCore/ESPEasy_Log.h" - -#include "../Globals/RTC.h" -#include "../Globals/Settings.h" -#include "../Globals/WiFi_AP_Candidates.h" - -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/Networking.h" - - -#define WIFI_RECONNECT_WAIT 30000 // in milliSeconds - -#define CONNECT_TIMEOUT_MAX 4000 // in milliSeconds - - -#if FEATURE_USE_IPV6 -#include - -// ----------------------------------------------------------------------------------------------------------------------- -// ---------------------------------------------------- Private functions ------------------------------------------------ -// ----------------------------------------------------------------------------------------------------------------------- - -esp_netif_t* get_esp_interface_netif(esp_interface_t interface); -#endif - - - -bool WiFiEventData_t::WiFiConnectAllowed() const { - if (WiFi.status() == WL_IDLE_STATUS) { - // FIXME TD-er: What to do now? Set a timer? - //return false; - if (last_wifi_connect_attempt_moment.isSet() && - !last_wifi_connect_attempt_moment.timeoutReached(WIFI_PROCESS_EVENTS_TIMEOUT)) { - return false; - } - } - if (!wifiConnectAttemptNeeded) return false; - if (intent_to_reboot) return false; - if (wifiSetupConnect) return true; - if (wifiConnectInProgress) { - if (last_wifi_connect_attempt_moment.isSet() && - !last_wifi_connect_attempt_moment.timeoutReached(WIFI_PROCESS_EVENTS_TIMEOUT)) { - return false; - } - } - if (lastDisconnectMoment.isSet()) { - // TODO TD-er: Make this time more dynamic. - if (!lastDisconnectMoment.timeoutReached(1000)) { - return false; - } - } - return true; -} - -bool WiFiEventData_t::unprocessedWifiEvents() const { - if (processedConnect && processedDisconnect && processedGotIP && processedDHCPTimeout -#if FEATURE_USE_IPV6 - && processedGotIP6 -#endif - ) - { - return false; - } - if (!processedConnect) { - if (lastConnectMoment.isSet() && lastConnectMoment.timeoutReached(WIFI_PROCESS_EVENTS_TIMEOUT)) { - return false; - } - } - if (!processedGotIP) { - if (lastGetIPmoment.isSet() && lastGetIPmoment.timeoutReached(WIFI_PROCESS_EVENTS_TIMEOUT)) { - return false; - } - } - if (!processedDisconnect) { - if (lastDisconnectMoment.isSet() && lastDisconnectMoment.timeoutReached(WIFI_PROCESS_EVENTS_TIMEOUT)) { - return false; - } - } - if (!processedDHCPTimeout) { - return false; - } - return true; -} - -void WiFiEventData_t::clearAll() { - markWiFiTurnOn(); - lastGetScanMoment.clear(); - last_wifi_connect_attempt_moment.clear(); - timerAPstart.clear(); - - lastWiFiResetMoment.setNow(); - wifi_TX_pwr = 0; - usedChannel = 0; -} - -void WiFiEventData_t::markWiFiTurnOn() { - setWiFiDisconnected(); -// lastDisconnectMoment.clear(); - lastConnectMoment.clear(); - lastGetIPmoment.clear(); - wifi_considered_stable = false; - - clear_processed_flags(); -} - -void WiFiEventData_t::clear_processed_flags() { - // Mark all flags to default to prevent handling old events. - WiFi.scanDelete(); - processedConnect = true; - processedDisconnect = true; - processedGotIP = true; - #if FEATURE_USE_IPV6 - processedGotIP6 = true; - #endif - processedDHCPTimeout = true; - processedConnectAPmode = true; - processedDisconnectAPmode = true; - processedScanDone = true; - wifiConnectAttemptNeeded = true; - wifiConnectInProgress = false; - processingDisconnect.clear(); - dns0_cache = IPAddress(); - dns1_cache = IPAddress(); -} - -void WiFiEventData_t::markWiFiBegin() { - markWiFiTurnOn(); - last_wifi_connect_attempt_moment.setNow(); - wifiConnectInProgress = true; - usedChannel = 0; - ++wifi_connect_attempt; - if (!timerAPstart.isSet()) { - timerAPstart.setMillisFromNow(3 * WIFI_RECONNECT_WAIT); - } -} - - -void WiFiEventData_t::setWiFiDisconnected() { - wifiStatus = ESPEASY_WIFI_DISCONNECTED; - last_wifi_connect_attempt_moment.clear(); - wifiConnectInProgress = false; -#if FEATURE_ESPEASY_P2P - updateUDPport(true); -#endif - -} - -void WiFiEventData_t::setWiFiGotIP() { - bitSet(wifiStatus, ESPEASY_WIFI_GOT_IP); - processedGotIP = true; - if (valid_DNS_address(WiFi.dnsIP(0))) { - dns0_cache = WiFi.dnsIP(0); - } - if (valid_DNS_address(WiFi.dnsIP(1))) { - dns1_cache = WiFi.dnsIP(1); - } -} - -void WiFiEventData_t::setWiFiConnected() { - bitSet(wifiStatus, ESPEASY_WIFI_CONNECTED); - processedConnect = true; -} - -void WiFiEventData_t::setWiFiServicesInitialized() { - if (!unprocessedWifiEvents() && WiFiConnected() && WiFiGotIP()) { - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("WiFi : WiFi services initialized")); - #endif - bitSet(wifiStatus, ESPEASY_WIFI_SERVICES_INITIALIZED); - wifiConnectInProgress = false; - wifiConnectAttemptNeeded = false; - dns0_cache = WiFi.dnsIP(0); - dns1_cache = WiFi.dnsIP(1); - -#if FEATURE_ESPEASY_P2P - updateUDPport(false); -#endif - } -} - -void WiFiEventData_t::markGotIP() { - lastGetIPmoment.setNow(); - - // Create the 'got IP event' so mark the wifiStatus to not have the got IP flag set - // This also implies the services are not fully initialized. - bitClear(wifiStatus, ESPEASY_WIFI_GOT_IP); - bitClear(wifiStatus, ESPEASY_WIFI_SERVICES_INITIALIZED); - processedGotIP = false; -} - -#if FEATURE_USE_IPV6 - void WiFiEventData_t::markGotIPv6(const IPAddress& ip6) { - processedGotIP6 = false; - unprocessed_IP6 = ip6; - } -#endif - - -void WiFiEventData_t::markLostIP() { - bitClear(wifiStatus, ESPEASY_WIFI_GOT_IP); - bitClear(wifiStatus, ESPEASY_WIFI_SERVICES_INITIALIZED); -} - -void WiFiEventData_t::markDisconnect(WiFiDisconnectReason reason) { -/* - #if defined(ESP32) - if ((WiFi.getMode() & WIFI_MODE_STA) == 0) return; - #else // if defined(ESP32) - if ((WiFi.getMode() & WIFI_STA) == 0) return; - #endif // if defined(ESP32) -*/ - lastDisconnectMoment.setNow(); - usedChannel = 0; - - if (last_wifi_connect_attempt_moment.isSet() && !lastConnectMoment.isSet()) { - // There was an unsuccessful connection attempt - lastConnectedDuration_us = last_wifi_connect_attempt_moment.timeDiff(lastDisconnectMoment); - } else { - if (last_wifi_connect_attempt_moment.isSet()) - lastConnectedDuration_us = lastConnectMoment.timeDiff(lastDisconnectMoment); - else - lastConnectedDuration_us = 0; - } - lastDisconnectReason = reason; - processedDisconnect = false; - wifiConnectInProgress = false; -} - -void WiFiEventData_t::markConnected(const String& ssid, const uint8_t bssid[6], uint8_t channel) { - usedChannel = channel; - lastConnectMoment.setNow(); - processedConnect = false; - channel_changed = RTC.lastWiFiChannel != channel; - last_ssid = ssid; - bssid_changed = false; - auth_mode = WiFi_AP_Candidates.getCurrent().enc_type; - - RTC.lastWiFiChannel = channel; - for (uint8_t i = 0; i < 6; ++i) { - if (RTC.lastBSSID[i] != bssid[i]) { - bssid_changed = true; - RTC.lastBSSID[i] = bssid[i]; - } - } -#if FEATURE_USE_IPV6 - if (Settings.EnableIPv6()) { - WiFi.enableIPv6(true); - } -#endif -} - -void WiFiEventData_t::markConnectedAPmode(const uint8_t mac[6]) { - lastMacConnectedAPmode = mac; - processedConnectAPmode = false; -} - -void WiFiEventData_t::markDisconnectedAPmode(const uint8_t mac[6]) { - lastMacDisconnectedAPmode = mac; - processedDisconnectAPmode = false; -} - - - -String WiFiEventData_t::ESPeasyWifiStatusToString() const { - String log; - if (WiFiDisconnected()) { - log = F("DISCONNECTED"); - } else { - if (WiFiConnected()) { - log += F("Conn. "); - } - if (WiFiGotIP()) { - log += F("IP "); - } - if (WiFiServicesInitialized()) { - log += F("Init"); - } - } - return log; -} - - -uint32_t WiFiEventData_t::getSuggestedTimeout(int index, uint32_t minimum_timeout) const { - auto it = connectDurations.find(index); - if (it == connectDurations.end()) { - return 3 * minimum_timeout; - } - const uint32_t res = 3 * it->second; - return constrain(res, minimum_timeout, CONNECT_TIMEOUT_MAX); -} \ No newline at end of file diff --git a/src/src/DataStructs/WiFiEventData.h b/src/src/DataStructs/WiFiEventData.h deleted file mode 100644 index 1dbd20fdb..000000000 --- a/src/src/DataStructs/WiFiEventData.h +++ /dev/null @@ -1,163 +0,0 @@ -#ifndef DATASTRUCTS_WIFIEVENTDATA_H -#define DATASTRUCTS_WIFIEVENTDATA_H - -#include "../DataStructs/MAC_address.h" -#include "../DataTypes/WiFiDisconnectReason.h" -#include "../Helpers/LongTermTimer.h" - - -#ifdef ESP32 -# include -# include -# include - -#endif // ifdef ESP32 - -#include - -#ifdef ESP8266 -# include -# include -#endif // ifdef ESP8266 - -#include - -// WifiStatus -#define ESPEASY_WIFI_DISCONNECTED 0 - -// Bit numbers for WiFi status -#define ESPEASY_WIFI_CONNECTED 0 -#define ESPEASY_WIFI_GOT_IP 1 -#define ESPEASY_WIFI_SERVICES_INITIALIZED 2 - - -#define WIFI_PROCESS_EVENTS_TIMEOUT 20000 // in milliSeconds - -struct WiFiEventData_t { - bool WiFiConnectAllowed() const; - - bool unprocessedWifiEvents() const; - - void clearAll(); - void markWiFiTurnOn(); - void clear_processed_flags(); - void markWiFiBegin(); - - bool WiFiDisconnected() const { - return wifiStatus == ESPEASY_WIFI_DISCONNECTED; - } - - bool WiFiGotIP() const { - return bitRead(wifiStatus, ESPEASY_WIFI_GOT_IP); - } - - bool WiFiConnected() const { - return bitRead(wifiStatus, ESPEASY_WIFI_CONNECTED); - } - - bool WiFiServicesInitialized() const { - return bitRead(wifiStatus, ESPEASY_WIFI_SERVICES_INITIALIZED); - } - - void setWiFiDisconnected(); - void setWiFiGotIP(); - void setWiFiConnected(); - void setWiFiServicesInitialized(); - - - void markGotIP(); -#if FEATURE_USE_IPV6 - void markGotIPv6(const IPAddress& ip6); -#endif - void markLostIP(); - void markDisconnect(WiFiDisconnectReason reason); - void markConnected(const String& ssid, - const uint8_t bssid[6], - uint8_t channel); - void markConnectedAPmode(const uint8_t mac[6]); - void markDisconnectedAPmode(const uint8_t mac[6]); - - void setAuthMode(uint8_t newMode) { - auth_mode = newMode; - } - - String ESPeasyWifiStatusToString() const; - - uint32_t getSuggestedTimeout(int index, - uint32_t minimum_timeout) const; - - - // WiFi related data - bool wifiSetup = false; - bool wifiSetupConnect = false; - uint8_t wifiStatus = ESPEASY_WIFI_DISCONNECTED; - LongTermTimer last_wifi_connect_attempt_moment; - unsigned int wifi_connect_attempt = 0; - bool wifi_considered_stable = false; - int wifi_reconnects = -1; // First connection attempt is not a reconnect. - String last_ssid; - float wifi_TX_pwr = 0; - bool bssid_changed = false; - bool channel_changed = false; - - uint8_t auth_mode = 0; - uint8_t lastScanChannel = 0; - uint8_t usedChannel = 0; - - bool eventError = false; - - - WiFiDisconnectReason lastDisconnectReason = WIFI_DISCONNECT_REASON_UNSPECIFIED; - LongTermTimer lastScanMoment; - LongTermTimer lastConnectMoment; - LongTermTimer lastDisconnectMoment; - LongTermTimer lastWiFiResetMoment; - LongTermTimer lastGetIPmoment; - LongTermTimer lastGetScanMoment; - LongTermTimer::Duration lastConnectedDuration_us = 0ll; - LongTermTimer timerAPoff; // Timer to check whether the AP mode should be disabled (0 = disabled) - LongTermTimer timerAPstart; // Timer to start AP mode, started when no valid network is detected. - bool intent_to_reboot = false; - MAC_address lastMacConnectedAPmode; - MAC_address lastMacDisconnectedAPmode; - - IPAddress dns0_cache; - IPAddress dns1_cache; - - #if FEATURE_USE_IPV6 - IPAddress unprocessed_IP6; - #endif - - - // processDisconnect() may clear all WiFi settings, resulting in clearing processedDisconnect - // This can cause recursion, so a semaphore is needed here. - LongTermTimer processingDisconnect; - - - // Semaphore like bools for processing data gathered from WiFi events. - bool processedConnect = true; - bool processedDisconnect = true; - bool processedGotIP = true; - #if FEATURE_USE_IPV6 - bool processedGotIP6 = true; - #endif - bool processedDHCPTimeout = true; - bool processedConnectAPmode = true; - bool processedDisconnectAPmode = true; - bool processedScanDone = true; - bool wifiConnectAttemptNeeded = true; - bool wifiConnectInProgress = false; - bool warnedNoValidWiFiSettings = false; - - bool performedClearWiFiCredentials = false; - - unsigned long connectionFailures = 0; - - std::mapconnectDurations; - -#ifdef ESP32 - WiFiEventId_t wm_event_id = 0; -#endif // ifdef ESP32 -}; - -#endif // ifndef DATASTRUCTS_WIFIEVENTDATA_H diff --git a/src/src/DataStructs/WiFi_AP_Candidates_NVS.h b/src/src/DataStructs/WiFi_AP_Candidates_NVS.h deleted file mode 100644 index 45e96d675..000000000 --- a/src/src/DataStructs/WiFi_AP_Candidates_NVS.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef DATASTRUCTS_WIFI_AP_CANDIDATES_NVS_H -#define DATASTRUCTS_WIFI_AP_CANDIDATES_NVS_H - -#include "../../ESPEasy_common.h" - -#ifdef ESP32 -#include "../DataStructs/WiFi_AP_Candidate.h" - -class WiFi_AP_Candidates_NVS { -public: - - static bool loadCandidate_from_NVS(WiFi_AP_Candidate& candidate); - - static void currentConnection_to_NVS(const WiFi_AP_Candidate& candidate); - - static void clear_from_NVS(); -}; - -#endif -#endif \ No newline at end of file diff --git a/src/src/DataStructs_templ/SettingsStruct.cpp b/src/src/DataStructs_templ/SettingsStruct.cpp index d2473f5ef..41f017435 100644 --- a/src/src/DataStructs_templ/SettingsStruct.cpp +++ b/src/src/DataStructs_templ/SettingsStruct.cpp @@ -3,52 +3,66 @@ #include "../../ESPEasy_common.h" #ifndef DATASTRUCTS_SETTINGSSTRUCT_CPP -#define DATASTRUCTS_SETTINGSSTRUCT_CPP +# define DATASTRUCTS_SETTINGSSTRUCT_CPP -#include "../CustomBuild/CompiletimeDefines.h" -#include "../CustomBuild/ESPEasyLimits.h" -#include "../DataStructs/DeviceStruct.h" -#include "../DataTypes/SPI_options.h" -#include "../DataTypes/NPluginID.h" -#include "../DataTypes/PluginID.h" -#include "../Globals/Plugins.h" -#include "../Globals/CPlugins.h" -#include "../Helpers/Misc.h" -#include "../Helpers/StringParser.h" +# include "../CustomBuild/CompiletimeDefines.h" +# include "../CustomBuild/ESPEasyLimits.h" +# include "../DataStructs/DeviceStruct.h" +# include "../ESPEasy/net/DataStructs/NetworkDriverStruct.h" +# include "../DataTypes/NPluginID.h" +# include "../ESPEasy/net/DataTypes/NWPluginID.h" +# include "../ESPEasy/net/DataTypes/NWPluginID.h" +# include "../DataTypes/PluginID.h" +# include "../DataTypes/SPI_options.h" +# include "../Globals/CPlugins.h" +# include "../Globals/Plugins.h" +# include "../Helpers/Misc.h" +# include "../Helpers/StringParser.h" +# include "../ESPEasy/net/Helpers/_NWPlugin_init.h" -#if FEATURE_I2C_MULTIPLE -#include "../Helpers/Hardware_device_info.h" -#endif +# include "../../ESPEasy/net/Globals/NWPlugins.h" -#if ESP_IDF_VERSION_MAJOR >= 5 -#include -#include "include/esp32x_fixes.h" -#endif +# if FEATURE_I2C_MULTIPLE +# include "../Helpers/Hardware_device_info.h" +# endif + +# if ESP_IDF_VERSION_MAJOR >= 5 +# include +# include "include/esp32x_fixes.h" +# endif // if ESP_IDF_VERSION_MAJOR >= 5 + +# if FEATURE_ETHERNET +# include "../../ESPEasy/net/DataTypes/EthernetParameters.h" +# endif + +# if CONFIG_ETH_USE_ESP32_EMAC && FEATURE_ETHERNET +# include +# endif /* -// VariousBits1 defaults to 0, keep in mind when adding bit lookups. -template + // VariousBits1 defaults to 0, keep in mind when adding bit lookups. +template bool SettingsStruct_tmpl::appendUnitToHostname() const { return !bitRead(VariousBits1, 1); } -template +template void SettingsStruct_tmpl::appendUnitToHostname(bool value) { bitWrite(VariousBits1, 1, !value); } -template +template bool SettingsStruct_tmpl::uniqueMQTTclientIdReconnect_unused() const { return bitRead(VariousBits1, 2); } -template +template void SettingsStruct_tmpl::uniqueMQTTclientIdReconnect_unused(bool value) { bitWrite(VariousBits1, 2, value); } -template +template bool SettingsStruct_tmpl::OldRulesEngine() const { #ifdef WEBSERVER_NEW_RULES return !bitRead(VariousBits1, 3); @@ -57,83 +71,83 @@ bool SettingsStruct_tmpl::OldRulesEngine() const { #endif } -template +template void SettingsStruct_tmpl::OldRulesEngine(bool value) { bitWrite(VariousBits1, 3, !value); } -template +template bool SettingsStruct_tmpl::ForceWiFi_bg_mode() const { return bitRead(VariousBits1, 4); } -template +template void SettingsStruct_tmpl::ForceWiFi_bg_mode(bool value) { bitWrite(VariousBits1, 4, value); } -template +template bool SettingsStruct_tmpl::WiFiRestart_connection_lost() const { return bitRead(VariousBits1, 5); } -template +template void SettingsStruct_tmpl::WiFiRestart_connection_lost(bool value) { bitWrite(VariousBits1, 5, value); } -template +template bool SettingsStruct_tmpl::EcoPowerMode() const { return bitRead(VariousBits1, 6); } -template +template void SettingsStruct_tmpl::EcoPowerMode(bool value) { bitWrite(VariousBits1, 6, value); } -template +template bool SettingsStruct_tmpl::WifiNoneSleep() const { return bitRead(VariousBits1, 7); } -template +template void SettingsStruct_tmpl::WifiNoneSleep(bool value) { bitWrite(VariousBits1, 7, value); } // Enable send gratuitous ARP by default, so invert the values (default = 0) -template +template bool SettingsStruct_tmpl::gratuitousARP() const { return !bitRead(VariousBits1, 8); } -template +template void SettingsStruct_tmpl::gratuitousARP(bool value) { bitWrite(VariousBits1, 8, !value); } -template +template bool SettingsStruct_tmpl::TolerantLastArgParse() const { return bitRead(VariousBits1, 9); } -template +template void SettingsStruct_tmpl::TolerantLastArgParse(bool value) { bitWrite(VariousBits1, 9, value); } -template +template bool SettingsStruct_tmpl::SendToHttp_ack() const { return bitRead(VariousBits1, 10); } -template +template void SettingsStruct_tmpl::SendToHttp_ack(bool value) { bitWrite(VariousBits1, 10, value); } -template +template bool SettingsStruct_tmpl::UseESPEasyNow() const { #ifdef USES_ESPEASY_NOW return bitRead(VariousBits1, 11); @@ -142,58 +156,58 @@ bool SettingsStruct_tmpl::UseESPEasyNow() const { #endif } -template +template void SettingsStruct_tmpl::UseESPEasyNow(bool value) { #ifdef USES_ESPEASY_NOW bitWrite(VariousBits1, 11, value); #endif } -template +template bool SettingsStruct_tmpl::IncludeHiddenSSID() const { return bitRead(VariousBits1, 12); } -template +template void SettingsStruct_tmpl::IncludeHiddenSSID(bool value) { bitWrite(VariousBits1, 12, value); } -template +template bool SettingsStruct_tmpl::UseMaxTXpowerForSending() const { return bitRead(VariousBits1, 13); } -template +template void SettingsStruct_tmpl::UseMaxTXpowerForSending(bool value) { bitWrite(VariousBits1, 13, value); } -template -bool SettingsStruct_tmpl::ApDontForceSetup() const { +template +bool SettingsStruct_tmpl::ApCaptivePortal() const { return bitRead(VariousBits1, 14); } -template -void SettingsStruct_tmpl::ApDontForceSetup(bool value) { +template +void SettingsStruct_tmpl::ApCaptivePortal(bool value) { bitWrite(VariousBits1, 14, value); } // VariousBits1 bit 15 was used by PeriodicalScanWiFi // Now removed, is reset to 0, can be used for some other setting. -template +template bool SettingsStruct_tmpl::JSONBoolWithoutQuotes() const { return bitRead(VariousBits1, 16); } -template +template void SettingsStruct_tmpl::JSONBoolWithoutQuotes(bool value) { bitWrite(VariousBits1, 16, value); } */ -template +template bool SettingsStruct_tmpl::CombineTaskValues_SingleEvent(taskIndex_t taskIndex) const { if (validTaskIndex(taskIndex)) { return bitRead(TaskDeviceSendDataFlags[taskIndex], 0); @@ -201,7 +215,7 @@ bool SettingsStruct_tmpl::CombineTaskValues_SingleEvent(taskIndex_t tas return false; } -template +template void SettingsStruct_tmpl::CombineTaskValues_SingleEvent(taskIndex_t taskIndex, bool value) { if (validTaskIndex(taskIndex)) { bitWrite(TaskDeviceSendDataFlags[taskIndex], 0, value); @@ -209,7 +223,7 @@ void SettingsStruct_tmpl::CombineTaskValues_SingleEvent(taskIndex_t tas } #if FEATURE_STRING_VARIABLES -template +template bool SettingsStruct_tmpl::ShowDerivedTaskValues(taskIndex_t taskIndex) const { if (validTaskIndex(taskIndex)) { return bitRead(TaskDeviceSendDataFlags[taskIndex], 1); @@ -217,14 +231,14 @@ bool SettingsStruct_tmpl::ShowDerivedTaskValues(taskIndex_t taskIndex) return false; } -template +template void SettingsStruct_tmpl::ShowDerivedTaskValues(taskIndex_t taskIndex, bool value) { if (validTaskIndex(taskIndex)) { bitWrite(TaskDeviceSendDataFlags[taskIndex], 1, value); } } -template +template bool SettingsStruct_tmpl::EventAndLogDerivedTaskValues(taskIndex_t taskIndex) const { if (validTaskIndex(taskIndex)) { return bitRead(TaskDeviceSendDataFlags[taskIndex], 2); @@ -232,14 +246,14 @@ bool SettingsStruct_tmpl::EventAndLogDerivedTaskValues(taskIndex_t task return false; } -template +template void SettingsStruct_tmpl::EventAndLogDerivedTaskValues(taskIndex_t taskIndex, bool value) { if (validTaskIndex(taskIndex)) { bitWrite(TaskDeviceSendDataFlags[taskIndex], 2, value); } } -template +template bool SettingsStruct_tmpl::SendDerivedTaskValues(taskIndex_t taskIndex, controllerIndex_t controllerIndex) const { if (validTaskIndex(taskIndex) && validControllerIndex(controllerIndex)) { return bitRead(TaskDeviceSendDataFlags[taskIndex], 3 + controllerIndex); // ATTENTION: uses bits 3..6!!! @@ -247,7 +261,7 @@ bool SettingsStruct_tmpl::SendDerivedTaskValues(taskIndex_t taskIndex, return false; } -template +template void SettingsStruct_tmpl::SendDerivedTaskValues(taskIndex_t taskIndex, controllerIndex_t controllerIndex, bool value) { if (validTaskIndex(taskIndex) && validControllerIndex(controllerIndex)) { bitWrite(TaskDeviceSendDataFlags[taskIndex], 3 + controllerIndex, value); // ATTENTION: uses bits 3..6!!! @@ -255,183 +269,183 @@ void SettingsStruct_tmpl::SendDerivedTaskValues(taskIndex_t taskIndex, } #endif // if FEATURE_STRING_VARIABLES /* -template -bool SettingsStruct_tmpl::DoNotStartAP() const { +template +bool SettingsStruct_tmpl::DoNotStartAPfallback_ConnectFail() const { return bitRead(VariousBits1, 17); } -template -void SettingsStruct_tmpl::DoNotStartAP(bool value) { +template +void SettingsStruct_tmpl::DoNotStartAPfallback_ConnectFail(bool value) { bitWrite(VariousBits1, 17, value); } -template +template bool SettingsStruct_tmpl::UseAlternativeDeepSleep() const { return bitRead(VariousBits1, 18); } -template +template void SettingsStruct_tmpl::UseAlternativeDeepSleep(bool value) { bitWrite(VariousBits1, 18, value); } -template +template bool SettingsStruct_tmpl::UseLastWiFiFromRTC() const { return bitRead(VariousBits1, 19); } -template +template void SettingsStruct_tmpl::UseLastWiFiFromRTC(bool value) { bitWrite(VariousBits1, 19, value); } -template +template bool SettingsStruct_tmpl::EnableTimingStats() const { return bitRead(VariousBits1, 20); } -template +template void SettingsStruct_tmpl::EnableTimingStats(bool value) { bitWrite(VariousBits1, 20, value); } -template +template bool SettingsStruct_tmpl::AllowTaskValueSetAllPlugins() const { return bitRead(VariousBits1, 21); } -template +template void SettingsStruct_tmpl::AllowTaskValueSetAllPlugins(bool value) { bitWrite(VariousBits1, 21, value); } -template +template bool SettingsStruct_tmpl::EnableClearHangingI2Cbus() const { return bitRead(VariousBits1, 22); } -template +template void SettingsStruct_tmpl::EnableClearHangingI2Cbus(bool value) { bitWrite(VariousBits1, 22, value); } -template +template bool SettingsStruct_tmpl::EnableRAMTracking() const { return bitRead(VariousBits1, 23); } -template +template void SettingsStruct_tmpl::EnableRAMTracking(bool value) { bitWrite(VariousBits1, 23, value); } -template +template bool SettingsStruct_tmpl::EnableRulesCaching() const { return !bitRead(VariousBits1, 24); } -template +template void SettingsStruct_tmpl::EnableRulesCaching(bool value) { bitWrite(VariousBits1, 24, !value); } -template +template bool SettingsStruct_tmpl::EnableRulesEventReorder() const { return !bitRead(VariousBits1, 25); } -template +template void SettingsStruct_tmpl::EnableRulesEventReorder(bool value) { bitWrite(VariousBits1, 25, !value); } -template +template bool SettingsStruct_tmpl::AllowOTAUnlimited() const { return bitRead(VariousBits1, 26); } -template +template void SettingsStruct_tmpl::AllowOTAUnlimited(bool value) { bitWrite(VariousBits1, 26, value); } -template +template bool SettingsStruct_tmpl::SendToHTTP_follow_redirects() const { return bitRead(VariousBits1, 27); } -template +template void SettingsStruct_tmpl::SendToHTTP_follow_redirects(bool value) { bitWrite(VariousBits1, 27, value); } #if FEATURE_AUTO_DARK_MODE -template +template uint8_t SettingsStruct_tmpl::getCssMode() const { return get2BitFromUL(VariousBits1, 28); // Also occupies bit 29! } -template +template void SettingsStruct_tmpl::setCssMode(uint8_t value) { set2BitToUL(VariousBits1, 28, value); // Also occupies bit 29! } #endif // FEATURE_AUTO_DARK_MODE #if FEATURE_I2C_DEVICE_CHECK -template +template bool SettingsStruct_tmpl::CheckI2Cdevice() const { // Inverted return !bitRead(VariousBits1, 30); } -template +template void SettingsStruct_tmpl::CheckI2Cdevice(bool value) { // Inverted bitWrite(VariousBits1, 30, !value); } #endif // if FEATURE_I2C_DEVICE_CHECK */ /* -template +template bool SettingsStruct_tmpl::WaitWiFiConnect() const { return bitRead(VariousBits2, 0); } -template +template void SettingsStruct_tmpl::WaitWiFiConnect(bool value) { bitWrite(VariousBits2, 0, value); } -template +template bool SettingsStruct_tmpl::SDK_WiFi_autoreconnect() const { return bitRead(VariousBits2, 1); } -template +template void SettingsStruct_tmpl::SDK_WiFi_autoreconnect(bool value) { bitWrite(VariousBits2, 1, value); } #if FEATURE_RULES_EASY_COLOR_CODE -template +template bool SettingsStruct_tmpl::DisableRulesCodeCompletion() const { return bitRead(VariousBits2, 2); } -template +template void SettingsStruct_tmpl::DisableRulesCodeCompletion(bool value) { bitWrite(VariousBits2, 2, value); } #endif // if FEATURE_RULES_EASY_COLOR_CODE #if FEATURE_TARSTREAM_SUPPORT -template +template bool SettingsStruct_tmpl::DisableSaveConfigAsTar() const { return bitRead(VariousBits2, 3); // Using bit 4 now... } -template +template void SettingsStruct_tmpl::DisableSaveConfigAsTar(bool value) { bitWrite(VariousBits2, 3, value); // Using bit 4 now... } @@ -439,7 +453,7 @@ void SettingsStruct_tmpl::DisableSaveConfigAsTar(bool value) { */ -template +template bool SettingsStruct_tmpl::isTaskEnableReadonly(taskIndex_t taskIndex) const { if (validTaskIndex(taskIndex)) { return bitRead(VariousTaskBits[taskIndex], 0); @@ -447,7 +461,7 @@ bool SettingsStruct_tmpl::isTaskEnableReadonly(taskIndex_t taskIndex) c return false; } -template +template void SettingsStruct_tmpl::setTaskEnableReadonly(taskIndex_t taskIndex, bool value) { if (validTaskIndex(taskIndex)) { bitWrite(VariousTaskBits[taskIndex], 0, value); @@ -455,7 +469,7 @@ void SettingsStruct_tmpl::setTaskEnableReadonly(taskIndex_t taskIndex, } #if FEATURE_PLUGIN_PRIORITY -template +template bool SettingsStruct_tmpl::isPowerManagerTask(taskIndex_t taskIndex) const { if (validTaskIndex(taskIndex)) { return bitRead(VariousTaskBits[taskIndex], 1); @@ -463,14 +477,14 @@ bool SettingsStruct_tmpl::isPowerManagerTask(taskIndex_t taskIndex) con return false; } -template +template void SettingsStruct_tmpl::setPowerManagerTask(taskIndex_t taskIndex, bool value) { if (validTaskIndex(taskIndex)) { bitWrite(VariousTaskBits[taskIndex], 1, value); } } -template +template bool SettingsStruct_tmpl::isPriorityTask(taskIndex_t taskIndex) const { if (validTaskIndex(taskIndex)) { return isPowerManagerTask(taskIndex); // Add more? @@ -480,7 +494,7 @@ bool SettingsStruct_tmpl::isPriorityTask(taskIndex_t taskIndex) const { #endif // if FEATURE_PLUGIN_PRIORITY #if FEATURE_MQTT -template +template bool SettingsStruct_tmpl::SendRetainedTaskValues(taskIndex_t taskIndex, controllerIndex_t controllerIndex) const { if (validTaskIndex(taskIndex) && validControllerIndex(controllerIndex)) { return bitRead(VariousTaskBits[taskIndex], 2 + controllerIndex); // ATTENTION: uses bits 2..5!!! @@ -488,7 +502,7 @@ bool SettingsStruct_tmpl::SendRetainedTaskValues(taskIndex_t taskIndex, return false; } -template +template void SettingsStruct_tmpl::SendRetainedTaskValues(taskIndex_t taskIndex, controllerIndex_t controllerIndex, bool value) { if (validTaskIndex(taskIndex) && validControllerIndex(controllerIndex)) { bitWrite(VariousTaskBits[taskIndex], 2 + controllerIndex, value); // ATTENTION: uses bits 2..5!!! @@ -496,12 +510,12 @@ void SettingsStruct_tmpl::SendRetainedTaskValues(taskIndex_t taskIndex, } #endif // if FEATURE_MQTT -template +template ExtTimeSource_e SettingsStruct_tmpl::ExtTimeSource() const { return static_cast(ExternalTimeSource >> 1); } -template +template void SettingsStruct_tmpl::ExtTimeSource(ExtTimeSource_e value) { uint8_t newValue = static_cast(value) << 1; if (UseNTP()) { @@ -510,18 +524,18 @@ void SettingsStruct_tmpl::ExtTimeSource(ExtTimeSource_e value) { ExternalTimeSource = newValue; } -template +template bool SettingsStruct_tmpl::UseNTP() const { return bitRead(ExternalTimeSource, 0); } -template +template void SettingsStruct_tmpl::UseNTP(bool value) { bitWrite(ExternalTimeSource, 0, value); } -template +template void SettingsStruct_tmpl::validate() { if (UDPPort > 65535) { UDPPort = 0; } @@ -554,16 +568,60 @@ void SettingsStruct_tmpl::validate() { console_serial_rxpin = 13; console_serial_txpin = 15; } +# endif // ifdef ESP8266 + # endif // if FEATURE_DEFINE_SERIAL_CONSOLE_PORT + + // Make sure the WiFi and AP drivers are always added and set enabled when loading older settings, + // or factory default settings +# if FEATURE_ETHERNET + bool fixedNetworkIndex_drivers_added = false; +# endif + + for (ESPEasy::net::networkDriverIndex_t index; ESPEasy::net::validNetworkDriverIndex(index); ++index) + { + const ESPEasy::net::NetworkDriverStruct& nw = ESPEasy::net::getNetworkDriverStruct(index); + + if (nw.alwaysPresent) { + if (validNetworkIndex(nw.fixedNetworkIndex)) { + const ESPEasy::net::nwpluginID_t nwpluginID = ESPEasy::net::getNWPluginID_from_NetworkDriverIndex(index); + + if (getNWPluginID_for_network(nw.fixedNetworkIndex) != nwpluginID) { + setNWPluginID_for_network(nw.fixedNetworkIndex, nwpluginID); +# if FEATURE_ETHERNET + fixedNetworkIndex_drivers_added = true; +# endif + setNetworkEnabled(nw.fixedNetworkIndex, nw.enabledOnFactoryReset); + } + } + } +# if FEATURE_ETHERNET + else if (fixedNetworkIndex_drivers_added) { + // Check to see if there are other network adapters configured in the settings and not setup yet + if (ETH_Phy_Type != ESPEasy::net::EthPhyType_t::notSet) { + const ESPEasy::net::nwpluginID_t nwpluginID = ESPEasy::net::getNWPluginID_from_NetworkDriverIndex(index); + + if ((nwpluginID.value == 3 && ESPEasy::net::isRMII_EthernetType(ETH_Phy_Type)) || + (nwpluginID.value == 4 && ESPEasy::net::isSPI_EthernetType(ETH_Phy_Type))) + { + const ESPEasy::net::networkIndex_t nw_index = nwpluginID.value - 1; + + if (getNWPluginID_for_network(nw_index) != nwpluginID) { + setNWPluginID_for_network(nw_index, nwpluginID); + setNetworkEnabled(nw_index, nw.enabledOnFactoryReset); + } + } + } + } #endif - #endif + } } -template +template bool SettingsStruct_tmpl::networkSettingsEmpty() const { return IP[0] == 0 && Gateway[0] == 0 && Subnet[0] == 0 && DNS[0] == 0; } -template +template void SettingsStruct_tmpl::clearNetworkSettings() { for (uint8_t i = 0; i < 4; ++i) { IP[i] = 0; @@ -577,7 +635,7 @@ void SettingsStruct_tmpl::clearNetworkSettings() { } } -template +template void SettingsStruct_tmpl::clearTimeSettings() { ExternalTimeSource = 0; ZERO_FILL(NTPHost); @@ -589,7 +647,7 @@ void SettingsStruct_tmpl::clearTimeSettings() { Longitude = 0.0f; } -template +template void SettingsStruct_tmpl::clearNotifications() { for (uint8_t i = 0; i < NOTIFICATION_MAX; ++i) { Notification[i] = 0u;// .setInvalid(); @@ -597,7 +655,7 @@ void SettingsStruct_tmpl::clearNotifications() { } } -template +template void SettingsStruct_tmpl::clearControllers() { for (controllerIndex_t i = 0; i < CONTROLLER_MAX; ++i) { Protocol[i] = 0; @@ -605,14 +663,14 @@ void SettingsStruct_tmpl::clearControllers() { } } -template +template void SettingsStruct_tmpl::clearTasks() { for (taskIndex_t task = 0; task < N_TASKS; ++task) { clearTask(task); } } -template +template void SettingsStruct_tmpl::clearLogSettings() { SyslogLevel = 0; SerialLogLevel = 0; @@ -622,14 +680,15 @@ void SettingsStruct_tmpl::clearLogSettings() { ZERO_FILL(Syslog_IP); } -template +template void SettingsStruct_tmpl::clearUnitNameSettings() { Unit = 0; + appendUnitToHostname(false); ZERO_FILL(Name); UDPPort = 0; } -template +template void SettingsStruct_tmpl::clearMisc() { PID = ESP_PROJECT_PID; Version = VERSION; @@ -686,7 +745,7 @@ void SettingsStruct_tmpl::clearMisc() { I2C_clockSpeed = DEFAULT_I2C_CLOCK_SPEED; WebserverPort = 80; SyslogPort = 514; - GlobalSync = false; + VariousBits_3._all_bits = 0; ConnectionFailuresThreshold = 0; MQTTRetainFlag_unused = false; InitSPI = DEFAULT_SPI; @@ -718,12 +777,12 @@ void SettingsStruct_tmpl::clearMisc() { #else UseESPEasyNow(false); #endif - ApDontForceSetup(DEFAULT_AP_DONT_FORCE_SETUP); - DoNotStartAP(DEFAULT_DONT_ALLOW_START_AP); + ApCaptivePortal(DEFAULT_AP_FORCE_SETUP); + DoNotStartAPfallback_ConnectFail(DEFAULT_DONT_ALLOW_START_AP); } -template +template void SettingsStruct_tmpl::clearTask(taskIndex_t task) { if (task >= N_TASKS) { return; } @@ -760,12 +819,12 @@ void SettingsStruct_tmpl::clearTask(taskIndex_t task) { I2C_Multiplexer_Channel[task] = -1; } -template +template String SettingsStruct_tmpl::getHostname() const { return this->getHostname(this->appendUnitToHostname()); } -template +template String SettingsStruct_tmpl::getHostname(bool appendUnit) const { String hostname = this->getName(); @@ -776,14 +835,14 @@ String SettingsStruct_tmpl::getHostname(bool appendUnit) const { return hostname; } -template +template String SettingsStruct_tmpl::getName() const { String unitname = this->Name; return parseTemplate(unitname); } -template +template bool SettingsStruct_tmpl::getPinBootStateIndex( int8_t gpio_pin, int8_t& index_low @@ -807,7 +866,7 @@ bool SettingsStruct_tmpl::getPinBootStateIndex( index_high = gpio_pin - maxStates; -# if defined(ESP32_CLASSIC) || defined(ESP32C2) || defined(ESP32C3)|| defined(ESP32C6) +# if defined(ESP32_CLASSIC) || defined(ESP32C2) || defined(ESP32C3) || defined(ESP32C5) || defined(ESP32C5) || defined(ESP32C6) || defined(ESP32C61) // These can all store in the PinBootStates_ESP32 array return (index_high < maxStatesesp32); @@ -847,6 +906,22 @@ bool SettingsStruct_tmpl::getPinBootStateIndex( index_high = gpio_pin - maxStates + 22 - 33; return true; } + +# elif defined(ESP32P4) + + // GPIO 27 ... 39 should never be used. + // Thus: + // - map ... <27> to the beginning of PinBootStates_ESP32 + // - map <40> ... <54> to the end of PinBootStates_ESP32 + if (gpio_pin < 27) { + return true; + } + + if (gpio_pin >= 40) { + index_high = gpio_pin - maxStates + 27 - 39; + return true; + } + # else // if defined(ESP32_CLASSIC) || defined(ESP32C3) static_assert(false, "Implement processor architecture"); @@ -857,7 +932,7 @@ bool SettingsStruct_tmpl::getPinBootStateIndex( return false; } -template +template PinBootState SettingsStruct_tmpl::getPinBootState(int8_t gpio_pin) const { if (gpio_pin < 0) return PinBootState::Default_state; # ifdef ESP8266 @@ -885,7 +960,7 @@ PinBootState SettingsStruct_tmpl::getPinBootState(int8_t gpio_pin) cons return PinBootState::Default_state; } -template +template void SettingsStruct_tmpl::setPinBootState(int8_t gpio_pin, PinBootState state) { if (gpio_pin < 0) return; # ifdef ESP8266 @@ -912,7 +987,7 @@ void SettingsStruct_tmpl::setPinBootState(int8_t gpio_pin, PinBootState # endif // ifdef ESP32 } -template +template bool SettingsStruct_tmpl::getSPI_pins(int8_t spi_gpios[3]) const { spi_gpios[0] = -1; spi_gpios[1] = -1; @@ -959,7 +1034,7 @@ bool SettingsStruct_tmpl::getSPI_pins(int8_t spi_gpios[3]) const { } #ifdef ESP32 -template +template spi_host_device_t SettingsStruct_tmpl::getSPI_host() const { if (isSPI_valid()) { @@ -1005,7 +1080,7 @@ spi_host_device_t SettingsStruct_tmpl::getSPI_host() const #endif -template +template bool SettingsStruct_tmpl::isSPI_pin(int8_t pin) const { if (pin < 0) { return false; } int8_t spi_gpios[3]; @@ -1018,7 +1093,7 @@ bool SettingsStruct_tmpl::isSPI_pin(int8_t pin) const { return false; } -template +template bool SettingsStruct_tmpl::isSPI_valid() const { if (InitSPI == static_cast(SPI_Options_e::None)) { return false; } @@ -1033,7 +1108,7 @@ bool SettingsStruct_tmpl::isSPI_valid() const { return true; } -template +template bool SettingsStruct_tmpl::isI2C_pin(int8_t pin) const { if (pin < 0) { return false; } #if !FEATURE_I2C_MULTIPLE @@ -1049,7 +1124,7 @@ bool SettingsStruct_tmpl::isI2C_pin(int8_t pin) const { return false; } -template +template bool SettingsStruct_tmpl::isI2CEnabled(uint8_t i2cBus) const { return (getI2CSdaPin(i2cBus) != -1) && (getI2CSclPin(i2cBus) != -1) && @@ -1057,12 +1132,12 @@ bool SettingsStruct_tmpl::isI2CEnabled(uint8_t i2cBus) const { (getI2CClockSpeedSlow(i2cBus) > 0); } -template +template uint8_t SettingsStruct_tmpl::getI2CInterface(taskIndex_t TaskIndex) const { return get3BitFromUL(I2C_Flags[TaskIndex], I2C_FLAGS_BUS_NUMBER); } -template +template int8_t SettingsStruct_tmpl::getI2CSdaPin(uint8_t i2cBus) const { if (0 == i2cBus) { return Pin_i2c_sda; @@ -1078,7 +1153,7 @@ int8_t SettingsStruct_tmpl::getI2CSdaPin(uint8_t i2cBus) const { return -1; } -template +template int8_t SettingsStruct_tmpl::getI2CSclPin(uint8_t i2cBus) const { if (0 == i2cBus) { return Pin_i2c_scl; @@ -1094,7 +1169,7 @@ int8_t SettingsStruct_tmpl::getI2CSclPin(uint8_t i2cBus) const { return -1; } -template +template uint32_t SettingsStruct_tmpl::getI2CClockSpeed(uint8_t i2cBus) const { if (0 == i2cBus) { return I2C_clockSpeed; @@ -1110,7 +1185,7 @@ uint32_t SettingsStruct_tmpl::getI2CClockSpeed(uint8_t i2cBus) const { return 0u; } -template +template uint32_t SettingsStruct_tmpl::getI2CClockSpeedSlow(uint8_t i2cBus) const { if (0 == i2cBus) { return I2C_clockSpeed_Slow; @@ -1126,7 +1201,7 @@ uint32_t SettingsStruct_tmpl::getI2CClockSpeedSlow(uint8_t i2cBus) cons return 0u; } -template +template uint32_t SettingsStruct_tmpl::getI2CClockStretch(uint8_t i2cBus) const { if (0 == i2cBus) { return WireClockStretchLimit; @@ -1135,24 +1210,24 @@ uint32_t SettingsStruct_tmpl::getI2CClockStretch(uint8_t i2cBus) const } #if FEATURE_I2C_MULTIPLE -template +template uint8_t SettingsStruct_tmpl::getI2CInterfaceRTC() const { return get3BitFromUL(I2C_peripheral_bus, I2C_PERIPHERAL_BUS_CLOCK); } -template +template uint8_t SettingsStruct_tmpl::getI2CInterfaceWDT() const { return get3BitFromUL(I2C_peripheral_bus, I2C_PERIPHERAL_BUS_WDT); } -template +template uint8_t SettingsStruct_tmpl::getI2CInterfacePCFMCP() const { return get3BitFromUL(I2C_peripheral_bus, I2C_PERIPHERAL_BUS_PCFMCP); } #endif // if FEATURE_I2C_MULTIPLE #if FEATURE_I2CMULTIPLEXER -template +template int8_t SettingsStruct_tmpl::getI2CMultiplexerType(uint8_t i2cBus) const { if (0 == i2cBus) { return I2C_Multiplexer_Type; @@ -1168,7 +1243,7 @@ int8_t SettingsStruct_tmpl::getI2CMultiplexerType(uint8_t i2cBus) const return -1; } -template +template int8_t SettingsStruct_tmpl::getI2CMultiplexerAddr(uint8_t i2cBus) const { if (0 == i2cBus) { return I2C_Multiplexer_Addr; @@ -1184,7 +1259,7 @@ int8_t SettingsStruct_tmpl::getI2CMultiplexerAddr(uint8_t i2cBus) const return -1; } -template +template int8_t SettingsStruct_tmpl::getI2CMultiplexerResetPin(uint8_t i2cBus) const { if (0 == i2cBus) { return I2C_Multiplexer_ResetPin; @@ -1201,30 +1276,53 @@ int8_t SettingsStruct_tmpl::getI2CMultiplexerResetPin(uint8_t i2cBus) c } #endif // if FEATURE_I2CMULTIPLEXER -template +template bool SettingsStruct_tmpl::isEthernetPin(int8_t pin) const { #if FEATURE_ETHERNET if (pin < 0) return false; - if (NetworkMedium == NetworkMedium_t::Ethernet && - !isSPI_EthernetType(ETH_Phy_Type)) { + if (NetworkMedium == ESPEasy::net::NetworkMedium_t::Ethernet && + !ESPEasy::net::isSPI_EthernetType(ETH_Phy_Type)) { + #if CONFIG_ETH_USE_ESP32_EMAC && FEATURE_ETHERNET + #ifdef ESP32P4 + switch (pin) { + // MDC/MDIO/Power can be configured by user + //case ETH_PHY_MDC : + //case ETH_PHY_MDIO : + //case ETH_PHY_POWER : + case ETH_RMII_TX_EN : + case ETH_RMII_TX0 : + case ETH_RMII_TX1 : + case ETH_RMII_RX0 : + case ETH_RMII_RX1_EN : + case ETH_RMII_CRS_DV : + case ETH_RMII_CLK : + return true; + } + #else if (19 == pin) return true; // ETH TXD0 if (21 == pin) return true; // ETH TX EN if (22 == pin) return true; // ETH TXD1 if (25 == pin) return true; // ETH RXD0 if (26 == pin) return true; // ETH RXD1 if (27 == pin) return true; // ETH CRS_DV + #endif + #endif } #endif // if FEATURE_ETHERNET return false; } -template +template bool SettingsStruct_tmpl::isEthernetPinOptional(int8_t pin) const { #if FEATURE_ETHERNET if (pin < 0) return false; - if (NetworkMedium == NetworkMedium_t::Ethernet) { - if (!isSPI_EthernetType(ETH_Phy_Type) && isGpioUsedInETHClockMode(ETH_Clock_Mode, pin)) return true; + if (NetworkMedium == ESPEasy::net::NetworkMedium_t::Ethernet) { + if (!ESPEasy::net::isSPI_EthernetType(ETH_Phy_Type) +# if CONFIG_ETH_USE_ESP32_EMAC && FEATURE_ETHERNET + && ESPEasy::net::isGpioUsedInETHClockMode(ETH_Clock_Mode, pin) + #endif + ) return true; if (ETH_Pin_mdc_cs == pin) return true; if (ETH_Pin_mdio_irq == pin) return true; if (ETH_Pin_power_rst == pin) return true; @@ -1233,7 +1331,7 @@ bool SettingsStruct_tmpl::isEthernetPinOptional(int8_t pin) const { return false; } -template +template int8_t SettingsStruct_tmpl::getTaskDevicePin(taskIndex_t taskIndex, uint8_t pinnr) const { if (validTaskIndex(taskIndex)) { switch(pinnr) { @@ -1245,17 +1343,17 @@ int8_t SettingsStruct_tmpl::getTaskDevicePin(taskIndex_t taskIndex, uin return -1; } -template +template float SettingsStruct_tmpl::getWiFi_TX_power() const { return WiFi_TX_power / 4.0f; } -template +template void SettingsStruct_tmpl::setWiFi_TX_power(float dBm) { WiFi_TX_power = dBm * 4.0f; } -template +template pluginID_t SettingsStruct_tmpl::getPluginID_for_task(taskIndex_t taskIndex) const { if (validTaskIndex(taskIndex)) { const uint8_t tdn = TaskDeviceNumber[taskIndex]; @@ -1266,4 +1364,117 @@ pluginID_t SettingsStruct_tmpl::getPluginID_for_task(taskIndex_t taskIn return INVALID_PLUGIN_ID; } +template +ESPEasy::net::nwpluginID_t SettingsStruct_tmpl::getNWPluginID_for_network(ESPEasy::net::networkIndex_t index) const +{ + if (validNetworkIndex(index)) { + const uint8_t nwa = NWPluginID[index]; + if (nwa > 0) { + return ESPEasy::net::nwpluginID_t::toPluginID(nwa); + } + } + return ESPEasy::net::INVALID_NW_PLUGIN_ID; +} + +template +void SettingsStruct_tmpl::setNWPluginID_for_network(ESPEasy::net::networkIndex_t index, ESPEasy::net::nwpluginID_t id) +{ + if (validNetworkIndex(index)) { + NWPluginID[index] = id.value; + if (id.isValid()) { + ESPEasy::net::networkDriverIndex_t NetworkDriverIndex = + ESPEasy::net::getNetworkDriverIndex_from_NetworkIndex(index); + + if (ESPEasy::net::validNetworkDriverIndex(NetworkDriverIndex)) { + struct EventStruct TempEvent; + TempEvent.NetworkIndex = index; + + String dummy; + ESPEasy::net::NWPluginCall(NWPlugin::Function::NWPLUGIN_LOAD_DEFAULTS, &TempEvent, dummy); + } + } + } +} + +template +bool SettingsStruct_tmpl::getNetworkEnabled(ESPEasy::net::networkIndex_t index) const { + //if (index == 1) return true; + if (validNetworkIndex(index)) return bitRead(NetworkEnabled_bits, index); + return false; +} + +template +void SettingsStruct_tmpl::setNetworkEnabled(ESPEasy::net::networkIndex_t index, bool enabled) { + if (validNetworkIndex(index)) { + bitWrite(NetworkEnabled_bits, index, enabled); + } +} + +template +bool SettingsStruct_tmpl::getNetworkInterfaceSubnetBlockClientIP(ESPEasy::net::networkIndex_t index) const { + if (validNetworkIndex(index)) return bitRead(NetworkInterfaceSubnetBlockClientIP_bits, index); + return false; +} + +template +void SettingsStruct_tmpl::setNetworkInterfaceSubnetBlockClientIP(ESPEasy::net::networkIndex_t index, bool enabled) { + if (validNetworkIndex(index)) { + bitWrite(NetworkInterfaceSubnetBlockClientIP_bits, index, enabled); + } +} + +#if FEATURE_USE_IPV6 +template +bool SettingsStruct_tmpl::getNetworkEnabled_IPv6(ESPEasy::net::networkIndex_t index) const { + //if (index == 1) return true; + if (validNetworkIndex(index)) return bitRead(NetworkEnabled_ipv6_bits, index); + return false; +} + +template +void SettingsStruct_tmpl::setNetworkEnabled_IPv6(ESPEasy::net::networkIndex_t index, bool enabled) { + if (validNetworkIndex(index)) { + bitWrite(NetworkEnabled_ipv6_bits, index, enabled); + } +} +#endif + + +#ifdef ESP32 +template +uint8_t SettingsStruct_tmpl::getRoutePrio_for_network(ESPEasy::net::networkIndex_t index) const +{ + if (validNetworkIndex(index)) { + return NetworkRoutePrio[index]; + } + return 0; +} + +template +void SettingsStruct_tmpl::setRoutePrio_for_network(ESPEasy::net::networkIndex_t index, uint8_t prio) +{ + if (validNetworkIndex(index)) { + NetworkRoutePrio[index] = prio; + } +} +#endif + +template +uint32_t SettingsStruct_tmpl::getNetworkInterfaceStartupDelayAtBoot(ESPEasy::net::networkIndex_t index) const +{ + if (validNetworkIndex(index)) { + return static_cast(NetworkInterfaceStartupDelayAtBoot[index]) * 10ul; + } + return 0; +} + +template +void SettingsStruct_tmpl::setNetworkInterfaceStartupDelayAtBoot(ESPEasy::net::networkIndex_t index, uint32_t delay_ms) +{ + if (validNetworkIndex(index)) { + NetworkInterfaceStartupDelayAtBoot[index] = delay_ms/10ul; + } +} + + #endif // ifndef DATASTRUCTS_SETTINGSSTRUCT_CPP diff --git a/src/src/DataTypes/DeviceModel.h b/src/src/DataTypes/DeviceModel.h index 3af8f1f45..fc67ff4c2 100644 --- a/src/src/DataTypes/DeviceModel.h +++ b/src/src/DataTypes/DeviceModel.h @@ -21,7 +21,7 @@ enum class DeviceModel : uint8_t { DeviceModel_Sonoff_POWr2, DeviceModel_Shelly1, DeviceModel_ShellyPLUG_S, -# if CONFIG_ETH_USE_ESP32_EMAC +# if CONFIG_ETH_USE_ESP32_EMAC && FEATURE_ETHERNET DeviceModel_Olimex_ESP32_PoE, DeviceModel_Olimex_ESP32_EVB, DeviceModel_Olimex_ESP32_GATEWAY, diff --git a/src/src/DataTypes/ESPEasyFileType.cpp b/src/src/DataTypes/ESPEasyFileType.cpp index a8b3bf4bc..f6e4f26a4 100644 --- a/src/src/DataTypes/ESPEasyFileType.cpp +++ b/src/src/DataTypes/ESPEasyFileType.cpp @@ -38,6 +38,9 @@ bool isProtectedFileType(const String& filename) matchFileType(filename, FileType::CONFIG_DAT) || matchFileType(filename, FileType::SECURITY_DAT) || matchFileType(filename, FileType::NOTIFICATION_DAT) || +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + matchFileType(filename, FileType::DEV_SECURITY_DAT) || +#endif matchFileType(filename, FileType::PROVISIONING_DAT); } @@ -48,6 +51,9 @@ const __FlashStringHelper* getFileName(FileType::Enum filetype) { case FileType::NOTIFICATION_DAT: return F("notification.dat"); case FileType::SECURITY_DAT: return F("security.dat"); case FileType::PROVISIONING_DAT: return F("provisioning.dat"); +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + case FileType::DEV_SECURITY_DAT: return F("devsecurity.dat"); +#endif case FileType::RULES_TXT: // Use getRulesFileName break; @@ -84,6 +90,9 @@ bool getDownloadFiletypeChecked(FileType::Enum filetype, unsigned int filenr) { case FileType::NOTIFICATION_DAT: return ResetFactoryDefaultPreference.fetchNotificationDat(); case FileType::RULES_TXT: return ResetFactoryDefaultPreference.fetchRulesTXT(filenr); case FileType::PROVISIONING_DAT: return ResetFactoryDefaultPreference.fetchProvisioningDat(); +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + case FileType::DEV_SECURITY_DAT: return ResetFactoryDefaultPreference.fetchDeviceSecurityDat(); +#endif break; case FileType::MAX_FILETYPE: diff --git a/src/src/DataTypes/ESPEasyFileType.h b/src/src/DataTypes/ESPEasyFileType.h index 48dbf5567..aaec346df 100644 --- a/src/src/DataTypes/ESPEasyFileType.h +++ b/src/src/DataTypes/ESPEasyFileType.h @@ -10,7 +10,9 @@ struct FileType { RULES_TXT, NOTIFICATION_DAT, PROVISIONING_DAT, - +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + DEV_SECURITY_DAT, +#endif MAX_FILETYPE }; }; diff --git a/src/src/DataTypes/ESPEasy_key_value_store_4byte_data.cpp b/src/src/DataTypes/ESPEasy_key_value_store_4byte_data.cpp new file mode 100644 index 000000000..e3d6337af --- /dev/null +++ b/src/src/DataTypes/ESPEasy_key_value_store_4byte_data.cpp @@ -0,0 +1,66 @@ +#include "../DataTypes/ESPEasy_key_value_store_data.h" + +#if FEATURE_ESPEASY_KEY_VALUE_STORE + +constexpr unsigned int size_32bit = sizeof(float); + +ESPEasy_key_value_store_4byte_data_t::ESPEasy_key_value_store_4byte_data_t() { + ZERO_FILL(binary); +} + +ESPEasy_key_value_store_4byte_data_t::ESPEasy_key_value_store_4byte_data_t(const ESPEasy_key_value_store_4byte_data_t& other) +{ + memcpy(binary, other.binary, sizeof(binary)); +} + +ESPEasy_key_value_store_4byte_data_t::ESPEasy_key_value_store_4byte_data_t(const uint8_t*data) +{ + memcpy(binary, data, sizeof(binary)); +} + +ESPEasy_key_value_store_4byte_data_t& ESPEasy_key_value_store_4byte_data_t::operator=(const ESPEasy_key_value_store_4byte_data_t& other) +{ + memcpy(binary, other.binary, sizeof(binary)); + return *this; +} + +void ESPEasy_key_value_store_4byte_data_t::clear() { ZERO_FILL(binary); } + +int32_t ESPEasy_key_value_store_4byte_data_t::getInt32() const +{ + int32_t res{}; + + memcpy(&res, binary, size_32bit); + return res; +} + +bool ESPEasy_key_value_store_4byte_data_t::setInt32(int32_t value) { return set(&value); } + +uint32_t ESPEasy_key_value_store_4byte_data_t::getUint32() const +{ + uint32_t res{}; + + memcpy(&res, binary, size_32bit); + return res; +} + +bool ESPEasy_key_value_store_4byte_data_t::setUint32(uint32_t value) { return set(&value); } + +float ESPEasy_key_value_store_4byte_data_t::getFloat() const +{ + float res{}; + + memcpy(&res, binary, size_32bit); + return res; +} + +bool ESPEasy_key_value_store_4byte_data_t::setFloat(float value) { return set(&value); } + +bool ESPEasy_key_value_store_4byte_data_t::set(const void*value) +{ + if (memcmp(binary, value, size_32bit) == 0) { return false; } + memcpy(binary, value, size_32bit); + return true; +} + +#endif // if FEATURE_ESPEASY_KEY_VALUE_STORE diff --git a/src/src/DataTypes/ESPEasy_key_value_store_8byte_data.cpp b/src/src/DataTypes/ESPEasy_key_value_store_8byte_data.cpp new file mode 100644 index 000000000..68650472f --- /dev/null +++ b/src/src/DataTypes/ESPEasy_key_value_store_8byte_data.cpp @@ -0,0 +1,66 @@ +#include "../DataTypes/ESPEasy_key_value_store_data.h" + +#if FEATURE_ESPEASY_KEY_VALUE_STORE + +constexpr unsigned int size_64bit = sizeof(double); + +ESPEasy_key_value_store_8byte_data_t::ESPEasy_key_value_store_8byte_data_t() { + ZERO_FILL(binary); +} + +ESPEasy_key_value_store_8byte_data_t::ESPEasy_key_value_store_8byte_data_t(const ESPEasy_key_value_store_8byte_data_t& other) +{ + memcpy(binary, other.binary, sizeof(binary)); +} + +ESPEasy_key_value_store_8byte_data_t::ESPEasy_key_value_store_8byte_data_t(const uint8_t*data) +{ + memcpy(binary, data, sizeof(binary)); +} + +ESPEasy_key_value_store_8byte_data_t& ESPEasy_key_value_store_8byte_data_t::operator=(const ESPEasy_key_value_store_8byte_data_t& other) +{ + memcpy(binary, other.binary, sizeof(binary)); + return *this; +} + +void ESPEasy_key_value_store_8byte_data_t::clear() { ZERO_FILL(binary); } + +int64_t ESPEasy_key_value_store_8byte_data_t::getInt64() const +{ + int64_t res{}; + + memcpy(&res, binary, size_64bit); + return res; +} + +bool ESPEasy_key_value_store_8byte_data_t::setInt64(int64_t value) { return set(&value); } + +uint64_t ESPEasy_key_value_store_8byte_data_t::getUint64() const +{ + uint64_t res{}; + + memcpy(&res, binary, size_64bit); + return res; +} + +bool ESPEasy_key_value_store_8byte_data_t::setUint64(uint64_t value) { return set(&value); } + +double ESPEasy_key_value_store_8byte_data_t::getDouble() const +{ + double res{}; + + memcpy(&res, binary, size_64bit); + return res; +} + +bool ESPEasy_key_value_store_8byte_data_t::setDouble(double value) { return set(&value); } + +bool ESPEasy_key_value_store_8byte_data_t::set(const void*value) +{ + if (memcmp(binary, value, size_64bit) == 0) { return false; } + memcpy(binary, value, size_64bit); + return true; +} + +#endif // if FEATURE_ESPEASY_KEY_VALUE_STORE diff --git a/src/src/DataTypes/ESPEasy_key_value_store_StorageType.cpp b/src/src/DataTypes/ESPEasy_key_value_store_StorageType.cpp new file mode 100644 index 000000000..f3794be43 --- /dev/null +++ b/src/src/DataTypes/ESPEasy_key_value_store_StorageType.cpp @@ -0,0 +1,52 @@ +#include "../DataTypes/ESPEasy_key_value_store_StorageType.h" +#if FEATURE_ESPEASY_KEY_VALUE_STORE + +uint32_t KVS_StorageType::combine_StorageType_and_key( + KVS_StorageType::Enum storageType, + uint32_t key) +{ + uint32_t res = key & ((1 << 24) - 1); + + res |= (static_cast(storageType) << 24); + return res; +} + +KVS_StorageType::Enum KVS_StorageType::get_StorageType_from_combined_key(uint32_t combined_key) +{ + return static_cast(combined_key >> 24); +} + +uint32_t KVS_StorageType::getKey_from_combined_key(uint32_t combined_key) +{ + const uint32_t res = combined_key & ((1 << 24) - 1); + + return res; +} + +size_t KVS_StorageType::getStorageSizePerType(KVS_StorageType::Enum storageType) +{ + switch (storageType) + { + case KVS_StorageType::Enum::not_set: break; + case KVS_StorageType::Enum::string_type: break; + case KVS_StorageType::Enum::int8_type: return 4 + 1; + case KVS_StorageType::Enum::uint8_type: return 4 + 1; + case KVS_StorageType::Enum::int16_type: return 4 + 2; + case KVS_StorageType::Enum::uint16_type: return 4 + 2; + case KVS_StorageType::Enum::int32_type: return 4 + 4; + case KVS_StorageType::Enum::uint32_type: return 4 + 4; + case KVS_StorageType::Enum::int64_type: return 4 + 8; + case KVS_StorageType::Enum::uint64_type: return 4 + 8; + case KVS_StorageType::Enum::float_type: return 4 + 4; + case KVS_StorageType::Enum::double_type: return 4 + 8; + case KVS_StorageType::Enum::bool_type: return 4 + 0; + case KVS_StorageType::Enum::MAX_Type: break; + case KVS_StorageType::Enum::bool_true: + case KVS_StorageType::Enum::bool_false: return 4 + 0; + + case KVS_StorageType::Enum::binary: break; + } + return 0; +} + +#endif // if FEATURE_ESPEASY_KEY_VALUE_STORE diff --git a/src/src/DataTypes/ESPEasy_key_value_store_StorageType.h b/src/src/DataTypes/ESPEasy_key_value_store_StorageType.h new file mode 100644 index 000000000..119e293de --- /dev/null +++ b/src/src/DataTypes/ESPEasy_key_value_store_StorageType.h @@ -0,0 +1,50 @@ +#pragma once + +#include "../../ESPEasy_common.h" +#if FEATURE_ESPEASY_KEY_VALUE_STORE + +class KVS_StorageType +{ + +public: + + // Type is stored, so do not change the order/values + enum class Enum : uint8_t { + not_set = 0, + string_type = 1, + int8_type = 2, + uint8_type = 3, + int16_type = 4, + uint16_type = 5, + int32_type = 6, + uint32_type = 7, + int64_type = 8, + uint64_type = 9, + float_type = 10, + double_type = 11, + bool_type = 12, + + MAX_Type, // Leave this one after the generic type and before 'special' types + + + bool_true = 126, // small optimization to store 'true' as extra type + bool_false = 127, // small optimization to store 'false' as extra type + binary = 255 + + }; + + static uint32_t combine_StorageType_and_key( + KVS_StorageType::Enum storageType, + uint32_t key); + + static KVS_StorageType::Enum get_StorageType_from_combined_key( + uint32_t combined_key); + + static uint32_t getKey_from_combined_key(uint32_t combined_key); + + static size_t getStorageSizePerType(KVS_StorageType::Enum storageType); + + +}; // class KVS_StorageType + +#endif // if FEATURE_ESPEASY_KEY_VALUE_STORE diff --git a/src/src/DataTypes/ESPEasy_key_value_store_data.h b/src/src/DataTypes/ESPEasy_key_value_store_data.h new file mode 100644 index 000000000..3252e5337 --- /dev/null +++ b/src/src/DataTypes/ESPEasy_key_value_store_data.h @@ -0,0 +1,96 @@ +#ifndef DATATYPES_ESPEASY_KEY_VALUE_STORE_DATA_H +#define DATATYPES_ESPEASY_KEY_VALUE_STORE_DATA_H + +#include "../../ESPEasy_common.h" + +#if FEATURE_ESPEASY_KEY_VALUE_STORE + +// Store various types of 32 bit +class alignas(uint32_t) ESPEasy_key_value_store_4byte_data_t +{ +public: + + ESPEasy_key_value_store_4byte_data_t(); + + ESPEasy_key_value_store_4byte_data_t(const ESPEasy_key_value_store_4byte_data_t& other); + + ESPEasy_key_value_store_4byte_data_t(const uint8_t*data); + + ESPEasy_key_value_store_4byte_data_t& operator=(const ESPEasy_key_value_store_4byte_data_t& other); + + void clear(); + + + int32_t getInt32() const; + + // Return true when content has changed + bool setInt32(int32_t value); + + uint32_t getUint32() const; + + // Return true when content has changed + bool setUint32(uint32_t value); + + float getFloat() const; + + // Return true when content has changed + bool setFloat(float value); + + uint8_t* getBinary() { return binary; } + + const uint8_t* getBinary() const { return binary; } + + // Return true when content has changed + bool set(const void*value); + +private: + + uint8_t binary[sizeof(float)]{}; +}; // class alignas + +// Store various types of 64 bit +class alignas(uint32_t) ESPEasy_key_value_store_8byte_data_t +{ +public: + + ESPEasy_key_value_store_8byte_data_t(); + + ESPEasy_key_value_store_8byte_data_t(const ESPEasy_key_value_store_8byte_data_t& other); + + ESPEasy_key_value_store_8byte_data_t(const uint8_t*data); + + ESPEasy_key_value_store_8byte_data_t& operator=(const ESPEasy_key_value_store_8byte_data_t& other); + + void clear(); + + + int64_t getInt64() const; + + // Return true when content has changed + bool setInt64(int64_t value); + + uint64_t getUint64() const; + + // Return true when content has changed + bool setUint64(uint64_t value); + + double getDouble() const; + + // Return true when content has changed + bool setDouble(double value); + + uint8_t* getBinary() { return binary; } + + const uint8_t* getBinary() const { return binary; } + + + // Return true when content has changed + bool set(const void*value); + +private: + + uint8_t binary[sizeof(double)]{}; +}; // class alignas + +#endif // if FEATURE_ESPEASY_KEY_VALUE_STORE +#endif // ifndef DATATYPES_ESPEASY_KEY_VALUE_STORE_DATA_H diff --git a/src/src/DataTypes/ESPEasy_plugin_functions.cpp b/src/src/DataTypes/ESPEasy_plugin_functions.cpp new file mode 100644 index 000000000..58948aa81 --- /dev/null +++ b/src/src/DataTypes/ESPEasy_plugin_functions.cpp @@ -0,0 +1,335 @@ +#include "../DataTypes/ESPEasy_plugin_functions.h" + +#ifdef ESP32 + +# include "../Helpers/StringConverter.h" + +# include + +bool NWPlugin::canQueryViaNetworkInterface(NWPlugin::Function function) +{ + switch (function) + { + // TD-er: Do not try to fetch hostname via base class. No idea why, but it doesn't work well + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_ACTIVE: + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_ROUTE_PRIO: + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_HOSTNAME: + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_NAME: + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_HW_ADDRESS: + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_IP: + case NWPlugin::Function::NWPLUGIN_CLIENT_IP_WEB_ACCESS_ALLOWED: + + return true; + default: break; + } + return false; +} + +const __FlashStringHelper * NWPlugin::toString(ConnectionState connectionState, bool asUnicodeSymbol) +{ + if (asUnicodeSymbol) { + switch (connectionState) + { + case NWPlugin::ConnectionState::Disabled: return F("❌"); // Same icon as used everywhere else in ESPEasy to + // indicate disabled + case NWPlugin::ConnectionState::Error: return F("⚠"); // Warning Emoji + case NWPlugin::ConnectionState::Initializing: return F("⏳"); // Hourglass Not Done Emoji + case NWPlugin::ConnectionState::Connecting: return F("⏶⏳"); // LinkUp & Initializing + case NWPlugin::ConnectionState::LinkDown: return F("🔻"); // Red Triangle Pointed Down Emoji + case NWPlugin::ConnectionState::LinkUp: return F("⏶"); // Black Medium Up-Pointing Triangle + case NWPlugin::ConnectionState::Connected: return F("🔗"); // Link Emoji + } + } + else { + switch (connectionState) + { + case NWPlugin::ConnectionState::Disabled: return F("Disabled"); + case NWPlugin::ConnectionState::Error: return F("Error"); + case NWPlugin::ConnectionState::Initializing: return F("Initializing"); + case NWPlugin::ConnectionState::Connecting: return F("Connecting"); + case NWPlugin::ConnectionState::LinkDown: return F("LinkDown"); + case NWPlugin::ConnectionState::LinkUp: return F("LinkUp"); + case NWPlugin::ConnectionState::Connected: return F("Connected"); + } + } + return F(""); +} + +const __FlashStringHelper * NWPlugin::toString(NWPlugin::IP_type ip_type) +{ + switch (ip_type) + { + case NWPlugin::IP_type::inet: return F("Inet"); + case NWPlugin::IP_type::network_id_cdr: return F("Network/CDR"); + case NWPlugin::IP_type::netmask: return F("Netmask"); + case NWPlugin::IP_type::broadcast: return F("Broadcast"); + case NWPlugin::IP_type::gateway: return F("Gateway"); + case NWPlugin::IP_type::dns1: return F("Dns1"); + case NWPlugin::IP_type::dns2: return F("Dns2"); +# if CONFIG_LWIP_IPV6 + case NWPlugin::IP_type::ipv6_unknown: return F("IPv6 unknown"); + case NWPlugin::IP_type::ipv6_global: return F("IPv6 global"); + case NWPlugin::IP_type::ipv6_link_local: return F("IPv6 link local"); + case NWPlugin::IP_type::ipv6_site_local: return F("IPv6 site local"); + case NWPlugin::IP_type::ipv6_unique_local: return F("IPv6 unique local"); + case NWPlugin::IP_type::ipv4_mapped_ipv6: return F("IPv4 mapped IPv6"); +# endif // if CONFIG_LWIP_IPV6 + } + return F("unknown"); +} + +bool NWPlugin::print_IP_address(NWPlugin::IP_type ip_type, NetworkInterface*networkInterface, Print& out) +{ + const IPAddress ip(get_IP_address(ip_type, networkInterface)); + const size_t nrBytes = ip.printTo(out, true) > 0; + + if (ip.type() == IPv4) { + const uint32_t val(ip); + + if (val == 0) { return false; } + } + + if (ip_type == NWPlugin::IP_type::network_id_cdr) { + out.print('/'); + out.print(networkInterface->subnetCIDR()); + } + return nrBytes > 0; +} + +IPAddress NWPlugin::get_IP_address(NWPlugin::IP_type ip_type, NetworkInterface*networkInterface) +{ + IPAddress ip; + + if (networkInterface) { +# if CONFIG_LWIP_IPV6 + esp_ip6_addr_type_t ip6_addr_type = ESP_IP6_ADDR_IS_UNKNOWN; +# endif + + switch (ip_type) + { + case NWPlugin::IP_type::inet: return networkInterface->localIP(); + case NWPlugin::IP_type::network_id_cdr: return networkInterface->networkID(); + case NWPlugin::IP_type::netmask: return networkInterface->subnetMask(); + case NWPlugin::IP_type::broadcast: return networkInterface->broadcastIP(); + case NWPlugin::IP_type::gateway: return networkInterface->gatewayIP(); + case NWPlugin::IP_type::dns1: return networkInterface->dnsIP(0); + case NWPlugin::IP_type::dns2: return networkInterface->dnsIP(1); +# if CONFIG_LWIP_IPV6 + case NWPlugin::IP_type::ipv6_unknown: ip6_addr_type = ESP_IP6_ADDR_IS_UNKNOWN; + break; + case NWPlugin::IP_type::ipv6_global: ip6_addr_type = ESP_IP6_ADDR_IS_GLOBAL; + break; + case NWPlugin::IP_type::ipv6_link_local: ip6_addr_type = ESP_IP6_ADDR_IS_LINK_LOCAL; + break; + case NWPlugin::IP_type::ipv6_site_local: ip6_addr_type = ESP_IP6_ADDR_IS_SITE_LOCAL; + break; + case NWPlugin::IP_type::ipv6_unique_local: ip6_addr_type = ESP_IP6_ADDR_IS_UNIQUE_LOCAL; + break; + case NWPlugin::IP_type::ipv4_mapped_ipv6: ip6_addr_type = ESP_IP6_ADDR_IS_IPV4_MAPPED_IPV6; + break; +# endif // if CONFIG_LWIP_IPV6 + default: + return ip; + } +# if CONFIG_LWIP_IPV6 + + if (networkInterface->netif()) { + esp_ip6_addr_t if_ip6[CONFIG_LWIP_IPV6_NUM_ADDRESSES]; + int v6addrs = esp_netif_get_all_ip6(networkInterface->netif(), if_ip6); + + for (int i = 0; i < v6addrs; ++i) { + if (esp_netif_ip6_get_addr_type(&if_ip6[i]) == ip6_addr_type) { + return IPAddress(IPv6, (const uint8_t *)if_ip6[i].addr, if_ip6[i].zone); + } + } + } +# endif // if CONFIG_LWIP_IPV6 + } + + return ip; +} + +bool NWPlugin::get_subnet(NWPlugin::IP_type ip_type, NetworkInterface*networkInterface, IPAddress& networkID, IPAddress& broadcast) +{ + if (!networkInterface) { return false; } + IPAddress ip = get_IP_address(ip_type, networkInterface); + + if (ip == INADDR_NONE) { return false; } + +# if CONFIG_LWIP_IPV6 + + if (ip.type() == IPv6) { + // FIXME TD-er: For now, just assume /64 subnets until we have actually access to some subnet info. + networkID = ip; + broadcast = ip; + + uint8_t i = 8; + + if ((ip[0] == 0xFD) || (ip[0] == 0xFE)) { + // /8 subnet for local IPv6 + // 0xFD: Unique Local + // 0xFE: Link Local + i = 1; + } + + for (; i < 16; ++i) { + networkID[i] = 0; + broadcast[i] = 0xFF; + } + return true; + } +# endif // if CONFIG_LWIP_IPV6 + + networkID = networkInterface->networkID(); + broadcast = networkInterface->broadcastIP(); + return true; +} +#endif + +bool NWPlugin::ipLessEqual(const IPAddress& ip, const IPAddress& high) +{ + // FIXME TD-er: Must check whether both are of same type and check full range IPv6 + int nrOctets = 4; + + # if FEATURE_USE_IPV6 + + if (ip.type() != high.type()) { return false; } + + if (ip.type() == IPv6) { + nrOctets = 16; + } + # endif // if FEATURE_USE_IPV6 + + for (int i = 0; i < nrOctets; ++i) { + if (ip[i] != high[i]) { + return ip[i] < high[i]; + } + } + + // Is equal + return true; +} + +bool NWPlugin::ipInRange(const IPAddress& ip, const IPAddress& low, const IPAddress& high) { + return ipLessEqual(low, ip) && ipLessEqual(ip, high); +} + +#ifdef ESP32 +bool NWPlugin::IP_in_subnet(const IPAddress & ip, + NetworkInterface *networkInterface) +{ + const NWPlugin::IP_type ip_type = get_IP_type(ip); + IPAddress networkID; + IPAddress broadcast; + + if (!get_subnet(ip_type, networkInterface, networkID, broadcast)) { + return false; + } +#if FEATURE_USE_IPV6 + if (ip_type == NWPlugin::IP_type::ipv6_link_local) { + // Must match zone, or else it will always match. + return (ip.zone() == networkID.zone()); + } +#endif + + return ipLessEqual(networkID, ip) && ipLessEqual(ip, broadcast); +} + +NWPlugin::IP_type NWPlugin::get_IP_type(const IPAddress& ip) +{ +# if FEATURE_USE_IPV6 + + if (ip.type() == IPv6) { + switch (ip.addr_type()) + { + case ESP_IP6_ADDR_IS_UNKNOWN: + { + return NWPlugin::IP_type::ipv6_unknown; + } + case ESP_IP6_ADDR_IS_GLOBAL: + { + return NWPlugin::IP_type::ipv6_global; + } + case ESP_IP6_ADDR_IS_LINK_LOCAL: + { + return NWPlugin::IP_type::ipv6_link_local; + } + case ESP_IP6_ADDR_IS_SITE_LOCAL: + { + return NWPlugin::IP_type::ipv6_site_local; + } + case ESP_IP6_ADDR_IS_UNIQUE_LOCAL: + { + return NWPlugin::IP_type::ipv6_unique_local; + } + case ESP_IP6_ADDR_IS_IPV4_MAPPED_IPV6: + { + return NWPlugin::IP_type::ipv4_mapped_ipv6; + } + } + } +# endif // if FEATURE_USE_IPV6 + + // FIXME TD-er: Maybe also try to determine whether we have a netmask/broadcast/etc? + return NWPlugin::IP_type::inet; +} + +const __FlashStringHelper * NWPlugin::toString(NWPlugin::NetforkFlags flag) +{ + switch (flag) + { + case NWPlugin::NetforkFlags::DHCP_client: return F("DHCP Client"); + case NWPlugin::NetforkFlags::DHCP_server: return F("DHCP Server"); + case NWPlugin::NetforkFlags::AutoUp: return F("Auto Up"); + case NWPlugin::NetforkFlags::GratuituousArp: return F("Gratuituous Arp"); + case NWPlugin::NetforkFlags::EventIPmodified: return F("Send Event when IP Modified"); + case NWPlugin::NetforkFlags::isPPP: return F("PPP"); + case NWPlugin::NetforkFlags::isBridge: return F("Bridge"); + case NWPlugin::NetforkFlags::MLD_v6_report: return F("MLD IPv6 Report"); + case NWPlugin::NetforkFlags::IPv6_autoconf_enabled: return F("IPv6 Autoconf"); + + } + return F("unknown"); +} + +bool NWPlugin::isFlagSet(NWPlugin::NetforkFlags flag, NetworkInterface*networkInterface) +{ + if (networkInterface == nullptr) { return false; } + auto netif = networkInterface->netif(); + + if (netif == nullptr) { return false; } + const auto flags = esp_netif_get_flags(netif); + + const uint32_t mask = static_cast(flag); + return flags & mask; +} + +bool NWPlugin::forceDHCP_request(NetworkInterface*networkInterface) +{ + if (!NWPlugin::isFlagSet(NWPlugin::NetforkFlags::DHCP_client, networkInterface)) { + return false; + } + + auto netif = networkInterface->netif(); + + if (netif == nullptr) { return false; } + + const char*desc = esp_netif_get_desc(netif); + esp_err_t err = esp_netif_dhcpc_stop(netif); + + if (err != 0) { + addLog(LOG_LEVEL_ERROR, strformat(F("%s: DHCPc could not be stopped! Error: 0x%04x: %s"), desc, err, esp_err_to_name(err))); + return false; + } + addLog(LOG_LEVEL_INFO, strformat(F("%s: DHCPc stopped"), desc)); + err = esp_netif_dhcpc_start(netif); + + if ((err != ESP_OK) && (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED)) { + addLog(LOG_LEVEL_ERROR, strformat(F("%s: DHCPc could not be started! Error: 0x%04x: %s"), desc, err, esp_err_to_name(err))); + return false; + } + addLog(LOG_LEVEL_INFO, strformat(F("%s: DHCPc Client started"), desc)); + return true; +} + +#endif // ifdef ESP32 diff --git a/src/src/DataTypes/ESPEasy_plugin_functions.h b/src/src/DataTypes/ESPEasy_plugin_functions.h index f3e460438..224a02034 100644 --- a/src/src/DataTypes/ESPEasy_plugin_functions.h +++ b/src/src/DataTypes/ESPEasy_plugin_functions.h @@ -8,69 +8,79 @@ // Plugin (Task) function calls // ******************************************************************************** enum PluginFunctions_e { - PLUGIN_INIT_ALL , // Not implemented in a plugin, only called during boot - PLUGIN_INIT , // Init the task, called when task is set to enabled (also at boot) - PLUGIN_READ , // This call can yield new data (when success = true) and then send to controllers - PLUGIN_ONCE_A_SECOND , // Called once a second - PLUGIN_TEN_PER_SECOND , // Called 10x per second (typical for checking new data instead of waiting) - PLUGIN_DEVICE_ADD , // Called at boot for letting a plugin adding itself to list of available plugins/devices - PLUGIN_EVENTLIST_ADD , // Not used. - PLUGIN_WEBFORM_SAVE , // Call from web interface to save settings - PLUGIN_WEBFORM_LOAD , // Call from web interface for presenting settings and status of plugin - PLUGIN_WEBFORM_SHOW_VALUES , // Call from devices overview page to format values in HTML - PLUGIN_GET_DEVICENAME , // Call to get the plugin description (e.g. "Switch input - Switch") - PLUGIN_GET_DEVICEVALUENAMES , // Call to let the plugin generate some default value names when not defined. - PLUGIN_GET_DEVICEVALUECOUNT , // Optional function call to allow tasks to specify the number of output values (e.g. P026_Sysinfo.ino) - PLUGIN_GET_DEVICEVTYPE , // Only needed when Device[deviceCount].OutputDataType is not Output_Data_type_t::Default - PLUGIN_WRITE , // Called to allow a task to process a command. Must return success = true when it can handle the command. -// PLUGIN_EVENT_OUT , // Does not seem to be used - PLUGIN_WEBFORM_SHOW_CONFIG , // Called to show non default pin assignment or addresses like for plugins using serial or 1-Wire - PLUGIN_SERIAL_IN , // Called on received data via serial port Serial0 (N.B. this may conflict with sending commands via serial) - PLUGIN_UDP_IN , // Called for received UDP data via ESPEasy p2p which isn't a standard p2p packet. (See C013 for handling standard p2p packets) - PLUGIN_CLOCK_IN , // Called every new minute - PLUGIN_TASKTIMER_IN , // Called with a previously defined event at a specific time, set via setPluginTaskTimer - PLUGIN_FIFTY_PER_SECOND , // Called 50 times per second - PLUGIN_SET_CONFIG , // Counterpart of PLUGIN_GET_CONFIG_VALUE to allow to set a config via a command. - PLUGIN_GET_DEVICEGPIONAMES , // Allow for specific formatting of the label for standard pin configuration (e.g. "GPIO <- TX") - PLUGIN_EXIT , // Called when a task no longer is enabled (or deleted) - PLUGIN_GET_CONFIG_VALUE , // Similar to PLUGIN_WRITE, but meant to fetch some information. Must return success = true when it can handle the command. Can also be used to access extra unused task values. -// PLUGIN_UNCONDITIONAL_POLL , // Used to be called 10x per sec, but no longer used as GPIO related plugins now use a different technique. - PLUGIN_REQUEST , // Specific command to fetch a state (FIXME TD-er: Seems very similar to PLUGIN_GET_CONFIG_VALUE) - PLUGIN_TIME_CHANGE , // Called when system time is set (e.g. via NTP) - PLUGIN_MONITOR , // Replaces PLUGIN_UNCONDITIONAL_POLL - PLUGIN_SET_DEFAULTS , // Called when assigning a plugin to a task, to set some default config. - PLUGIN_GET_PACKED_RAW_DATA , // Return all data in a compact binary format specific for that plugin. - // Needs FEATURE_PACKED_RAW_DATA - PLUGIN_DEVICETIMER_IN , // Similar to PLUGIN_TASKTIMER_IN, addressed to a plugin instead of a task. - PLUGIN_WEBFORM_SHOW_I2C_PARAMS , // Show I2C parameters like address. - PLUGIN_WEBFORM_SHOW_SERIAL_PARAMS , // When needed, show additional parameters like baudrate or specific serial config - PLUGIN_MQTT_CONNECTION_STATE , // Signal when connection to MQTT broker is re-established - PLUGIN_MQTT_IMPORT , // For P037 MQTT import - PLUGIN_FORMAT_USERVAR , // Allow plugin specific formatting of a task variable (event->idx = variable) - PLUGIN_WEBFORM_SHOW_GPIO_DESCR , // Show GPIO description on devices overview tab + PLUGIN_INIT_ALL, // Not implemented in a plugin, only called during boot + PLUGIN_INIT, // Init the task, called when task is set to enabled (also at boot) + PLUGIN_READ, // This call can yield new data (when success = true) and then send to controllers + PLUGIN_ONCE_A_SECOND, // Called once a second + PLUGIN_TEN_PER_SECOND, // Called 10x per second (typical for checking new data instead of waiting) + PLUGIN_DEVICE_ADD, // Called at boot for letting a plugin adding itself to list of available plugins/devices + PLUGIN_EVENTLIST_ADD, // Not used. + PLUGIN_WEBFORM_SAVE, // Call from web interface to save settings + PLUGIN_WEBFORM_LOAD, // Call from web interface for presenting settings and status of plugin + PLUGIN_WEBFORM_SHOW_VALUES, // Call from devices overview page to format values in HTML + PLUGIN_GET_DEVICENAME, // Call to get the plugin description (e.g. "Switch input - Switch") + PLUGIN_GET_DEVICEVALUENAMES, // Call to let the plugin generate some default value names when not defined. + PLUGIN_GET_DEVICEVALUECOUNT, // Optional function call to allow tasks to specify the number of output values (e.g. + // P026_Sysinfo.ino) + PLUGIN_GET_DEVICEVTYPE, // Only needed when Device[deviceCount].OutputDataType is not Output_Data_type_t::Default + PLUGIN_WRITE, // Called to allow a task to process a command. Must return success = true when it can handle the + // command. + // PLUGIN_EVENT_OUT , // Does not seem to be used + PLUGIN_WEBFORM_SHOW_CONFIG, // Called to show non default pin assignment or addresses like for plugins using serial or 1-Wire + PLUGIN_SERIAL_IN, // Called on received data via serial port Serial0 (N.B. this may conflict with sending commands via + // serial) + PLUGIN_UDP_IN, // Called for received UDP data via ESPEasy p2p which isn't a standard p2p packet. (See C013 for + // handling standard p2p packets) + PLUGIN_CLOCK_IN, // Called every new minute + PLUGIN_TASKTIMER_IN, // Called with a previously defined event at a specific time, set via setPluginTaskTimer + PLUGIN_FIFTY_PER_SECOND, // Called 50 times per second + PLUGIN_SET_CONFIG, // Counterpart of PLUGIN_GET_CONFIG_VALUE to allow to set a config via a command. + PLUGIN_GET_DEVICEGPIONAMES, // Allow for specific formatting of the label for standard pin configuration (e.g. "GPIO <- TX") + PLUGIN_EXIT, // Called when a task no longer is enabled (or deleted) + PLUGIN_GET_CONFIG_VALUE, // Similar to PLUGIN_WRITE, but meant to fetch some information. Must return success = true when it + // can handle the command. Can also be used to access extra unused task values. + // PLUGIN_UNCONDITIONAL_POLL , // Used to be called 10x per sec, but no longer used as GPIO related plugins now use a different + // technique. + PLUGIN_REQUEST, // Specific command to fetch a state (FIXME TD-er: Seems very similar to PLUGIN_GET_CONFIG_VALUE) + PLUGIN_TIME_CHANGE, // Called when system time is set (e.g. via NTP) + PLUGIN_MONITOR, // Replaces PLUGIN_UNCONDITIONAL_POLL + PLUGIN_SET_DEFAULTS, // Called when assigning a plugin to a task, to set some default config. + PLUGIN_GET_PACKED_RAW_DATA, // Return all data in a compact binary format specific for that plugin. + // Needs FEATURE_PACKED_RAW_DATA + PLUGIN_DEVICETIMER_IN, // Similar to PLUGIN_TASKTIMER_IN, addressed to a plugin instead of a task. + PLUGIN_WEBFORM_SHOW_I2C_PARAMS, // Show I2C parameters like address. + PLUGIN_WEBFORM_SHOW_SERIAL_PARAMS, // When needed, show additional parameters like baudrate or specific serial config + PLUGIN_MQTT_CONNECTION_STATE, // Signal when connection to MQTT broker is re-established + PLUGIN_MQTT_IMPORT, // For P037 MQTT import + PLUGIN_FORMAT_USERVAR, // Allow plugin specific formatting of a task variable (event->idx = variable) + PLUGIN_WEBFORM_SHOW_GPIO_DESCR, // Show GPIO description on devices overview tab #if FEATURE_PLUGIN_STATS - PLUGIN_WEBFORM_LOAD_SHOW_STATS , // Show PluginStats on task config page + PLUGIN_WEBFORM_LOAD_SHOW_STATS, // Show PluginStats on task config page #endif // if FEATURE_PLUGIN_STATS - PLUGIN_I2C_HAS_ADDRESS , // Check the I2C addresses from the plugin, output in 'success' - PLUGIN_I2C_GET_ADDRESS , // Get the current I2C addresses from the plugin, output in 'event->Par1' and 'success' - PLUGIN_GET_DISPLAY_PARAMETERS , // Fetch X/Y resolution and Rotation setting from the plugin, output in 'success' - PLUGIN_WEBFORM_SHOW_ERRORSTATE_OPT , // Show Error State Value options, so be saved during PLUGIN_WEBFORM_SAVE - PLUGIN_INIT_VALUE_RANGES , // Initialize the ranges of values, called just before PLUGIN_INIT - PLUGIN_READ_ERROR_OCCURED , // Function returns "true" when last measurement was an error, called when PLUGIN_READ returns false - PLUGIN_WEBFORM_LOAD_OUTPUT_SELECTOR, // Show the configuration for output type and what value to set to which taskvalue - PLUGIN_PROCESS_CONTROLLER_DATA , // Can be called from the controller to signal the plugin to generate (or handle) sending the data. - PLUGIN_PRIORITY_INIT_ALL , // Pre-initialize all plugins that are set to PowerManager priority (not implemented in plugins) - PLUGIN_PRIORITY_INIT , // Pre-initialize a singe plugins that is set to PowerManager priority - PLUGIN_WEBFORM_LOAD_ALWAYS , // Loaded *after* PLUGIN_WEBFORM_LOAD, also shown for remote data-feed devices + PLUGIN_I2C_HAS_ADDRESS, // Check the I2C addresses from the plugin, output in 'success' + PLUGIN_I2C_GET_ADDRESS, // Get the current I2C addresses from the plugin, output in 'event->Par1' and 'success' + PLUGIN_GET_DISPLAY_PARAMETERS, // Fetch X/Y resolution and Rotation setting from the plugin, output in 'success' + PLUGIN_WEBFORM_SHOW_ERRORSTATE_OPT, // Show Error State Value options, so be saved during PLUGIN_WEBFORM_SAVE + PLUGIN_INIT_VALUE_RANGES, // Initialize the ranges of values, called just before PLUGIN_INIT + PLUGIN_READ_ERROR_OCCURED, // Function returns "true" when last measurement was an error, called when PLUGIN_READ returns false + PLUGIN_WEBFORM_LOAD_OUTPUT_SELECTOR, // Show the configuration for output type and what value to set to which taskvalue + PLUGIN_PROCESS_CONTROLLER_DATA, // Can be called from the controller to signal the plugin to generate (or handle) sending the data. + PLUGIN_PRIORITY_INIT_ALL, // Pre-initialize all plugins that are set to PowerManager priority (not implemented in plugins) + PLUGIN_PRIORITY_INIT, // Pre-initialize a singe plugins that is set to PowerManager priority + PLUGIN_WEBFORM_LOAD_ALWAYS, // Loaded *after* PLUGIN_WEBFORM_LOAD, also shown for remote data-feed devices #ifdef USES_ESPEASY_NOW - PLUGIN_FILTEROUT_CONTROLLER_DATA , // Can be called from the controller to query a task whether the data should be processed further. + PLUGIN_FILTEROUT_CONTROLLER_DATA, // Can be called from the controller to query a task whether the data should be processed further. #endif - PLUGIN_WEBFORM_PRE_SERIAL_PARAMS , // Before serial parameters, convert additional parameters like baudrate or specific serial config + PLUGIN_WEBFORM_PRE_SERIAL_PARAMS, // Before serial parameters, convert additional parameters like baudrate or specific serial config #if FEATURE_MQTT_DISCOVER || FEATURE_CUSTOM_TASKVAR_VTYPE - PLUGIN_GET_DISCOVERY_VTYPES , // Fetch the Sensor_VType values for dynamic value configurations in event->Par1..Par4 + PLUGIN_GET_DISCOVERY_VTYPES, // Fetch the Sensor_VType values for dynamic value configurations in event->Par1..Par4 #endif // IF FEATURE_MQTT_DISCOVER || FEATURE_CUSTOM_TASKVAR_VTYPE + #if FEATURE_TASKVALUE_UNIT_OF_MEASURE + PLUGIN_GET_UOM_GROUPS , // Fetch the UoM groups in a bitmap per value, as found in ESPEasy_UnitOfMeasure.cpp unit_of_measure_labels with -1024 offset + #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE + + PLUGIN_MAX_FUNCTION // Leave as last one. - PLUGIN_MAX_FUNCTION // Leave as last one. }; @@ -80,15 +90,16 @@ enum PluginFunctions_e { // CPlugin (Controller) function calls // ******************************************************************************** -class CPlugin { +class CPlugin +{ public: // As these function values are also used in the timing stats, make sure there is no overlap with the PLUGIN_xxx numbering. enum class Function { CPLUGIN_PROTOCOL_ADD = 127, // Called at boot for letting a controller adding itself to list of available controllers - CPLUGIN_CONNECT_SUCCESS, // Only used for timing stats - CPLUGIN_CONNECT_FAIL, // Only used for timing stats + CPLUGIN_CONNECT_SUCCESS, // Only used for timing stats + CPLUGIN_CONNECT_FAIL, // Only used for timing stats CPLUGIN_PROTOCOL_TEMPLATE, CPLUGIN_PROTOCOL_SEND, CPLUGIN_PROTOCOL_RECV, @@ -103,8 +114,9 @@ public: CPLUGIN_TEN_PER_SECOND, // Called 10x per second (typical for checking new data instead of waiting) CPLUGIN_FIFTY_PER_SECOND, // Called 50x per second (typical for checking new data instead of waiting) CPLUGIN_INIT_ALL, + CPLUGIN_EXIT_ALL, CPLUGIN_EXIT, - CPLUGIN_WRITE, // Send commands to a controller. + CPLUGIN_WRITE, // Send commands to a controller. // new messages for autodiscover controller plugins (experimental) i.e. C014 @@ -115,13 +127,173 @@ public: CPLUGIN_ACKNOWLEDGE, // call for sending acknowledges !ToDo done by direct function call in PluginCall() for now. CPLUGIN_WEBFORM_SHOW_HOST_CONFIG // Used for showing host information for the controller. + }; -}; + +}; // class CPlugin + +// ******************************************************************************** +// NWPlugin (Network) function calls +// ******************************************************************************** +class NWPlugin +{ +public: + + enum class ConnectionState { + Disabled, + Error, + Initializing, // e.g. power-on sequence of PPP modem may take quite long + Connecting, + LinkDown, + LinkUp, + Connected + + }; + + static const __FlashStringHelper* toString(ConnectionState connectionState, + bool asUnicodeSymbol = false); + + // As these function values are also used in the timing stats, make sure there is no overlap with the PLUGIN_xxx numbering. + + enum class Function { + NWPLUGIN_DRIVER_ADD = 192, // Called at boot for letting a network adapter adding itself to list of available controllers + NWPLUGIN_CONNECT_SUCCESS, // Only used for timing stats + NWPLUGIN_CONNECT_FAIL, // Only used for timing stats + NWPLUGIN_CONNECTION_STATE, + NWPLUGIN_DRIVER_TEMPLATE, + NWPLUGIN_LOAD_DEFAULTS, + NWPLUGIN_GET_DEVICENAME, + NWPLUGIN_CLIENT_IP_WEB_ACCESS_ALLOWED, + NWPLUGIN_WEBFORM_SAVE, + NWPLUGIN_WEBFORM_LOAD, + NWPLUGIN_GET_PARAMETER_DISPLAY_NAME, + NWPLUGIN_TIMER_IN, + NWPLUGIN_INIT, + NWPLUGIN_TEN_PER_SECOND, // Called 10x per second (typical for checking new data instead of waiting) + NWPLUGIN_FIFTY_PER_SECOND, // Called 50x per second (typical for checking new data instead of waiting) + NWPLUGIN_INIT_ALL, + NWPLUGIN_EXIT_ALL, // Exit all network interfaces + NWPLUGIN_EXIT, + NWPLUGIN_WRITE, // Send commands to a network adapter. + NWPLUGIN_CREDENTIALS_CHANGED, +#ifdef ESP32 + NWPLUGIN_GET_INTERFACE, + NWPLUGIN_WEBFORM_SHOW_ROUTE_PRIO, + NWPLUGIN_PRIORITY_ROUTE_CHANGED, // The default interface was changed, let other interfaces check to see if they need to do something. +#endif // ifdef ESP32 +#if FEATURE_NETWORK_TRAFFIC_COUNT + NWPLUGIN_GET_TRAFFIC_COUNT, // TX/RX count in event->Par64_1, resp. event->Par64_2 +#endif + NWPLUGIN_GET_CONNECTED_DURATION, + NWPLUGIN_PROCESS_EVENT, // Process received event arduino_event_id_t + NWPLUGIN_WEBSERVER_SHOULD_RUN, // Check whether the addressed interface is active and needs the webserver to be active +#if FEATURE_NETWORK_STATS + NWPLUGIN_RECORD_STATS, + NWPLUGIN_WEBFORM_LOAD_SHOW_STATS, +#endif + NWPLUGIN_WEBFORM_SHOW_NAME, // Name like "ppp", "eth0", etc. + NWPLUGIN_WEBFORM_SHOW_ACTIVE, // Check whether interface is active and if not, return some error value + NWPLUGIN_WEBFORM_SHOW_CONNECTED, // Used for showing connected state/speed + NWPLUGIN_WEBFORM_SHOW_EXTENDED, // Used for showing all connection info + NWPLUGIN_WEBFORM_SHOW_HOSTNAME, // Used for showing hostname + NWPLUGIN_WEBFORM_SHOW_HW_ADDRESS, // Used for showing MAC + NWPLUGIN_WEBFORM_SHOW_IP, // Used for showing IP +#ifndef LIMIT_BUILD_SIZE + NWPLUGIN_WEBFORM_SHOW_PORT, // Used for showing host information for the network adapter. +#endif + + NWPLUGIN_MAX_FUNCTION // Keep as last + + }; + +#define NrBitsNWPluginFunctions NR_BITS(static_cast(NWPlugin::Function::NWPLUGIN_MAX_FUNCTION)) + + +#ifdef ESP32 + static bool canQueryViaNetworkInterface(Function function); + + enum class IP_type { + inet = 0, // Keep index at 0 as default query + network_id_cdr, + netmask, + broadcast, + gateway, + dns1, + dns2, +# if CONFIG_LWIP_IPV6 + ipv6_unknown, + ipv6_global, + ipv6_link_local, + ipv6_site_local, + ipv6_unique_local, + ipv4_mapped_ipv6 +# endif // if CONFIG_LWIP_IPV6 + + }; + + + static const __FlashStringHelper* toString(IP_type ip_type); + + + static IPAddress get_IP_address(NWPlugin::IP_type ip_type, + NetworkInterface *networkInterface); + + static bool print_IP_address(NWPlugin::IP_type ip_type, + NetworkInterface *networkInterface, + Print & out); + + static bool get_subnet(NWPlugin::IP_type ip_type, + NetworkInterface *networkInterface, + IPAddress & networkID, + IPAddress & broadcast); +#endif // ifdef ESP32 + + static bool ipLessEqual(const IPAddress& ip, + const IPAddress& high); + + static bool ipInRange(const IPAddress& ip, + const IPAddress& low, + const IPAddress& high); + +#ifdef ESP32 + static bool IP_in_subnet(const IPAddress & ip, + NetworkInterface *networkInterface); + + static NWPlugin::IP_type get_IP_type(const IPAddress& ip); + + + enum class NetforkFlags { + DHCP_client = ESP_NETIF_DHCP_CLIENT, + DHCP_server = ESP_NETIF_DHCP_SERVER, + AutoUp = ESP_NETIF_FLAG_AUTOUP, + GratuituousArp = ESP_NETIF_FLAG_GARP, + EventIPmodified = ESP_NETIF_FLAG_EVENT_IP_MODIFIED, + isPPP = ESP_NETIF_FLAG_IS_PPP, + isBridge = ESP_NETIF_FLAG_IS_BRIDGE, + MLD_v6_report = ESP_NETIF_FLAG_MLDV6_REPORT, + IPv6_autoconf_enabled = ESP_NETIF_FLAG_IPV6_AUTOCONFIG_ENABLED + + }; + + static const __FlashStringHelper* toString(NetforkFlags flag); + + static bool isFlagSet(NetforkFlags flag, + NetworkInterface*networkInterface); + + static bool forceDHCP_request(NetworkInterface*networkInterface); + + +#endif // ifdef ESP32 + + +}; // class NWPlugin + // ******************************************************************************** // NPlugin (Notification) function calls // ******************************************************************************** -class NPlugin { +class NPlugin +{ public: enum class Function { @@ -131,8 +303,10 @@ public: NPLUGIN_WEBFORM_LOAD, NPLUGIN_WRITE, NPLUGIN_NOTIFY + }; -}; + +}; // class NPlugin #endif // DATATYPES_ESPEASY_PLUGIN_DEFS_H diff --git a/src/src/DataTypes/EthernetParameters.h b/src/src/DataTypes/EthernetParameters.h deleted file mode 100644 index a017e6c62..000000000 --- a/src/src/DataTypes/EthernetParameters.h +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef DATASTRUCTS_ETHERNETPARAMETERS_H -#define DATASTRUCTS_ETHERNETPARAMETERS_H - -#include "../../ESPEasy_common.h" - - -// Is stored in settings -enum class EthClockMode_t : uint8_t { - Ext_crystal_osc = 0, - Int_50MHz_GPIO_0 = 1, - Int_50MHz_GPIO_16 = 2, - Int_50MHz_GPIO_17_inv = 3 -}; - -bool isValid(EthClockMode_t clockMode); - -const __FlashStringHelper * toString(EthClockMode_t clockMode); - -bool isGpioUsedInETHClockMode(EthClockMode_t clockMode, - int8_t gpio); - -// Is stored in settings -enum class EthPhyType_t : uint8_t { -#if CONFIG_ETH_USE_ESP32_EMAC - LAN8720 = 0, - TLK110 = 1, -#if ESP_IDF_VERSION_MAJOR > 3 - - RTL8201 = 2, -#if ETH_TYPE_JL1101_SUPPORTED - JL1101 = 3, -#endif - DP83848 = 4, - KSZ8041 = 5, - KSZ8081 = 6, -#endif -#endif -#if ESP_IDF_VERSION_MAJOR >= 5 -#if CONFIG_ETH_SPI_ETHERNET_DM9051 - DM9051 = 10, -#endif -#if CONFIG_ETH_SPI_ETHERNET_W5500 - W5500 = 11, -#endif -#if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL - KSZ8851 = 12, -#endif -#endif - notSet = 127 // Might be processed in some code as int, uint8_t and int8_t -}; - -bool isValid(EthPhyType_t phyType); - -#if FEATURE_ETHERNET -#include - -bool isSPI_EthernetType(EthPhyType_t phyType); - -// Convert to internal enum type as those enum values may not always be the same int value -eth_phy_type_t to_ESP_phy_type(EthPhyType_t phyType); -#endif - -const __FlashStringHelper * toString(EthPhyType_t phyType); - - -#endif // DATASTRUCTS_ETHERNETPARAMETERS_H diff --git a/src/src/DataTypes/EventValueSource.cpp b/src/src/DataTypes/EventValueSource.cpp new file mode 100644 index 000000000..c4acbf193 --- /dev/null +++ b/src/src/DataTypes/EventValueSource.cpp @@ -0,0 +1,79 @@ +#include "../DataTypes/EventValueSource.h" + +const __FlashStringHelper * EventValueSource::toString(Enum source) +{ + switch (source) + { + case EventValueSource::Enum::VALUE_SOURCE_SYSTEM: return F("System"); + case EventValueSource::Enum::VALUE_SOURCE_SERIAL: return F("Serial"); + case EventValueSource::Enum::VALUE_SOURCE_UDP: return F("UDP"); + case EventValueSource::Enum::VALUE_SOURCE_WEB_FRONTEND: return F("Web"); + case EventValueSource::Enum::VALUE_SOURCE_RULES: return F("Rules"); + case EventValueSource::Enum::VALUE_SOURCE_HTTP: return F("HTTP"); + case EventValueSource::Enum::VALUE_SOURCE_MQTT: return F("MQTT"); + case EventValueSource::Enum::VALUE_SOURCE_RULES_RESTRICTED: return F("Restricted Rules"); + + case EventValueSource::Enum::VALUE_SOURCE_NOT_SET: + case EventValueSource::Enum::VALUE_SOURCE_NR_VALUES: + break; + } + return F(""); +} + +bool EventValueSource::partOfGroup(EventValueSource::Enum source, EventValueSourceGroup::Enum group) +{ + switch (source) + { + case EventValueSource::Enum::VALUE_SOURCE_NOT_SET: + case EventValueSource::Enum::VALUE_SOURCE_NR_VALUES: + return false; + case EventValueSource::Enum::VALUE_SOURCE_SYSTEM: + case EventValueSource::Enum::VALUE_SOURCE_SERIAL: + case EventValueSource::Enum::VALUE_SOURCE_UDP: + case EventValueSource::Enum::VALUE_SOURCE_WEB_FRONTEND: + case EventValueSource::Enum::VALUE_SOURCE_RULES: + return true; + case EventValueSource::Enum::VALUE_SOURCE_HTTP: + case EventValueSource::Enum::VALUE_SOURCE_MQTT: + case EventValueSource::Enum::VALUE_SOURCE_RULES_RESTRICTED: + return group == EventValueSourceGroup::Enum::ALL; + } + return false; +} + +bool EventValueSource::SourceNeedsStatusUpdate(EventValueSource::Enum eventSource) +{ + switch (eventSource) + { + case EventValueSource::Enum::VALUE_SOURCE_HTTP: + case EventValueSource::Enum::VALUE_SOURCE_SERIAL: + case EventValueSource::Enum::VALUE_SOURCE_MQTT: + case EventValueSource::Enum::VALUE_SOURCE_WEB_FRONTEND: + return true; + + default: + break; + } + return false; +} + +bool EventValueSource::isExternalSource(EventValueSource::Enum eventSource) +{ + switch (eventSource) + { + case EventValueSource::Enum::VALUE_SOURCE_NOT_SET: + case EventValueSource::Enum::VALUE_SOURCE_NR_VALUES: + case EventValueSource::Enum::VALUE_SOURCE_SYSTEM: + case EventValueSource::Enum::VALUE_SOURCE_RULES: + case EventValueSource::Enum::VALUE_SOURCE_RULES_RESTRICTED: + break; + + case EventValueSource::Enum::VALUE_SOURCE_SERIAL: + case EventValueSource::Enum::VALUE_SOURCE_UDP: + case EventValueSource::Enum::VALUE_SOURCE_WEB_FRONTEND: + case EventValueSource::Enum::VALUE_SOURCE_HTTP: + case EventValueSource::Enum::VALUE_SOURCE_MQTT: + return true; +} + return false; +} \ No newline at end of file diff --git a/src/src/DataTypes/EventValueSource.h b/src/src/DataTypes/EventValueSource.h index 12de4c45e..751c0305a 100644 --- a/src/src/DataTypes/EventValueSource.h +++ b/src/src/DataTypes/EventValueSource.h @@ -7,7 +7,9 @@ struct EventValueSourceGroup { enum class Enum : uint8_t { RESTRICTED, ALL + }; + }; @@ -25,27 +27,18 @@ struct EventValueSource { VALUE_SOURCE_RULES_RESTRICTED = 8, VALUE_SOURCE_NR_VALUES + }; - static bool partOfGroup(EventValueSource::Enum source, EventValueSourceGroup::Enum group) - { - switch (source) { - case EventValueSource::Enum::VALUE_SOURCE_NOT_SET: - case EventValueSource::Enum::VALUE_SOURCE_NR_VALUES: - return false; - case EventValueSource::Enum::VALUE_SOURCE_SYSTEM: - case EventValueSource::Enum::VALUE_SOURCE_SERIAL: - case EventValueSource::Enum::VALUE_SOURCE_UDP: - case EventValueSource::Enum::VALUE_SOURCE_WEB_FRONTEND: - case EventValueSource::Enum::VALUE_SOURCE_RULES: - return true; - case EventValueSource::Enum::VALUE_SOURCE_HTTP: - case EventValueSource::Enum::VALUE_SOURCE_MQTT: - case EventValueSource::Enum::VALUE_SOURCE_RULES_RESTRICTED: - return group == EventValueSourceGroup::Enum::ALL; - } - return false; - } + static bool SourceNeedsStatusUpdate(EventValueSource::Enum eventSource); + + static bool isExternalSource(EventValueSource::Enum eventSource); + + static const __FlashStringHelper* toString(Enum); + + static bool partOfGroup(EventValueSource::Enum source, + EventValueSourceGroup::Enum group); + }; diff --git a/src/src/DataTypes/FormSelectorOptions.cpp b/src/src/DataTypes/FormSelectorOptions.cpp index 8b157d1f1..ed9b9f589 100644 --- a/src/src/DataTypes/FormSelectorOptions.cpp +++ b/src/src/DataTypes/FormSelectorOptions.cpp @@ -91,6 +91,16 @@ void FormSelectorOptions::clearClassName() classname = F(""); } +void FormSelectorOptions::addFormSelector(LabelType::Enum label, int selectedIndex) const +{ + auto kv = getKeyValue(label); + addFormSelector(getLabel(kv), getInternalLabel(kv), selectedIndex); + #if FEATURE_TASKVALUE_UNIT_OF_MEASURE + addUnit(kv.getUnit()); + #endif + addFormNote(getFormNote(label)); +} + void FormSelectorOptions::addFormSelector( const __FlashStringHelper *label, const __FlashStringHelper *id, diff --git a/src/src/DataTypes/FormSelectorOptions.h b/src/src/DataTypes/FormSelectorOptions.h index e5b01a24a..c5133b53e 100644 --- a/src/src/DataTypes/FormSelectorOptions.h +++ b/src/src/DataTypes/FormSelectorOptions.h @@ -2,6 +2,7 @@ #define DATATYPES_FORMSELECTOROPTIONS_H #include "../../ESPEasy_common.h" +#include "../Helpers/StringProvider.h" class FormSelectorOptions { public: @@ -40,6 +41,9 @@ public: const __FlashStringHelper *id, int selectedIndex) const; + + void addFormSelector(LabelType::Enum label, int selectedIndex) const; + void addFormSelector(const String & label, const __FlashStringHelper *id, int selectedIndex) const; diff --git a/src/src/DataTypes/LogLevels.cpp b/src/src/DataTypes/LogLevels.cpp new file mode 100644 index 000000000..94fff720e --- /dev/null +++ b/src/src/DataTypes/LogLevels.cpp @@ -0,0 +1,77 @@ +#include "../DataTypes/LogLevels.h" + +#include "../DataTypes/FormSelectorOptions.h" + +constexpr int logLevels[] = +{ + LOG_LEVEL_NONE, + LOG_LEVEL_ERROR, + LOG_LEVEL_INFO + +#ifndef BUILD_NO_DEBUG + , LOG_LEVEL_DEBUG + , LOG_LEVEL_DEBUG_MORE + , LOG_LEVEL_DEBUG_DEV +#endif // ifndef BUILD_NO_DEBUG + +}; + +// We can't have a globally declared flash string array on ESP8266, so need to wrap it in a function. +const __FlashStringHelper* getLogLevelDisplayStringFromIndex(uint8_t index) +{ + const __FlashStringHelper *logLevelDisplayStrings[] + { + F("None"), + F("Error"), + F("Info") +#ifndef BUILD_NO_DEBUG + , F("Debug") + , F("Debug More") + , F("Debug dev") +#endif // ifndef BUILD_NO_DEBUG + }; + + if (index < NR_ELEMENTS(logLevelDisplayStrings)) { + return logLevelDisplayStrings[index]; + } + return F(""); +} + +uint8_t LOG_LEVEL_NRELEMENTS() { return NR_ELEMENTS(logLevels); } + +const __FlashStringHelper* getLogLevelDisplayString(int logLevel) { + for (uint8_t i = 0; i < NR_ELEMENTS(logLevels); ++i) { + if (logLevels[i] == logLevel) { + return getLogLevelDisplayStringFromIndex(i); + } + } + return F(""); +} + +const __FlashStringHelper* getLogLevelDisplayStringFromIndex(uint8_t index, int& logLevel) { + if (index < NR_ELEMENTS(logLevels)) { + logLevel = logLevels[index]; + return getLogLevelDisplayStringFromIndex(index); + } + logLevel = -1; + return F(""); +} + +void addLogLevelFormSelectorOptions(const String& id, int choice) +{ + const __FlashStringHelper *logLevelDisplayStrings[] + { + F("None"), + F("Error"), + F("Info") +#ifndef BUILD_NO_DEBUG + , F("Debug") + , F("Debug More") + , F("Debug dev") +#endif // ifndef BUILD_NO_DEBUG + }; + + const FormSelectorOptions selector(NR_ELEMENTS(logLevels), logLevelDisplayStrings, logLevels); + + selector.addSelector(id, choice); +} diff --git a/src/src/DataTypes/LogLevels.h b/src/src/DataTypes/LogLevels.h new file mode 100644 index 000000000..0ccbefa7f --- /dev/null +++ b/src/src/DataTypes/LogLevels.h @@ -0,0 +1,45 @@ +#pragma once + +#include "../../ESPEasy_common.h" + + +#define LOG_LEVEL_NONE 0 // console output is also loglevel none, only shown on those 'log destinations' subscribing to + // get console output. +#define LOG_LEVEL_ERROR 1 +#define LOG_LEVEL_INFO 2 +#ifndef BUILD_NO_DEBUG +# define LOG_LEVEL_DEBUG 3 +# define LOG_LEVEL_DEBUG_MORE 4 +# define LOG_LEVEL_DEBUG_DEV 9 // use for testing/debugging only, not for regular use +#endif // ifndef BUILD_NO_DEBUG + +enum LogDestination : uint8_t { + + LOG_TO_SERIAL , + LOG_TO_SYSLOG , + LOG_TO_WEBLOG , +#if FEATURE_SD + LOG_TO_SDCARD , +#endif +#if USES_ESPEASY_CONSOLE_FALLBACK_PORT + LOG_TO_SERIAL_EXTRA , +#endif + + NR_LOG_TO_DESTINATIONS +}; + +#ifdef BUILD_NO_DEBUG +# define LOG_LEVEL_MAX_STRING_LENGTH 6 +#else +# define LOG_LEVEL_MAX_STRING_LENGTH 10 +#endif // ifdef BUILD_NO_DEBUG + +uint8_t LOG_LEVEL_NRELEMENTS(); + +const __FlashStringHelper* getLogLevelDisplayString(int logLevel); + +const __FlashStringHelper* getLogLevelDisplayStringFromIndex(uint8_t index, + int & logLevel); + +void addLogLevelFormSelectorOptions(const String& id, + int choice); diff --git a/src/src/DataTypes/NetworkMedium.cpp b/src/src/DataTypes/NetworkMedium.cpp deleted file mode 100644 index b66d210e7..000000000 --- a/src/src/DataTypes/NetworkMedium.cpp +++ /dev/null @@ -1,33 +0,0 @@ - -#include "../DataTypes/NetworkMedium.h" - -bool isValid(NetworkMedium_t medium) { - switch (medium) { - case NetworkMedium_t::WIFI: - case NetworkMedium_t::Ethernet: -#ifdef USES_ESPEASY_NOW - case NetworkMedium_t::ESPEasyNOW_only: -#endif - return true; - - case NetworkMedium_t::NotSet: - return false; - - // Do not use default: as this allows the compiler to detect any missing cases. - } - return false; -} - -const __FlashStringHelper * toString(NetworkMedium_t medium) { - switch (medium) { - case NetworkMedium_t::WIFI: return F("WiFi"); - case NetworkMedium_t::Ethernet: return F("Ethernet"); -#ifdef USES_ESPEASY_NOW - case NetworkMedium_t::ESPEasyNOW_only: return F(ESPEASY_NOW_NAME " only"); -#endif - case NetworkMedium_t::NotSet: return F("Not Set"); - - // Do not use default: as this allows the compiler to detect any missing cases. - } - return F("Unknown"); -} \ No newline at end of file diff --git a/src/src/DataTypes/NodeTypeID.cpp b/src/src/DataTypes/NodeTypeID.cpp index ea80827b9..475df69eb 100644 --- a/src/src/DataTypes/NodeTypeID.cpp +++ b/src/src/DataTypes/NodeTypeID.cpp @@ -1,24 +1,32 @@ #include "../DataTypes/NodeTypeID.h" +#include "../Helpers/StringConverter.h" + +String toNodeTypeDisplayString(uint8_t nodeType) { + const __FlashStringHelper* suffix = F(""); -const __FlashStringHelper* toNodeTypeDisplayString(uint8_t nodeType) { switch (nodeType) { - case NODE_TYPE_ID_ESP_EASY_STD: return F("ESP Easy"); - case NODE_TYPE_ID_ESP_EASYM_STD: return F("ESP Easy Mega"); - case NODE_TYPE_ID_ESP_EASY32_STD: return F("ESP Easy 32"); - case NODE_TYPE_ID_ESP_EASY32S2_STD: return F("ESP Easy 32-S2"); - case NODE_TYPE_ID_ESP_EASY32S3_STD: return F("ESP Easy 32-S3"); - case NODE_TYPE_ID_ESP_EASY32C2_STD: return F("ESP Easy 32-C2"); - case NODE_TYPE_ID_ESP_EASY32C3_STD: return F("ESP Easy 32-C3"); - case NODE_TYPE_ID_ESP_EASY32C5_STD: return F("ESP Easy 32-C5"); - case NODE_TYPE_ID_ESP_EASY32C6_STD: return F("ESP Easy 32-C6"); - case NODE_TYPE_ID_ESP_EASY32C61_STD: return F("ESP Easy 32-C61"); - case NODE_TYPE_ID_ESP_EASY32H2_STD: return F("ESP Easy 32-H2"); - case NODE_TYPE_ID_ESP_EASY32P4_STD: return F("ESP Easy 32-P4"); - case NODE_TYPE_ID_RPI_EASY_STD: return F("RPI Easy"); - case NODE_TYPE_ID_ARDUINO_EASY_STD: return F("Arduino Easy"); - case NODE_TYPE_ID_NANO_EASY_STD: return F("Nano Easy"); + case NODE_TYPE_ID_ESP_EASY_STD: return F("ESP Easy"); + case NODE_TYPE_ID_ESP_EASYM_STD: return F("ESP Easy Mega"); + case NODE_TYPE_ID_ESP_EASY32_STD: break; + case NODE_TYPE_ID_ESP_EASY32S2_STD: suffix = F("-S2"); break; + case NODE_TYPE_ID_ESP_EASY32S3_STD: suffix = F("-S3"); break; + case NODE_TYPE_ID_ESP_EASY32C2_STD: suffix = F("-C2"); break; + case NODE_TYPE_ID_ESP_EASY32C3_STD: suffix = F("-C3"); break; + case NODE_TYPE_ID_ESP_EASY32C5_STD: suffix = F("-C5"); break; + case NODE_TYPE_ID_ESP_EASY32C6_STD: suffix = F("-C6"); break; + case NODE_TYPE_ID_ESP_EASY32C61_STD: suffix = F("-C61"); break; + case NODE_TYPE_ID_ESP_EASY32H2_STD: suffix = F("-H2"); break; + case NODE_TYPE_ID_ESP_EASY32H21_STD: suffix = F("-H21"); break; + case NODE_TYPE_ID_ESP_EASY32H4_STD: suffix = F("-H4"); break; + case NODE_TYPE_ID_ESP_EASY32P4_STD: suffix = F("-P4"); break; + case NODE_TYPE_ID_ESP_EASY32P4r3_STD: suffix = F("-P4r3"); break; + case NODE_TYPE_ID_RPI_EASY_STD: return F("RPI Easy"); + case NODE_TYPE_ID_ARDUINO_EASY_STD: return F("Arduino Easy"); + case NODE_TYPE_ID_NANO_EASY_STD: return F("Nano Easy"); + default: + return EMPTY_STRING; } - return F(""); -} \ No newline at end of file + return concat(F("ESP Easy 32"), suffix); +} diff --git a/src/src/DataTypes/NodeTypeID.h b/src/src/DataTypes/NodeTypeID.h index d448efa7f..083c6fe73 100644 --- a/src/src/DataTypes/NodeTypeID.h +++ b/src/src/DataTypes/NodeTypeID.h @@ -18,6 +18,9 @@ #define NODE_TYPE_ID_ESP_EASY32C61_STD 40 #define NODE_TYPE_ID_ESP_EASY32C5_STD 41 #define NODE_TYPE_ID_ESP_EASY32P4_STD 42 +#define NODE_TYPE_ID_ESP_EASY32H21_STD 43 +#define NODE_TYPE_ID_ESP_EASY32H4_STD 44 +#define NODE_TYPE_ID_ESP_EASY32P4r3_STD 45 #define NODE_TYPE_ID_ARDUINO_EASY_STD 65 #define NODE_TYPE_ID_NANO_EASY_STD 81 @@ -44,8 +47,16 @@ # define NODE_TYPE_ID NODE_TYPE_ID_ESP_EASY32C61_STD # elif defined(ESP32H2) # define NODE_TYPE_ID NODE_TYPE_ID_ESP_EASY32H2_STD + # elif defined(ESP32H21) + # define NODE_TYPE_ID NODE_TYPE_ID_ESP_EASY32H21_STD + # elif defined(ESP32H4) + # define NODE_TYPE_ID NODE_TYPE_ID_ESP_EASY32H4_STD # elif defined(ESP32P4) + #ifdef CONFIG_ESP32P4_SELECTS_REV_LESS_V3 # define NODE_TYPE_ID NODE_TYPE_ID_ESP_EASY32P4_STD + #else + # define NODE_TYPE_ID NODE_TYPE_ID_ESP_EASY32P4r3_STD + #endif # elif defined(ESP32_CLASSIC) # define NODE_TYPE_ID NODE_TYPE_ID_ESP_EASY32_STD # else @@ -56,6 +67,6 @@ static_assert(false, "Implement processor architecture"); #endif -const __FlashStringHelper* toNodeTypeDisplayString(uint8_t nodeType); +String toNodeTypeDisplayString(uint8_t nodeType); #endif // ifndef DATATYPES_NODETYPEID_H diff --git a/src/src/DataTypes/PluginID.cpp b/src/src/DataTypes/PluginID.cpp index 6a866651d..c18ddaff5 100644 --- a/src/src/DataTypes/PluginID.cpp +++ b/src/src/DataTypes/PluginID.cpp @@ -8,4 +8,4 @@ String pluginID_t::toDisplayString() const { return strformat(F("P%03d"), value); } -const pluginID_t INVALID_PLUGIN_ID; +const pluginID_t INVALID_PLUGIN_ID{}; diff --git a/src/src/DataTypes/PluginID.h b/src/src/DataTypes/PluginID.h index 8e9b909da..5d5447ebc 100644 --- a/src/src/DataTypes/PluginID.h +++ b/src/src/DataTypes/PluginID.h @@ -23,6 +23,16 @@ struct __attribute__((__packed__)) pluginID_t { return res; } + operator int() const + { + return value; + } + + operator bool() const + { + return value != 0; + } + pluginID_t& operator=(const pluginID_t& other) { value = other.value; diff --git a/src/src/DataTypes/SPI_options.cpp b/src/src/DataTypes/SPI_options.cpp index 575611044..57bac16f2 100644 --- a/src/src/DataTypes/SPI_options.cpp +++ b/src/src/DataTypes/SPI_options.cpp @@ -42,8 +42,14 @@ #define VSPI_FSPI_SHORT_STRING "FSPI" # elif CONFIG_IDF_TARGET_ESP32C3 // ESP32-C3 #define VSPI_FSPI_SHORT_STRING "SPI" +# elif CONFIG_IDF_TARGET_ESP32C5 // ESP32-C5 +#define VSPI_FSPI_SHORT_STRING "FSPI" # elif CONFIG_IDF_TARGET_ESP32C6 // ESP32-C6 #define VSPI_FSPI_SHORT_STRING "FSPI" +# elif CONFIG_IDF_TARGET_ESP32C61 // ESP32-C61 +#define VSPI_FSPI_SHORT_STRING "FSPI" +# elif CONFIG_IDF_TARGET_ESP32P4 // ESP32-P4 +#define VSPI_FSPI_SHORT_STRING "FSPI" # elif CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 #define VSPI_FSPI_SHORT_STRING "VSPI" diff --git a/src/src/DataTypes/SPI_options.h b/src/src/DataTypes/SPI_options.h index b2a8627dc..3bb385a8f 100644 --- a/src/src/DataTypes/SPI_options.h +++ b/src/src/DataTypes/SPI_options.h @@ -16,15 +16,15 @@ // SPI_HOST = SPI1_HOST // Only usable on ESP32 // HSPI_HOST = SPI2_HOST // VSPI_HOST = SPI3_HOST -// +// // ESP32-S2: // FSPI_HOST = SPI2_HOST // HSPI_HOST = SPI3_HOST -// +// // ESP32-S3: // FSPI_HOST = SPI2_HOST // HSPI_HOST = SPI3_HOST -// +// // ESP32-C6: // FSPI_HOST = SPI1_HOST // ESP32-C3: @@ -39,47 +39,33 @@ // HSPI option is disabled for now on other than ESP32_CLASSIC. // Should be added later as "Custom HSPI" when we support multiple SPI busses. #ifdef ESP32 -# if CONFIG_IDF_TARGET_ESP32S3 // ESP32-S3 -#define VSPI_FSPI_SCK 36 -#define VSPI_FSPI_MISO 37 -#define VSPI_FSPI_MOSI 35 -# elif CONFIG_IDF_TARGET_ESP32S2 // ESP32-S2 -#define VSPI_FSPI_SCK 36 -#define VSPI_FSPI_MISO 37 -#define VSPI_FSPI_MOSI 35 -# elif CONFIG_IDF_TARGET_ESP32C6 // ESP32-C6 -#define VSPI_FSPI_SCK 21 -#define VSPI_FSPI_MISO 20 -#define VSPI_FSPI_MOSI 19 -# elif CONFIG_IDF_TARGET_ESP32C3 // ESP32-C3 -#define VSPI_FSPI_SCK 4 -#define VSPI_FSPI_MISO 5 -#define VSPI_FSPI_MOSI 6 -# elif CONFIG_IDF_TARGET_ESP32C2 // ESP32-C2 -#define VSPI_FSPI_SCK 4 -#define VSPI_FSPI_MISO 5 -#define VSPI_FSPI_MOSI 6 -# elif CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 -#define VSPI_FSPI_SCK 18 -#define VSPI_FSPI_MISO 19 -#define VSPI_FSPI_MOSI 23 -# define HSPI_SCLK 14 -# define HSPI_MISO 12 -# define HSPI_MOSI 13 -# define HSPI_SS 15 +# if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 -# else // if CONFIG_IDF_TARGET_ESP32S2 -# error Target CONFIG_IDF_TARGET is not supported -# endif // if CONFIG_IDF_TARGET_ESP32S2 +# define VSPI_FSPI_SCK 18 +# define VSPI_FSPI_MISO 19 +# define VSPI_FSPI_MOSI 23 -#endif +# define HSPI_SCLK 14 +# define HSPI_MISO 12 +# define HSPI_MOSI 13 +# define HSPI_SS 15 -enum class SPI_Options_e { // Do not change values as this is stored in the settings! - None = 0, - Vspi_Fspi = 1, // Default SPI bus +# else // if CONFIG_IDF_TARGET_ESP32 +# include + +# define VSPI_FSPI_SCK SPI2_IOMUX_PIN_NUM_CLK +# define VSPI_FSPI_MISO SPI2_IOMUX_PIN_NUM_MISO +# define VSPI_FSPI_MOSI SPI2_IOMUX_PIN_NUM_MOSI + +# endif // if CONFIG_IDF_TARGET_ESP32 +#endif // ifdef ESP32 +enum class SPI_Options_e { + // Do not change values as this is stored in the settings! + None = 0, + Vspi_Fspi = 1, // General-purpose SPI (GP-SPI) mode (Default SPI bus) #ifdef ESP32_CLASSIC - Hspi = 2, + Hspi = 2, #endif // UserDefined is using the default SPI bus. @@ -87,6 +73,7 @@ enum class SPI_Options_e { // Do not change values as this is stored in the sett // For later versions it is called FSPI // N.B. the ESP32-C3 does not seem to name these as there is no SPI3_HOST. UserDefined = 9 // Leave some room for other, possible future, hardware-related options + }; #ifdef ESP32 diff --git a/src/src/DataTypes/SchedulerIntervalTimer.cpp b/src/src/DataTypes/SchedulerIntervalTimer.cpp index bef495696..b5cc0e24f 100644 --- a/src/src/DataTypes/SchedulerIntervalTimer.cpp +++ b/src/src/DataTypes/SchedulerIntervalTimer.cpp @@ -29,12 +29,8 @@ String toString(SchedulerIntervalTimer_e timer) { timer <= SchedulerIntervalTimer_e::TIMER_C025_DELAY_QUEUE) { const int id = static_cast(timer) - static_cast(SchedulerIntervalTimer_e::TIMER_C001_DELAY_QUEUE) + 1; - String res; - res.reserve(24); - res = F("TIMER_"); - res += get_formatted_Controller_number(id); - res += F("_DELAY_QUEUE"); - return res; + + return strformat(F("TIMER_C%03d_DELAY_QUEUE"), id); } return toString_f(timer); #endif // ifdef BUILD_NO_DEBUG diff --git a/src/src/DataTypes/SchedulerTimerType.cpp b/src/src/DataTypes/SchedulerTimerType.cpp index 9d32cd1be..8a2136675 100644 --- a/src/src/DataTypes/SchedulerTimerType.cpp +++ b/src/src/DataTypes/SchedulerTimerType.cpp @@ -12,6 +12,7 @@ const __FlashStringHelper * toString(SchedulerTimerType_e timerType) { case SchedulerTimerType_e::PLUGIN_DEVICETIMER_IN_e: return F("PLUGIN_DEVICETIMER_IN"); case SchedulerTimerType_e::RulesTimer: return F("Rules#Timer"); case SchedulerTimerType_e::IntendedReboot: return F("Intended Reboot"); + case SchedulerTimerType_e::NWPLUGIN_TIMER_IN_e: return F("NWPLUGIN_TIMER_IN"); } return F("Unknown"); } diff --git a/src/src/DataTypes/SchedulerTimerType.h b/src/src/DataTypes/SchedulerTimerType.h index 804ed95ee..a51f230dd 100644 --- a/src/src/DataTypes/SchedulerTimerType.h +++ b/src/src/DataTypes/SchedulerTimerType.h @@ -12,6 +12,7 @@ GPIO_timer = 4u, PLUGIN_DEVICETIMER_IN_e = 5u, // Similar to PLUGIN_TASKTIMER_IN, addressed to a plugin instead of a task. RulesTimer = 6u, + NWPLUGIN_TIMER_IN_e = 7u, IntendedReboot = 15u // Used to show intended reboot }; diff --git a/src/src/DataTypes/SettingsType.cpp b/src/src/DataTypes/SettingsType.cpp index 52ddaf8f6..fde993e3e 100644 --- a/src/src/DataTypes/SettingsType.cpp +++ b/src/src/DataTypes/SettingsType.cpp @@ -27,6 +27,13 @@ const __FlashStringHelper * SettingsType::getSettingsTypeString(Enum settingsTyp #if FEATURE_ALTERNATIVE_CDN_URL case Enum::CdnSettings_Type: return F("CDN_url"); #endif +#if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + case Enum::NetworkInterfaceSettings_Type: return F("NetworkInterface"); +#endif +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + case Enum::DeviceSpecificCredentials_type: return F("DeviceSpecificCredentials"); +#endif + case Enum::SettingsType_MAX: break; } @@ -118,6 +125,18 @@ bool SettingsType::getSettingsParameters(Enum settingsType, int index, int& max_ struct_size = 0; break; } +#if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + case Enum::NetworkInterfaceSettings_Type: + { + max_index = NETWORK_MAX; + offset = DAT_NETWORK_INTERFACES_OFFSET + index * (DAT_NETWORK_INTERFACE_SIZE); + max_size = DAT_NETWORK_INTERFACE_SIZE; + + // struct_size may differ. + struct_size = 0; + break; + } +#endif #if FEATURE_ALTERNATIVE_CDN_URL case Enum::CdnSettings_Type: { @@ -130,6 +149,19 @@ bool SettingsType::getSettingsParameters(Enum settingsType, int index, int& max_ } break; #endif +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + case Enum::DeviceSpecificCredentials_type: + { + max_index = 1; + offset = DAT_OFFSET_DEV_CREDENTIALS; + max_size = DAT_DEV_CREDENTIALS_SIZE; + + // struct_size may differ. + struct_size = 0; + } + break; +#endif + case Enum::SettingsType_MAX: { @@ -180,7 +212,7 @@ int SettingsType::getFileSize(Enum settingsType) { return max_file_pos; } -#ifndef BUILD_MINIMAL_OTA +#ifndef BUILD_NO_DEBUG unsigned int SettingsType::getSVGcolor(Enum settingsType) { switch (settingsType) { case Enum::BasicSettings_Type: @@ -195,10 +227,17 @@ unsigned int SettingsType::getSVGcolor(Enum settingsType) { return 0xFAC05E; case Enum::NotificationSettings_Type: return 0xF79D84; +#if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + case Enum::NetworkInterfaceSettings_Type: + return 0x84F79D; +#endif case Enum::SecuritySettings_Type: return 0xff00a2; case Enum::ExtdControllerCredentials_Type: +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + case Enum::DeviceSpecificCredentials_type: +#endif return 0xc300ff; #if FEATURE_ALTERNATIVE_CDN_URL case Enum::CdnSettings_Type: @@ -222,6 +261,9 @@ SettingsType::SettingsFileEnum SettingsType::getSettingsFile(Enum settingsType) case Enum::CustomControllerSettings_Type: #if FEATURE_ALTERNATIVE_CDN_URL case Enum::CdnSettings_Type: +#endif +#if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + case Enum::NetworkInterfaceSettings_Type: #endif return SettingsFileEnum::FILE_CONFIG_type; case Enum::NotificationSettings_Type: @@ -229,6 +271,10 @@ SettingsType::SettingsFileEnum SettingsType::getSettingsFile(Enum settingsType) case Enum::SecuritySettings_Type: case Enum::ExtdControllerCredentials_Type: return SettingsFileEnum::FILE_SECURITY_type; +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + case Enum::DeviceSpecificCredentials_type: + return SettingsFileEnum::FILE_DEVICE_SECURITY_type; +#endif case Enum::SettingsType_MAX: break; @@ -250,6 +296,9 @@ const __FlashStringHelper * SettingsType::getSettingsFileName(SettingsType::Sett case SettingsFileEnum::FILE_CONFIG_type: return getFileName(FileType::CONFIG_DAT); case SettingsFileEnum::FILE_NOTIFICATION_type: return getFileName(FileType::NOTIFICATION_DAT); case SettingsFileEnum::FILE_SECURITY_type: return getFileName(FileType::SECURITY_DAT); +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + case SettingsFileEnum::FILE_DEVICE_SECURITY_type: return getFileName(FileType::DEV_SECURITY_DAT); +#endif case SettingsFileEnum::FILE_UNKNOWN_type: break; } return F(""); @@ -260,6 +309,9 @@ size_t SettingsType::getInitFileSize(SettingsType::SettingsFileEnum file_type) { case SettingsFileEnum::FILE_CONFIG_type: return CONFIG_FILE_SIZE; case SettingsFileEnum::FILE_NOTIFICATION_type: return 4096; case SettingsFileEnum::FILE_SECURITY_type: return 4096; +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + case SettingsFileEnum::FILE_DEVICE_SECURITY_type: return DAT_DEV_CREDENTIALS_SIZE; +#endif case SettingsFileEnum::FILE_UNKNOWN_type: break; } return 0; diff --git a/src/src/DataTypes/SettingsType.h b/src/src/DataTypes/SettingsType.h index 5030e2f7c..b5f2c43cb 100644 --- a/src/src/DataTypes/SettingsType.h +++ b/src/src/DataTypes/SettingsType.h @@ -19,6 +19,12 @@ public: #if FEATURE_ALTERNATIVE_CDN_URL CdnSettings_Type, #endif +#if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS + NetworkInterfaceSettings_Type, +#endif +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + DeviceSpecificCredentials_type, +#endif SettingsType_MAX }; @@ -27,6 +33,9 @@ public: FILE_CONFIG_type, FILE_NOTIFICATION_type, FILE_SECURITY_type, +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + FILE_DEVICE_SECURITY_type, +#endif FILE_UNKNOWN_type }; @@ -45,9 +54,9 @@ public: static int getMaxFilePos(Enum settingsType); static int getFileSize(Enum settingsType); -#ifndef BUILD_MINIMAL_OTA +#ifndef BUILD_NO_DEBUG static unsigned int getSVGcolor(Enum settingsType); -#endif // ifndef BUILD_MINIMAL_OTA +#endif static SettingsFileEnum getSettingsFile(Enum settingsType); static String getSettingsFileName(Enum settingsType, diff --git a/src/src/DataTypes/TaskValues_Data.cpp b/src/src/DataTypes/TaskValues_Data.cpp index 5b3017e99..dbc7cf748 100644 --- a/src/src/DataTypes/TaskValues_Data.cpp +++ b/src/src/DataTypes/TaskValues_Data.cpp @@ -276,7 +276,8 @@ String TaskValues_Data_t::getAsString(uint8_t varNr, Sensor_VType sensorType, u } else if (isInt32OutputDataType(sensorType)) { return String(getInt32(varNr)); } else if (isUInt64OutputDataType(sensorType)) { - return ull2String(getUint64(varNr)); + // Return unsigned 64-bit as hex string or else it could become way too long to be practical + return formatULLtoHex(getUint64(varNr)); } else if (isInt64OutputDataType(sensorType)) { return ll2String(getInt64(varNr)); #endif diff --git a/src/src/DataTypes/WiFiConnectionProtocol.cpp b/src/src/DataTypes/WiFiConnectionProtocol.cpp deleted file mode 100644 index c5a72595f..000000000 --- a/src/src/DataTypes/WiFiConnectionProtocol.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "../DataTypes/WiFiConnectionProtocol.h" - -const __FlashStringHelper * toString(WiFiConnectionProtocol proto) { - switch (proto) { - case WiFiConnectionProtocol::WiFi_Protocol_11b: - return F("802.11b"); - case WiFiConnectionProtocol::WiFi_Protocol_11g: - return F("802.11g"); -#ifdef ESP8266 - case WiFiConnectionProtocol::WiFi_Protocol_11n: - return F("802.11n"); -#endif -#ifdef ESP32 - case WiFiConnectionProtocol::WiFi_Protocol_HT20: - return F("802.11n (HT20)"); - case WiFiConnectionProtocol::WiFi_Protocol_HT40: - return F("802.11n (HT40)"); - case WiFiConnectionProtocol::WiFi_Protocol_HE20: - return F("802.11ax (HE20)"); -#if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(5, 2, 0) - case WiFiConnectionProtocol::WiFi_Protocol_11a: - return F("802.11a"); - case WiFiConnectionProtocol::WiFi_Protocol_VHT20: - return F("802.11ac (VHT20)"); -#endif - case WiFiConnectionProtocol::WiFi_Protocol_LR: - return F("802.11lr"); - -#endif - case WiFiConnectionProtocol::Unknown: - break;; - } - return F("-"); -} \ No newline at end of file diff --git a/src/src/DataTypes/WiFiConnectionProtocol.h b/src/src/DataTypes/WiFiConnectionProtocol.h deleted file mode 100644 index a605bf3ec..000000000 --- a/src/src/DataTypes/WiFiConnectionProtocol.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef DATATYPES_WIFICONNECTIONPROTOCOL_H -#define DATATYPES_WIFICONNECTIONPROTOCOL_H - -#include "../../ESPEasy_common.h" - -enum class WiFiConnectionProtocol { - Unknown, - WiFi_Protocol_11b, - WiFi_Protocol_11g, -#ifdef ESP8266 - WiFi_Protocol_11n -#endif -#ifdef ESP32 - WiFi_Protocol_HT20, - WiFi_Protocol_HT40, - WiFi_Protocol_HE20, // WiFi 6 -#if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(5, 2, 0) - WiFi_Protocol_11a, // traditional (old) 5 GHz - WiFi_Protocol_VHT20, // 5 GHz WiFi 5, 802.11ac -#endif - WiFi_Protocol_LR -#endif -}; - -const __FlashStringHelper * toString(WiFiConnectionProtocol proto); - - - -#endif \ No newline at end of file diff --git a/src/src/ESPEasyCore/Controller.cpp b/src/src/ESPEasyCore/Controller.cpp index 36a1b238c..cba1a0498 100644 --- a/src/src/ESPEasyCore/Controller.cpp +++ b/src/src/ESPEasyCore/Controller.cpp @@ -20,7 +20,7 @@ #include "../Globals/CPlugins.h" #include "../Globals/Device.h" -#include "../Globals/ESPEasyWiFiEvent.h" +#include "../../ESPEasy/net/Globals/ESPEasyWiFiEvent.h" #include "../Globals/ESPEasy_Scheduler.h" #include "../Globals/MQTT.h" #include "../Globals/Plugins.h" @@ -30,7 +30,7 @@ // #include "../Helpers/Memory.h" #include "../Helpers/Misc.h" -#include "../Helpers/Network.h" +#include "../Helpers/NetworkStatusLED.h" #include "../Helpers/PeriodicalActions.h" #include "../Helpers/PortStatus.h" @@ -43,9 +43,9 @@ constexpr pluginID_t PLUGIN_ID_MQTT_IMPORT(37); void sendData(struct EventStruct *event, bool sendEvents) { START_TIMER; - #ifndef BUILD_NO_RAM_TRACKER +#ifndef BUILD_NO_RAM_TRACKER checkRAM(F("sendData")); - #endif // ifndef BUILD_NO_RAM_TRACKER +#endif // ifndef BUILD_NO_RAM_TRACKER // LoadTaskSettings(event->TaskIndex); if (Settings.UseRules && sendEvents) { @@ -65,16 +65,17 @@ void sendData(struct EventStruct *event, bool sendEvents) Settings.Protocol[x]) { event->ControllerIndex = x; - const protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(event->ControllerIndex); - event->idx = Settings.TaskDeviceID[x][event->TaskIndex]; + event->idx = Settings.TaskDeviceID[x][event->TaskIndex]; if (validUserVar(event)) { String dummy; - CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_PROTOCOL_SEND, event, dummy); + CPluginCall(CPlugin::Function::CPLUGIN_PROTOCOL_SEND, event, dummy); } #ifndef BUILD_NO_DEBUG else { if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + const protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(event->ControllerIndex); + String log = F("Invalid value detected for controller "); log += getCPluginNameFromProtocolIndex(ProtocolIndex); addLogMove(LOG_LEVEL_DEBUG, log); @@ -130,10 +131,8 @@ void incoming_mqtt_callback(char *c_topic, uint8_t *b_payload, unsigned int leng } // TD-er: This one cannot set the TaskIndex, but that may seem to work out.... hopefully. - protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(enabledMqttController); - Scheduler.schedule_mqtt_controller_event_timer( - ProtocolIndex, + enabledMqttController, CPlugin::Function::CPLUGIN_PROTOCOL_RECV, c_topic, b_payload, length); @@ -159,29 +158,27 @@ void incoming_mqtt_callback(char *c_topic, uint8_t *b_payload, unsigned int leng void MQTTDisconnect() { if (MQTTclient.connected()) { +# if FEATURE_MQTT_CONNECT_BACKGROUND + + if (MQTT_task_data.taskHandle) { + vTaskDelete(MQTT_task_data.taskHandle); + MQTT_task_data.taskHandle = NULL; + } + MQTT_task_data.status = MQTT_connect_status_e::Disconnected; +# endif // if FEATURE_MQTT_CONNECT_BACKGROUND MQTTclient.disconnect(); addLog(LOG_LEVEL_INFO, F("MQTT : Disconnected from broker")); } updateMQTTclient_connected(); } -/*********************************************************************************************\ -* Connect to MQTT message broker -\*********************************************************************************************/ -bool MQTTConnect(controllerIndex_t controller_idx) -{ - if (MQTTclient_next_connect_attempt.isSet() && !MQTTclient_next_connect_attempt.timeoutReached(timermqtt_interval)) { - return false; - } - MQTTclient_next_connect_attempt.setNow(); - ++mqtt_reconnect_count; - +bool MQTTConnect_prepareClient(controllerIndex_t controller_idx) { MakeControllerSettings(ControllerSettings); // -V522 if (!AllocatedControllerSettings()) { - #ifndef BUILD_MINIMAL_OTA + # ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_ERROR, F("MQTT : Cannot connect, out of RAM")); - #endif + # endif // ifndef BUILD_NO_DEBUG return false; } LoadControllerSettings(controller_idx, *ControllerSettings); @@ -191,15 +188,21 @@ bool MQTTConnect(controllerIndex_t controller_idx) } if (MQTTclient.connected()) { +# if FEATURE_MQTT_CONNECT_BACKGROUND + + if (MQTT_task_data.status != MQTT_connect_status_e::Connecting) { + MQTT_task_data.status = MQTT_connect_status_e::Disconnected; + } +# endif // if FEATURE_MQTT_CONNECT_BACKGROUND MQTTclient.disconnect(); - # if FEATURE_MQTT_TLS +# if FEATURE_MQTT_TLS if (mqtt_tls != nullptr) { delete mqtt_tls; mqtt_tls = nullptr; } mqtt_rootCA.clear(); - # endif // if FEATURE_MQTT_TLS +# endif // if FEATURE_MQTT_TLS } updateMQTTclient_connected(); @@ -210,17 +213,15 @@ bool MQTTConnect(controllerIndex_t controller_idx) uint16_t mqttPort = ControllerSettings->Port; - mqtt_tls_last_errorstr.clear(); - mqtt_tls_last_error = 0; const TLS_types TLS_type = ControllerSettings->TLStype(); if ((TLS_type != TLS_types::NoTLS) && (nullptr == mqtt_tls)) { # ifdef ESP32 - # if MQTT_MAX_PACKET_SIZE > 2000 +# if MQTT_MAX_PACKET_SIZE > 2000 mqtt_tls = new BearSSL::WiFiClientSecure_light(4096, 4096); - # else // if MQTT_MAX_PACKET_SIZE > 2000 +# else // if MQTT_MAX_PACKET_SIZE > 2000 mqtt_tls = new BearSSL::WiFiClientSecure_light(2048, 2048); - # endif // if MQTT_MAX_PACKET_SIZE > 2000 +# endif // if MQTT_MAX_PACKET_SIZE > 2000 # else // ESP32 - ESP8266 mqtt_tls = new BearSSL::WiFiClientSecure_light(1024, 1024); # endif // ifdef ESP32 @@ -234,28 +235,30 @@ bool MQTTConnect(controllerIndex_t controller_idx) mqtt_tls->setUtcTime_fcn(getUnixTime); mqtt_tls->setCfgTime_fcn(get_build_unixtime); } + mqtt_tls_last_errorstr.clear(); + mqtt_tls_last_error = 0; } - switch (TLS_type) { + switch (TLS_type) + { case TLS_types::NoTLS: { // Ignoring the ACK from the server is probably set for a reason. // For example because the server does not give an acknowledgement. // This way, we always need the set amount of timeout to handle the request. // Thus we should not make the timeout dynamic here if set to ignore ack. - const uint32_t timeout = ControllerSettings->MustCheckReply - ? WiFiEventData.getSuggestedTimeout(Settings.Protocol[controller_idx], ControllerSettings->ClientTimeout) - : ControllerSettings->ClientTimeout; - # ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + const uint32_t timeout = ControllerSettings->getSuggestedTimeout(Settings.Protocol[controller_idx]); + +# ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS // See: https://github.com/espressif/arduino-esp32/pull/6676 mqtt.setTimeout((timeout + 500) / 1000); // in seconds!!!! Client *pClient = &mqtt; pClient->setTimeout(timeout); - # else // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS +# else // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS mqtt.setTimeout(timeout); // in msec as it should be! - # endif // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS +# endif // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS MQTTclient.setClient(mqtt); MQTTclient.setKeepAlive(ControllerSettings->KeepAliveTime ? ControllerSettings->KeepAliveTime : CONTROLLER_KEEP_ALIVE_TIME_DFLT); MQTTclient.setSocketTimeout(timeout); @@ -281,10 +284,10 @@ bool MQTTConnect(controllerIndex_t controller_idx) LoadCertificate(ControllerSettings->getCertificateFilename(), mqtt_rootCA); if (mqtt_rootCA.isEmpty()) { - // Fingerprint must be of some minimal length to continue. - mqtt_tls_last_errorstr = F("MQTT : No TLS root CA"); - addLog(LOG_LEVEL_ERROR, mqtt_tls_last_errorstr); - return false; + // Fingerprint must be of some minimal length to continue. + mqtt_tls_last_errorstr = F("MQTT : No TLS root CA"); + addLog(LOG_LEVEL_ERROR, mqtt_tls_last_errorstr); + return false; } @@ -331,6 +334,7 @@ bool MQTTConnect(controllerIndex_t controller_idx) mqtt_rootCA.clear(); if (mqtt_tls != nullptr) { + mqtt_tls->setTrustAnchor(Tasmota_TA, Tasmota_TA_size); mqtt_tls->setInsecure(); } break; @@ -344,9 +348,16 @@ bool MQTTConnect(controllerIndex_t controller_idx) // For example because the server does not give an acknowledgement. // This way, we always need the set amount of timeout to handle the request. // Thus we should not make the timeout dynamic here if set to ignore ack. - const uint32_t timeout = ControllerSettings->MustCheckReply - ? WiFiEventData.getSuggestedTimeout(Settings.Protocol[controller_idx], ControllerSettings->ClientTimeout) - : ControllerSettings->ClientTimeout; + const uint32_t timeout = ControllerSettings->getSuggestedTimeout(Settings.Protocol[controller_idx]); + + if (mqtt_tls_last_error == 296) { + // in this special case of cipher mismatch, we force enable ECDSA + // this would be the case for newer letsencrypt certificates now defaulting + // to EC certificates requiring ECDSA instead of RSA + mqtt_tls->setECDSA(true); + addLog(LOG_LEVEL_INFO, F("MQTT : TLS now enabling ECDSA")); + } + # ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS @@ -360,7 +371,7 @@ bool MQTTConnect(controllerIndex_t controller_idx) # ifdef ESP8266 mqtt_tls->setBufferSizes(1024, 1024); - # endif // ifdef ESP8266 +# endif // ifdef ESP8266 MQTTclient.setClient(*mqtt_tls); MQTTclient.setKeepAlive(ControllerSettings->KeepAliveTime ? ControllerSettings->KeepAliveTime : CONTROLLER_KEEP_ALIVE_TIME_DFLT); MQTTclient.setSocketTimeout(timeout); @@ -381,9 +392,7 @@ bool MQTTConnect(controllerIndex_t controller_idx) // For example because the server does not give an acknowledgement. // This way, we always need the set amount of timeout to handle the request. // Thus we should not make the timeout dynamic here if set to ignore ack. - const uint32_t timeout = ControllerSettings->MustCheckReply - ? WiFiEventData.getSuggestedTimeout(Settings.Protocol[controller_idx], ControllerSettings->ClientTimeout) - : ControllerSettings->ClientTimeout; + const uint32_t timeout = ControllerSettings->getSuggestedTimeout(Settings.Protocol[controller_idx]); # ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS @@ -401,49 +410,78 @@ bool MQTTConnect(controllerIndex_t controller_idx) # endif // if FEATURE_MQTT_TLS if (ControllerSettings->UseDNS) { +# if !defined(BUILD_NO_DEBUG) && FEATURE_MQTT_CONNECT_BACKGROUND + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("MQTT : Connecting to: %s:%u"), ControllerSettings->getHost().c_str(), ControllerSettings->Port)); + } +# endif // if !defined(BUILD_NO_DEBUG) && FEATURE_MQTT_CONNECT_BACKGROUND MQTTclient.setServer(ControllerSettings->getHost().c_str(), ControllerSettings->Port); +# if FEATURE_MQTT_TLS + + if (mqtt_tls) { + mqtt_tls->setDomainName(ControllerSettings->getHost().c_str()); + } +# endif // if FEATURE_MQTT_TLS } else { +# if !defined(BUILD_NO_DEBUG) && FEATURE_MQTT_CONNECT_BACKGROUND + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("MQTT : Connecting to: %s:%u"), + ControllerSettings->getIP().toString().c_str(), + ControllerSettings->Port)); + } +# endif // if !defined(BUILD_NO_DEBUG) && FEATURE_MQTT_CONNECT_BACKGROUND MQTTclient.setServer(ControllerSettings->getIP(), ControllerSettings->Port); } MQTTclient.setCallback(incoming_mqtt_callback); - // MQTT needs a unique clientname to subscribe to broker - const String clientid = getMQTTclientID(*ControllerSettings); - - const String LWTTopic = getLWT_topic(*ControllerSettings); - const String LWTMessageDisconnect = getLWT_messageDisconnect(*ControllerSettings); - bool MQTTresult = false; - const uint8_t willQos = 0; - const bool willRetain = ControllerSettings->mqtt_willRetain() && ControllerSettings->mqtt_sendLWT(); - const bool cleanSession = ControllerSettings->mqtt_cleanSession(); // As suggested here: - if (MQTTclient_should_reconnect) { addLog(LOG_LEVEL_ERROR, F("MQTT : Intentional reconnect")); } - const uint64_t statisticsTimerStart(getMicros64()); + return true; +} - // https://github.com/knolleary/pubsubclient/issues/458#issuecomment-493875150 - if (hasControllerCredentialsSet(controller_idx, *ControllerSettings)) { - MQTTresult = - MQTTclient.connect(clientid.c_str(), - getControllerUser(controller_idx, *ControllerSettings).c_str(), - getControllerPass(controller_idx, *ControllerSettings).c_str(), - ControllerSettings->mqtt_sendLWT() ? LWTTopic.c_str() : nullptr, - willQos, - willRetain, - ControllerSettings->mqtt_sendLWT() ? LWTMessageDisconnect.c_str() : nullptr, - cleanSession); - } else { - MQTTresult = MQTTclient.connect(clientid.c_str(), - nullptr, - nullptr, - ControllerSettings->mqtt_sendLWT() ? LWTTopic.c_str() : nullptr, - willQos, - willRetain, - ControllerSettings->mqtt_sendLWT() ? LWTMessageDisconnect.c_str() : nullptr, - cleanSession); +bool MQTTConnect_clientConnect(controllerIndex_t controller_idx) { + MakeControllerSettings(ControllerSettings); // -V522 + + if (!AllocatedControllerSettings()) { +# ifndef BUILD_MINIMAL_OTA + addLog(LOG_LEVEL_ERROR, F("MQTT : Cannot connect, out of RAM")); +# endif + return false; } + LoadControllerSettings(controller_idx, *ControllerSettings); + + bool MQTTresult = false; + + // MQTT needs a unique clientname to subscribe to broker + const String clientid = getMQTTclientID(*ControllerSettings); + + const String LWTTopic = getLWT_topic(*ControllerSettings); + const String LWTMessageDisconnect = getLWT_messageDisconnect(*ControllerSettings); + const uint8_t willQos = 0; + const bool willRetain = ControllerSettings->mqtt_willRetain() && ControllerSettings->mqtt_sendLWT(); + + // As suggested here: https://github.com/knolleary/pubsubclient/issues/458#issuecomment-493875150 + const bool cleanSession = ControllerSettings->mqtt_cleanSession(); + const bool hasCredentials = hasControllerCredentialsSet(controller_idx, *ControllerSettings); + + const uint64_t statisticsTimerStart(getMicros64()); +# if FEATURE_MQTT_TLS + mqtt_tls_last_errorstr.clear(); + mqtt_tls_last_error = 0; +#endif + MQTTresult = + MQTTclient.connect(clientid.c_str(), + hasCredentials ? getControllerUser(controller_idx, *ControllerSettings).c_str() : nullptr, + hasCredentials ? getControllerPass(controller_idx, *ControllerSettings).c_str() : nullptr, + ControllerSettings->mqtt_sendLWT() ? LWTTopic.c_str() : nullptr, + willQos, + willRetain, + ControllerSettings->mqtt_sendLWT() ? LWTMessageDisconnect.c_str() : nullptr, + cleanSession); delay(0); count_connection_results( @@ -452,17 +490,59 @@ bool MQTTConnect(controllerIndex_t controller_idx) Settings.Protocol[controller_idx], statisticsTimerStart); - # if FEATURE_MQTT_TLS + return MQTTresult; +} + +bool MQTTConnect_wrapUpConnect(controllerIndex_t controller_idx, bool MQTTresult) { + MakeControllerSettings(ControllerSettings); // -V522 + + if (!AllocatedControllerSettings()) { +# ifndef BUILD_MINIMAL_OTA + addLog(LOG_LEVEL_ERROR, F("MQTT : Cannot connect, out of RAM")); +# endif + return false; + } + LoadControllerSettings(controller_idx, *ControllerSettings); + + const String clientid = getMQTTclientID(*ControllerSettings); + const String LWTTopic = getLWT_topic(*ControllerSettings); + const bool willRetain = ControllerSettings->mqtt_willRetain() && ControllerSettings->mqtt_sendLWT(); + +# if FEATURE_MQTT_TLS + + const TLS_types TLS_type = ControllerSettings->TLStype(); if (mqtt_tls != nullptr) { - # ifdef ESP32 +# ifdef ESP32 mqtt_tls_last_error = mqtt_tls->getLastError(); mqtt_tls->clearLastError(); - # endif // ifdef ESP32 + mqtt_tls_last_cipher_suite = mqtt_tls->getLastCipherSuite(); +# endif // ifdef ESP32 + // mqtt_tls_last_errorstr = buf; + if (mqtt_tls_last_error == ERR_OOM) { mqtt_tls_last_errorstr = F("OutOfMemory"); } + + if (mqtt_tls_last_error == ERR_CANT_RESOLVE_IP) { mqtt_tls_last_errorstr = F("Can't resolve IP"); } + + if (mqtt_tls_last_error == ERR_TCP_CONNECT) { mqtt_tls_last_errorstr = F("TCP Connect error"); } + + // if (mqtt_tls_last_error == ERR_MISSING_CA) mqtt_tls_last_errorstr = F("Missing CA"); + if (mqtt_tls_last_error == ERR_TLS_TIMEOUT) { mqtt_tls_last_errorstr = F("TLS Timeout"); } + +# ifndef BUILD_NO_DEBUG + + if (BR_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 == mqtt_tls_last_cipher_suite) { + addLog(LOG_LEVEL_DEBUG, F("TLS cipher suite: ECDHE_RSA_AES_128_GCM_SHA256")); + } else if (BR_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 == mqtt_tls_last_cipher_suite) { + addLog(LOG_LEVEL_DEBUG, F("TLS cipher suite: ECDHE_ECDSA_AES_128_GCM_SHA256")); + } else if (0 != mqtt_tls_last_cipher_suite) { + addLog(LOG_LEVEL_DEBUG, strformat(F("TLS cipher suite: 0x%04X"), mqtt_tls_last_cipher_suite)); + } +# endif // ifndef BUILD_NO_DEBUG + } - # ifdef ESP32 +# ifdef ESP32 // FIXME TD-er: There seems to be no verify function in BearSSL used on ESP8266 if (TLS_type == TLS_types::TLS_FINGERPRINT) @@ -495,23 +575,23 @@ bool MQTTConnect(controllerIndex_t controller_idx) /* if (mqtt_tls != nullptr) { if (!mqtt_tls->verify( - fp.c_str(), - dn.isEmpty() ? nullptr : dn.c_str())) + fp.c_str(), + dn.isEmpty() ? nullptr : dn.c_str())) { - mqtt_tls_last_errorstr += F("TLS Fingerprint does not match"); - addLog(LOG_LEVEL_INFO, mqtt_fingerprint); - MQTTresult = false; + mqtt_tls_last_errorstr += F("TLS Fingerprint does not match"); + addLog(LOG_LEVEL_INFO, mqtt_fingerprint); + MQTTresult = false; } } */ } } - # endif // ifdef ESP32 +# endif // ifdef ESP32 - # endif // if FEATURE_MQTT_TLS +# endif // if FEATURE_MQTT_TLS if (!MQTTresult) { - # if FEATURE_MQTT_TLS +# if FEATURE_MQTT_TLS if ((mqtt_tls_last_error != 0) && loglevelActiveFor(LOG_LEVEL_ERROR)) { String log = F("MQTT : TLS error code: "); @@ -520,15 +600,21 @@ bool MQTTConnect(controllerIndex_t controller_idx) log += mqtt_tls_last_errorstr; addLog(LOG_LEVEL_ERROR, log); } - # endif // if FEATURE_MQTT_TLS +# endif // if FEATURE_MQTT_TLS +# if FEATURE_MQTT_CONNECT_BACKGROUND + + if (MQTT_task_data.status != MQTT_connect_status_e::Connecting) { + MQTT_task_data.status = MQTT_connect_status_e::Disconnected; + } +# endif // if FEATURE_MQTT_CONNECT_BACKGROUND MQTTclient.disconnect(); - # if FEATURE_MQTT_TLS +# if FEATURE_MQTT_TLS if (mqtt_tls != nullptr) { mqtt_tls->stop(); } - # endif // if FEATURE_MQTT_TLS +# endif // if FEATURE_MQTT_TLS updateMQTTclient_connected(); @@ -540,8 +626,8 @@ bool MQTTConnect(controllerIndex_t controller_idx) addLogMove(LOG_LEVEL_INFO, concat(F("MQTT : Connected to broker with client ID: "), clientid)); } - # if FEATURE_MQTT_TLS - # ifdef ESP32 +# if FEATURE_MQTT_TLS +# ifdef ESP32 // FIXME TD-er: Must get certificate info @@ -556,17 +642,17 @@ bool MQTTConnect(controllerIndex_t controller_idx) addLogMove(LOG_LEVEL_INFO, log); } */ - # endif // ifdef ESP32 - # endif // if FEATURE_MQTT_TLS +# endif // ifdef ESP32 +# endif // if FEATURE_MQTT_TLS MQTTparseSystemVariablesAndSubscribe(String(ControllerSettings->Subscribe)); - # if FEATURE_MQTT_DISCOVER +# if FEATURE_MQTT_DISCOVER if (ControllerSettings->mqtt_autoDiscovery()) { MQTTparseSystemVariablesAndSubscribe(String(ControllerSettings->MqttAutoDiscoveryTrigger)); } - # endif // if FEATURE_MQTT_DISCOVER +# endif // if FEATURE_MQTT_DISCOVER updateMQTTclient_connected(); statusLED(true); @@ -587,6 +673,26 @@ bool MQTTConnect(controllerIndex_t controller_idx) return true; } +/*********************************************************************************************\ +* Connect to MQTT message broker +\*********************************************************************************************/ +bool MQTTConnect(controllerIndex_t controller_idx) +{ + if (MQTTclient_next_connect_attempt.isSet() && !MQTTclient_next_connect_attempt.timeoutReached(timermqtt_interval)) { + return false; + } + MQTTclient_next_connect_attempt.setNow(); + ++mqtt_reconnect_count; + + if (!MQTTConnect_prepareClient(controller_idx)) { + return false; + } + + bool MQTTresult = MQTTConnect_clientConnect(controller_idx); + + return MQTTConnect_wrapUpConnect(controller_idx, MQTTresult); +} + void MQTTparseSystemVariablesAndSubscribe(String subscribeTo) { if (subscribeTo.isEmpty()) { return; } parseSystemVariables(subscribeTo, false); @@ -594,10 +700,12 @@ void MQTTparseSystemVariablesAndSubscribe(String subscribeTo) { if (!subscribeTo.isEmpty()) { MQTTclient.subscribe(subscribeTo.c_str()); +# ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, concat(F("Subscribed to: "), subscribeTo)); + addLogMove(LOG_LEVEL_INFO, concat(F("MQTT : Subscribed to: "), subscribeTo)); } +# endif // ifndef BUILD_NO_DEBUG } } @@ -611,18 +719,154 @@ String getMQTTclientID(const ControllerSettingsStruct& ControllerSettings) { parseSystemVariables(clientid, false); clientid.replace(' ', '_'); // Make sure no spaces are present in the client ID - if ((WiFiEventData.wifi_reconnects >= 1) && ControllerSettings.mqtt_uniqueMQTTclientIdReconnect()) { + if ( // (WiFiEventData.wifi_reconnects >= 1) && + ControllerSettings.mqtt_uniqueMQTTclientIdReconnect()) { // Work-around for 'lost connections' to the MQTT broker. // If the broker thinks the connection is still alive, a reconnect from the // client will be refused. // To overcome this issue, append the number of reconnects to the client ID to // make it different from the previous one. + static int id{}; clientid += '_'; - clientid += WiFiEventData.wifi_reconnects; + clientid += ++id; // WiFiEventData.wifi_reconnects; } return clientid; } +# if FEATURE_MQTT_CONNECT_BACKGROUND + +void MQTT_execute_connect_task(void *parameter) +{ + MQTT_connect_request*MQTT_task_data = static_cast(parameter); + + uint32_t timeout = MQTT_task_data->timeout; + uint8_t incr = 5; // Increment timeout between connection attempts 5 times by 100 msec + + while (ESPEasy::net::NetworkConnected() && + !MQTTConnect_clientConnect(MQTT_task_data->ControllerIndex) && + !MQTTclient.connected()) { + const TickType_t xDelay = timeout / portTICK_PERIOD_MS; + vTaskDelay(xDelay); // Use regular controller timeout also for delay between connection attempts (range 10..4000) + + if (incr > 0) { + timeout += 100; // Increment next few (5) times with 100 msec + incr--; + } + + if (timePassedSince(MQTT_task_data->startTime) > 120000) { // Quit after 120 seconds + break; + } + } + MQTT_task_data->result = MQTTclient.connected(); + MQTT_task_data->status = MQTT_task_data->result +? MQTT_connect_status_e::Connected +: MQTT_connect_status_e::Failure; + MQTT_task_data->endTime = millis(); + MQTT_task_data->taskHandle = NULL; + vTaskDelete(MQTT_task_data->taskHandle); +} + +bool MQTTConnectInBackground(controllerIndex_t controller_idx, bool reportOnly) { + if (!Settings.MQTTConnectInBackground()) { return false; } + + if ((MQTT_task_data.status == MQTT_connect_status_e::Connected) || (MQTT_task_data.status == MQTT_connect_status_e::Failure)) { + MQTT_task_data.status = MQTT_connect_status_e::Ready; // Set status first to avoid re-entry + + const bool result = MQTTConnect_wrapUpConnect(MQTT_task_data.ControllerIndex, MQTT_task_data.result); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("MQTT : Background Connect request %s, took %d msec"), + FsP(MQTT_task_data.result ? F("success") : F("FAILED")), + MQTT_task_data.endTime - MQTT_task_data.startTime + )); + } + + return result && MQTT_task_data.result; + } + + if ((MQTT_task_data.status == MQTT_connect_status_e::Ready) && MQTTclient.connected()) { + if (!MQTT_task_data.logged && loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, F("MQTT : Background Connect request Ready")); + } + MQTT_task_data.logged = true; + return MQTT_task_data.result; + } + + if (MQTT_task_data.status == MQTT_connect_status_e::Connecting) { + if (timePassedSince(MQTT_task_data.loopTime) > 1000) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("MQTT : Background waiting to Connect, %d sec"), + timePassedSince(MQTT_task_data.startTime) / 1000 + )); + } + MQTT_task_data.loopTime = millis(); + } + return false; // Not ready yet + } + + if ((MQTT_task_data.status == MQTT_connect_status_e::Ready) && + !MQTTclient.connected() && + NetworkConnected(10)) { // Unexpected network disconnect and reconnect? + MQTT_task_data.status = MQTT_connect_status_e::Disconnected; + reportOnly = false; // Reconnect ASAP + + if (CONTROLLER_MAX == controller_idx) { + controller_idx = firstEnabledMQTT_ControllerIndex(); + } + } + + if (!reportOnly && (MQTT_task_data.status == MQTT_connect_status_e::Disconnected) && (controller_idx < CONTROLLER_MAX)) { + if (MQTTclient_next_connect_attempt.isSet() && !MQTTclient_next_connect_attempt.timeoutReached(timermqtt_interval)) { + return false; + } + MQTTclient_next_connect_attempt.setNow(); + ++mqtt_reconnect_count; + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("MQTT : Start background connect for controller %d"), controller_idx + 1)); + } + MQTT_task_data.result = false; + + if (MQTTConnect_prepareClient(controller_idx)) { + MakeControllerSettings(ControllerSettings); // -V522 + + if (!AllocatedControllerSettings()) { +# ifndef BUILD_MINIMAL_OTA + addLog(LOG_LEVEL_ERROR, F("MQTT : Cannot load Controller settings, out of RAM")); +# endif + return false; + } + LoadControllerSettings(controller_idx, *ControllerSettings); + + uint32_t timeout = ControllerSettings->MustCheckReply +? ControllerSettings->getSuggestedTimeout(Settings.Protocol[MQTT_task_data.ControllerIndex]) +: ControllerSettings->ClientTimeout; + + MQTT_task_data.status = MQTT_connect_status_e::Connecting; + MQTT_task_data.logged = false; + MQTT_task_data.ControllerIndex = controller_idx; + MQTT_task_data.startTime = millis(); + MQTT_task_data.loopTime = millis(); + MQTT_task_data.timeout = timeout; + + xTaskCreatePinnedToCore( + MQTT_execute_connect_task, // Function that should be called + "MQTTClient.connect()", // Name of the task (for debugging) + 8192, // Stack size (bytes) + &MQTT_task_data, // Parameter to pass + 1, // Task priority + &MQTT_task_data.taskHandle, // Task handle + xPortGetCoreID() // Core you want to run the task on (0 or 1) + ); + } else { + MQTT_task_data.status = MQTT_connect_status_e::Failure; + } + } + return false; +} + +# endif // if FEATURE_MQTT_CONNECT_BACKGROUND + /*********************************************************************************************\ * Check connection MQTT message broker \*********************************************************************************************/ @@ -658,7 +902,7 @@ bool MQTTCheck(controllerIndex_t controller_idx) #ifdef USES_ESPEASY_NOW if (!MQTTclient.connected()) { if (ControllerSettings->enableESPEasyNowFallback()) { - return true; + return true; } } #endif @@ -678,6 +922,12 @@ bool MQTTCheck(controllerIndex_t controller_idx) if (MQTTclient_should_reconnect || !MQTTclient.connected()) { +# if FEATURE_MQTT_CONNECT_BACKGROUND + + if (Settings.MQTTConnectInBackground()) { + return MQTTConnectInBackground(controller_idx, false); + } +# endif // ifdef ESP32 return MQTTConnect(controller_idx); } @@ -747,36 +997,25 @@ String getLWT_messageDisconnect(const ControllerSettingsStruct& ControllerSettin * Send status info to request source \*********************************************************************************************/ void SendStatusOnlyIfNeeded(struct EventStruct *event, bool param1, uint32_t key, const String& param2, int16_t param3) { - if (SourceNeedsStatusUpdate(event->Source)) { + if (EventValueSource::SourceNeedsStatusUpdate(event->Source)) { SendStatus(event, getPinStateJSON(param1, key, param2, param3)); printToWeb = false; // SP: 2020-06-12: to avoid to add more info to a JSON structure } } -bool SourceNeedsStatusUpdate(EventValueSource::Enum eventSource) -{ - switch (eventSource) { - case EventValueSource::Enum::VALUE_SOURCE_HTTP: - case EventValueSource::Enum::VALUE_SOURCE_SERIAL: - case EventValueSource::Enum::VALUE_SOURCE_MQTT: - case EventValueSource::Enum::VALUE_SOURCE_WEB_FRONTEND: - return true; - - default: - break; - } - return false; -} - -void SendStatus(struct EventStruct *event, const __FlashStringHelper *status) -{ - SendStatus(event, String(status)); -} +void SendStatus(struct EventStruct *event, const __FlashStringHelper *status) { SendStatus(event, String(status)); } void SendStatus(struct EventStruct *event, const String& status) { if (status.isEmpty()) { return; } +#if FEATURE_COLORIZE_CONSOLE_LOGS + if (EventValueSource::isExternalSource(event->Source)) { + addLog(LOG_LEVEL_NONE, status); + } +#endif + + switch (event->Source) { case EventValueSource::Enum::VALUE_SOURCE_HTTP: @@ -792,7 +1031,9 @@ void SendStatus(struct EventStruct *event, const String& status) break; #endif // if FEATURE_MQTT case EventValueSource::Enum::VALUE_SOURCE_SERIAL: +#if !FEATURE_COLORIZE_CONSOLE_LOGS serialPrintln(status); +#endif break; default: @@ -801,6 +1042,7 @@ void SendStatus(struct EventStruct *event, const String& status) } #if FEATURE_MQTT + controllerIndex_t firstEnabledMQTT_ControllerIndex() { for (controllerIndex_t i = 0; i < CONTROLLER_MAX; ++i) { protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(i); @@ -843,11 +1085,12 @@ bool MQTTpublish(controllerIndex_t controller_idx, } String topic_str; String payload_str; + if (!reserve_special(topic_str, strlen_P(topic)) || !reserve_special(payload_str, strlen_P(payload))) { return false; } - topic_str = topic; + topic_str = topic; payload_str = payload; bool success = false; @@ -858,9 +1101,9 @@ bool MQTTpublish(controllerIndex_t controller_idx, if (ptr != nullptr) { success = MQTTDelayHandler->addToQueue( - std::unique_ptr( + UP_MQTT_queue_element ( new (ptr) MQTT_queue_element( - controller_idx, taskIndex, + controller_idx, taskIndex, std::move(topic_str), std::move(payload_str), retained, callbackTask))); @@ -890,9 +1133,9 @@ bool MQTTpublish(controllerIndex_t controller_idx, void *ptr = special_calloc(1, size); if (ptr != nullptr) { - success = + success = MQTTDelayHandler->addToQueue( - std::unique_ptr( + UP_MQTT_queue_element ( new (ptr) MQTT_queue_element( controller_idx, taskIndex, std::move(topic), @@ -950,9 +1193,10 @@ void MQTTStatus(struct EventStruct *event, const String& status) } # if FEATURE_MQTT_TLS + bool GetTLSfingerprint(String& fp) { - # ifdef ESP32 +# ifdef ESP32 if (MQTTclient_connected && (mqtt_tls != nullptr)) { const uint8_t *recv_fingerprint = mqtt_tls->getRecvPubKeyFingerprint(); @@ -963,7 +1207,8 @@ bool GetTLSfingerprint(String& fp) for (size_t i = 0; i < 21; ++i) { const String tmp(recv_fingerprint[i], HEX); - switch (tmp.length()) { + switch (tmp.length()) + { case 0: fp += '0'; @@ -978,13 +1223,13 @@ bool GetTLSfingerprint(String& fp) return true; } } - # endif // ifdef ESP32 +# endif // ifdef ESP32 return false; } bool GetTLS_Certificate(String& cert, bool caRoot) { - # ifdef ESP32 +# ifdef ESP32 // FIXME TD-er: Implement retrieval of certificate @@ -994,11 +1239,11 @@ bool GetTLS_Certificate(String& cert, bool caRoot) String subject; if (mqtt_tls->getPeerCertificate(cert, subject, caRoot) == 0) { - return true; + return true; } } */ - # endif // ifdef ESP32 +# endif // ifdef ESP32 return false; } @@ -1006,28 +1251,26 @@ bool GetTLS_Certificate(String& cert, bool caRoot) #endif // if FEATURE_MQTT - /*********************************************************************************************\ * send specific sensor task data, effectively calling PluginCall(PLUGIN_READ...) \*********************************************************************************************/ -void SensorSendTask(struct EventStruct *event, unsigned long timestampUnixTime) -{ +void SensorSendTask(struct EventStruct *event, unsigned long timestampUnixTime) { SensorSendTask(event, timestampUnixTime, millis()); } void SensorSendTask(struct EventStruct *event, unsigned long timestampUnixTime, unsigned long lasttimer) { - if (!validTaskIndex(event->TaskIndex)) { - return; + if (!validTaskIndex(event->TaskIndex)) { + return; } // FIXME TD-er: Should a 'disabled' task be rescheduled? // If not, then it should be rescheduled after the check to see if it is enabled. Scheduler.reschedule_task_device_timer(event->TaskIndex, lasttimer); - #ifndef BUILD_NO_RAM_TRACKER +#ifndef BUILD_NO_RAM_TRACKER checkRAM(F("SensorSendTask")); - #endif // ifndef BUILD_NO_RAM_TRACKER +#endif // ifndef BUILD_NO_RAM_TRACKER if (Settings.TaskDeviceEnabled[event->TaskIndex]) { diff --git a/src/src/ESPEasyCore/Controller.h b/src/src/ESPEasyCore/Controller.h index 91825acd0..21e4f59d7 100644 --- a/src/src/ESPEasyCore/Controller.h +++ b/src/src/ESPEasyCore/Controller.h @@ -29,6 +29,11 @@ void MQTTDisconnect(); /*********************************************************************************************\ * Connect to MQTT message broker \*********************************************************************************************/ +#if FEATURE_MQTT_CONNECT_BACKGROUND +bool MQTTConnectInBackground(controllerIndex_t controller_idx, + bool reportOnly); +#endif // if FEATURE_MQTT_CONNECT_BACKGROUND + bool MQTTConnect(controllerIndex_t controller_idx); void MQTTparseSystemVariablesAndSubscribe(String subscribeTo); @@ -54,8 +59,6 @@ String getLWT_messageDisconnect(const ControllerSettingsStruct& ControllerSettin \*********************************************************************************************/ void SendStatusOnlyIfNeeded(struct EventStruct *event, bool param1, uint32_t key, const String& param2, int16_t param3); -bool SourceNeedsStatusUpdate(EventValueSource::Enum eventSource); - void SendStatus(struct EventStruct *event, const __FlashStringHelper * status); void SendStatus(struct EventStruct *event, const String& status); diff --git a/src/src/ESPEasyCore/ESPEasyEth.cpp b/src/src/ESPEasyCore/ESPEasyEth.cpp deleted file mode 100644 index c0b8a8ac9..000000000 --- a/src/src/ESPEasyCore/ESPEasyEth.cpp +++ /dev/null @@ -1,372 +0,0 @@ -#include "../ESPEasyCore/ESPEasyEth.h" - -#if FEATURE_ETHERNET - -#include "../CustomBuild/ESPEasyLimits.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyWifi.h" -#include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/ESPEasyGPIO.h" -#include "../ESPEasyCore/ESPEasyEthEvent.h" -#include "../Globals/ESPEasyEthEvent.h" -#include "../Globals/NetworkState.h" -#include "../Globals/Settings.h" -#include "../Helpers/Hardware_GPIO.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/Networking.h" - -#include -#include -#if ESP_IDF_VERSION_MAJOR > 3 - #include -#else - #include -#endif - -#include - -bool ethUseStaticIP() { - return Settings.ETH_IP[0] != 0 && Settings.ETH_IP[0] != 255; -} - -void ethSetupStaticIPconfig() { - const IPAddress IP_zero(0, 0, 0, 0); - if (!ethUseStaticIP()) { - if (!ETH.config(IP_zero, IP_zero, IP_zero, IP_zero)) { - addLog(LOG_LEVEL_ERROR, F("ETH : Cannot set IP config")); - } - return; - } - const IPAddress ip = Settings.ETH_IP; - const IPAddress gw = Settings.ETH_Gateway; - const IPAddress subnet = Settings.ETH_Subnet; - const IPAddress dns = Settings.ETH_DNS; - - EthEventData.dns0_cache = dns; - EthEventData.dns1_cache = IP_zero; - - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("ETH IP : Static IP : "); - log += formatIP(ip); - log += F(" GW: "); - log += formatIP(gw); - log += F(" SN: "); - log += formatIP(subnet); - log += F(" DNS: "); - log += formatIP(dns); - addLogMove(LOG_LEVEL_INFO, log); - } - ETH.config(ip, gw, subnet, dns); - setDNS(0, EthEventData.dns0_cache); - setDNS(1, EthEventData.dns1_cache); -} - -bool ethCheckSettings() { - return isValid(Settings.ETH_Phy_Type) -#if CONFIG_ETH_USE_ESP32_EMAC - && (isValid(Settings.ETH_Clock_Mode)/* || isSPI_EthernetType(Settings.ETH_Phy_Type)*/) -#endif - && isValid(Settings.NetworkMedium) - && validGpio(Settings.ETH_Pin_mdc_cs) - && (isSPI_EthernetType(Settings.ETH_Phy_Type) || - ( validGpio(Settings.ETH_Pin_mdio_irq) && - (validGpio(Settings.ETH_Pin_power_rst) || (Settings.ETH_Pin_power_rst == -1)) - ) - ); // Some boards have fixed power -} - -bool ethPrepare() { - char hostname[40]; - safe_strncpy(hostname, NetworkCreateRFCCompliantHostname().c_str(), sizeof(hostname)); - ETH.setHostname(hostname); - ethSetupStaticIPconfig(); - return true; -} - -void ethPrintSettings() { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; - if (log.reserve(115)) { -// log += F("ETH/Wifi mode: "); -// log += toString(active_network_medium); - log += F("ETH PHY Type: "); - log += toString(Settings.ETH_Phy_Type); - log += F(" PHY Addr: "); - log += Settings.ETH_Phy_Addr; - - if (!isSPI_EthernetType(Settings.ETH_Phy_Type)) { - log += F(" Eth Clock mode: "); - log += toString(Settings.ETH_Clock_Mode); - } - log += strformat(isSPI_EthernetType(Settings.ETH_Phy_Type) - ? F(" CS: %d IRQ: %d RST: %d") : F(" MDC: %d MIO: %d PWR: %d"), - Settings.ETH_Pin_mdc_cs, - Settings.ETH_Pin_mdio_irq, - Settings.ETH_Pin_power_rst); - addLogMove(LOG_LEVEL_INFO, log); - } - } -} - -MAC_address ETHMacAddress() { - MAC_address mac; - if(!EthEventData.ethInitSuccess) { - addLog(LOG_LEVEL_ERROR, F("Call NetworkMacAddress() only on connected Ethernet!")); - } else { - #if ESP_IDF_VERSION_MAJOR > 3 - ETH.macAddress(mac.mac); - #else - esp_eth_get_mac(mac.mac); - #endif - } - return mac; -} - -void removeEthEventHandler() -{ - WiFi.removeEvent(EthEventData.wm_event_id); - EthEventData.wm_event_id = 0; -} - -void registerEthEventHandler() -{ - if (EthEventData.wm_event_id != 0) { - removeEthEventHandler(); - } - EthEventData.wm_event_id = WiFi.onEvent(EthEvent); -} - - -bool ETHConnectRelaxed() { - if (EthEventData.ethInitSuccess) { - return EthLinkUp(); - } - ethPrintSettings(); - if (!ethCheckSettings()) - { - addLog(LOG_LEVEL_ERROR, F("ETH: Settings not correct!!!")); - EthEventData.ethInitSuccess = false; - return false; - } - // Re-register event listener - removeEthEventHandler(); - - ethPower(true); - EthEventData.markEthBegin(); - - // Re-register event listener - registerEthEventHandler(); - - if (!EthEventData.ethInitSuccess) { -#if ESP_IDF_VERSION_MAJOR < 5 - EthEventData.ethInitSuccess = ETH.begin( - Settings.ETH_Phy_Addr, - Settings.ETH_Pin_power_rst, - Settings.ETH_Pin_mdc_cs, - Settings.ETH_Pin_mdio_irq, - (eth_phy_type_t)Settings.ETH_Phy_Type, - (eth_clock_mode_t)Settings.ETH_Clock_Mode); -#else -#if FEATURE_USE_IPV6 - if (Settings.EnableIPv6()) { - ETH.enableIPv6(true); - } -#endif - - if (isSPI_EthernetType(Settings.ETH_Phy_Type)) { - spi_host_device_t SPI_host = Settings.getSPI_host(); - if (SPI_host == spi_host_device_t::SPI_HOST_MAX) { - addLog(LOG_LEVEL_ERROR, F("SPI not enabled")); - #ifdef ESP32C3 - // FIXME TD-er: Fallback for ETH01-EVO board - SPI_host = spi_host_device_t::SPI2_HOST; - Settings.InitSPI = static_cast(SPI_Options_e::UserDefined); - Settings.SPI_SCLK_pin = 7; - Settings.SPI_MISO_pin = 3; - Settings.SPI_MOSI_pin = 10; - #endif - } - // else - { -#if ETH_SPI_SUPPORTS_CUSTOM - EthEventData.ethInitSuccess = ETH.begin( - to_ESP_phy_type(Settings.ETH_Phy_Type), - Settings.ETH_Phy_Addr, - Settings.ETH_Pin_mdc_cs, - Settings.ETH_Pin_mdio_irq, - Settings.ETH_Pin_power_rst, - SPI); -#else - EthEventData.ethInitSuccess = ETH.begin( - to_ESP_phy_type(Settings.ETH_Phy_Type), - Settings.ETH_Phy_Addr, - Settings.ETH_Pin_mdc_cs, - Settings.ETH_Pin_mdio_irq, - Settings.ETH_Pin_power_rst, - SPI_host, - static_cast(Settings.SPI_SCLK_pin), - static_cast(Settings.SPI_MISO_pin), - static_cast(Settings.SPI_MOSI_pin)); -#endif - } - } else { -# if CONFIG_ETH_USE_ESP32_EMAC - ethResetGPIOpins(); - EthEventData.ethInitSuccess = ETH.begin( - to_ESP_phy_type(Settings.ETH_Phy_Type), - Settings.ETH_Phy_Addr, - Settings.ETH_Pin_mdc_cs, - Settings.ETH_Pin_mdio_irq, - Settings.ETH_Pin_power_rst, - (eth_clock_mode_t)Settings.ETH_Clock_Mode); -#endif - } - -#endif - } - if (EthEventData.ethInitSuccess) { - // FIXME TD-er: Not sure if this is correctly set to false - //EthEventData.ethConnectAttemptNeeded = false; - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { -#if ESP_IDF_VERSION_MAJOR < 5 - addLog(LOG_LEVEL_INFO, strformat( - F("ETH : MAC: %s speed: %dM %s Link: %s"), - ETH.macAddress().c_str(), - ETH.linkSpeed(), - String(ETH.fullDuplex() ? F("Full Duplex") : F("Half Duplex")).c_str(), - String(ETH.linkUp() ? F("Up") : F("Down")).c_str())); -#else - addLog(LOG_LEVEL_INFO, strformat( - F("ETH : MAC: %s phy addr: %d speed: %dM %s Link: %s"), - ETH.macAddress().c_str(), - ETH.phyAddr(), - ETH.linkSpeed(), - concat( - ETH.fullDuplex() ? F("Full Duplex") : F("Half Duplex"), - ETH.autoNegotiation() ? F("(auto)") : F("")).c_str(), - String(ETH.linkUp() ? F("Up") : F("Down")).c_str())); -#endif - } - - if (EthLinkUp()) { - // We might miss the connected event, since we are already connected. - EthEventData.markConnected(); - } - } else { - addLog(LOG_LEVEL_ERROR, F("ETH : Failed to initialize ETH")); - } - return EthEventData.ethInitSuccess; -} - -void ethPower(bool enable) { - if (isSPI_EthernetType(Settings.ETH_Phy_Type)) - return; - if (Settings.ETH_Pin_power_rst != -1) { - if (GPIO_Internal_Read(Settings.ETH_Pin_power_rst) == enable) { - // Already the desired state - return; - } - addLog(LOG_LEVEL_INFO, enable ? F("ETH power ON") : F("ETH power OFF")); - if (!enable) { - EthEventData.ethInitSuccess = false; - EthEventData.clearAll(); - #ifdef ESP_IDF_VERSION_MAJOR - // FIXME TD-er: See: https://github.com/espressif/arduino-esp32/issues/6105 - // Need to store the last link state, as it will be cleared after destructing the object. - EthEventData.setEthDisconnected(); - if (ETH.linkUp()) { - EthEventData.setEthConnected(); - } - #endif -// ETH = ETHClass(); - } - if (enable) { -// ethResetGPIOpins(); - } -// gpio_reset_pin((gpio_num_t)Settings.ETH_Pin_power_rst); - - GPIO_Write(PLUGIN_GPIO, Settings.ETH_Pin_power_rst, enable ? 1 : 0); - if (!enable) { - if (Settings.ETH_Clock_Mode == EthClockMode_t::Ext_crystal_osc) { - delay(600); // Give some time to discharge any capacitors - // Delay is needed to make sure no clock signal remains present which may cause the ESP to boot into flash mode. - } - } else { - delay(400); // LAN chip needs to initialize before calling Eth.begin() - } - } -} - -void ethResetGPIOpins() { - if (isSPI_EthernetType(Settings.ETH_Phy_Type)) - return; - - // fix an disconnection issue after rebooting Olimex POE - this forces a clean state for all GPIO involved in RMII - // Thanks to @s-hadinger and @Jason2866 - // Resetting state of power pin is done in ethPower() - addLog(LOG_LEVEL_INFO, F("ethResetGPIOpins()")); - gpio_reset_pin((gpio_num_t)Settings.ETH_Pin_mdc_cs); - gpio_reset_pin((gpio_num_t)Settings.ETH_Pin_mdio_irq); -# if CONFIG_ETH_USE_ESP32_EMAC - gpio_reset_pin(GPIO_NUM_19); // EMAC_TXD0 - hardcoded - gpio_reset_pin(GPIO_NUM_21); // EMAC_TX_EN - hardcoded - gpio_reset_pin(GPIO_NUM_22); // EMAC_TXD1 - hardcoded - gpio_reset_pin(GPIO_NUM_25); // EMAC_RXD0 - hardcoded - gpio_reset_pin(GPIO_NUM_26); // EMAC_RXD1 - hardcoded - gpio_reset_pin(GPIO_NUM_27); // EMAC_RX_CRS_DV - hardcoded -#endif - /* - switch (Settings.ETH_Clock_Mode) { - case EthClockMode_t::Ext_crystal_osc: // ETH_CLOCK_GPIO0_IN - case EthClockMode_t::Int_50MHz_GPIO_0: // ETH_CLOCK_GPIO0_OUT - gpio_reset_pin(GPIO_NUM_0); - break; - case EthClockMode_t::Int_50MHz_GPIO_16: // ETH_CLOCK_GPIO16_OUT - gpio_reset_pin(GPIO_NUM_16); - break; - case EthClockMode_t::Int_50MHz_GPIO_17_inv: // ETH_CLOCK_GPIO17_OUT - gpio_reset_pin(GPIO_NUM_17); - break; - } - */ - delay(1); -} - -bool ETHConnected() { - if (EthEventData.EthServicesInitialized()) { - if (EthLinkUp()) { - return true; - } - // Apparently we missed an event - EthEventData.processedDisconnect = false; - } else if (EthEventData.ethInitSuccess) { - if (EthLinkUp()) { - EthEventData.setEthConnected(); - if (NetworkLocalIP() != IPAddress(0, 0, 0, 0) && - !EthEventData.EthGotIP()) { - EthEventData.processedGotIP = false; - } - if (EthEventData.lastConnectMoment.isSet()) { - if (!EthEventData.EthServicesInitialized()) { - if (EthEventData.lastConnectMoment.millisPassedSince() > 10000 && - EthEventData.lastGetIPmoment.isSet()) { - EthEventData.processedGotIP = false; - EthEventData.markLostIP(); - } - } - } - return EthEventData.EthServicesInitialized(); - } else { - if (EthEventData.last_eth_connect_attempt_moment.isSet() && - EthEventData.last_eth_connect_attempt_moment.millisPassedSince() < 5000) { - return false; - } - setNetworkMedium(NetworkMedium_t::WIFI); - } - } - return false; -} - -#endif // if FEATURE_ETHERNET \ No newline at end of file diff --git a/src/src/ESPEasyCore/ESPEasyEth.h b/src/src/ESPEasyCore/ESPEasyEth.h deleted file mode 100644 index f1a1f18aa..000000000 --- a/src/src/ESPEasyCore/ESPEasyEth.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef ESPEASY_ETH_H -#define ESPEASY_ETH_H - -#include "../../ESPEasy_common.h" - -#if FEATURE_ETHERNET - -#include "../DataStructs/MAC_address.h" - -#include - -bool ethUseStaticIP(); -void ethSetupStaticIPconfig(); -bool ethCheckSettings(); -bool ethPrepare(); -void ethPrintSettings(); -bool ETHConnectRelaxed(); -bool ETHConnected(); -void ethPower(bool enable); -void ethResetGPIOpins(); -MAC_address ETHMacAddress(); - -void removeEthEventHandler(); -void registerEthEventHandler(); - -#endif // if FEATURE_ETHERNET -#endif // ifndef ESPEASY_ETH_H diff --git a/src/src/ESPEasyCore/ESPEasyEthEvent.cpp b/src/src/ESPEasyCore/ESPEasyEthEvent.cpp deleted file mode 100644 index 8f69a1417..000000000 --- a/src/src/ESPEasyCore/ESPEasyEthEvent.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#include "../ESPEasyCore/ESPEasyEthEvent.h" - -#ifdef ESP32 -# if FEATURE_ETHERNET -# include - -#include "../ESPEasyCore/ESPEasyEth.h" -#include "../Globals/ESPEasyEthEvent.h" - - -// ******************************************************************************** -// Functions called on events. ** WARNING ** -// Make sure not to call anything in these functions that result in delay() or yield() -// ******************************************************************************** - -# include - - -# if ESP_IDF_VERSION_MAJOR > 3 -void EthEvent(WiFiEvent_t event, arduino_event_info_t info) { - switch (event) { - case ARDUINO_EVENT_ETH_START: - - if (ethPrepare()) { -// addLog(LOG_LEVEL_INFO, F("ETH event: Started")); -// } else { -// addLog(LOG_LEVEL_ERROR, F("ETH event: Could not prepare ETH!")); - } - break; - case ARDUINO_EVENT_ETH_CONNECTED: -// addLog(LOG_LEVEL_INFO, F("ETH event: Connected")); - EthEventData.markConnected(); - break; - case ARDUINO_EVENT_ETH_GOT_IP: - EthEventData.markGotIP(); -// addLog(LOG_LEVEL_INFO, F("ETH event: Got IP")); - break; - case ARDUINO_EVENT_ETH_DISCONNECTED: -// addLog(LOG_LEVEL_ERROR, F("ETH event: Disconnected")); - EthEventData.markDisconnect(); - break; - case ARDUINO_EVENT_ETH_STOP: -// addLog(LOG_LEVEL_INFO, F("ETH event: Stopped")); - break; - # if ESP_IDF_VERSION_MAJOR > 3 - case ARDUINO_EVENT_ETH_GOT_IP6: - # else // if ESP_IDF_VERSION_MAJOR > 3 - case ARDUINO_EVENT_GOT_IP6: - # endif // if ESP_IDF_VERSION_MAJOR > 3 - #if FEATURE_USE_IPV6 - { - ip_event_got_ip6_t * event = static_cast(&info.got_ip6); - IPAddress ip(IPv6, (const uint8_t*)event->ip6_info.ip.addr, event->ip6_info.ip.zone); - EthEventData.markGotIPv6(ip); -// addLog(LOG_LEVEL_INFO, String(F("ETH event: Got IP6 ")) + ip.toString(true)); - } - #else -// addLog(LOG_LEVEL_INFO, F("ETH event: Got IP6")); - #endif - break; - default: - { - break; - } - } -} - -# endif // if ESP_IDF_VERSION_MAJOR > 3 - -# endif // if FEATURE_ETHERNET - -#endif // ifdef ESP32 diff --git a/src/src/ESPEasyCore/ESPEasyEthEvent.h b/src/src/ESPEasyCore/ESPEasyEthEvent.h deleted file mode 100644 index 620f672b1..000000000 --- a/src/src/ESPEasyCore/ESPEasyEthEvent.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef ESPEASY_ETH_EVENT_H -#define ESPEASY_ETH_EVENT_H - -#include "../../ESPEasy_common.h" - - -// ******************************************************************************** -// Functions called on Eth events. -// Make sure not to call anything in these functions that result in delay() or yield() -// ******************************************************************************** -#ifdef ESP32 -#if FEATURE_ETHERNET -#if ESP_IDF_VERSION_MAJOR > 3 -#include -#include -void EthEvent(WiFiEvent_t event, arduino_event_info_t info); -#endif -#endif -#endif - - -#endif // ESPEASY_ETH_EVENT_H \ No newline at end of file diff --git a/src/src/ESPEasyCore/ESPEasyEth_ProcessEvent.cpp b/src/src/ESPEasyCore/ESPEasyEth_ProcessEvent.cpp deleted file mode 100644 index 50e35132c..000000000 --- a/src/src/ESPEasyCore/ESPEasyEth_ProcessEvent.cpp +++ /dev/null @@ -1,279 +0,0 @@ -#include "../ESPEasyCore/ESPEasyEth_ProcessEvent.h" - -#if FEATURE_ETHERNET - -# include "../../ESPEasy-Globals.h" - -# include "../ESPEasyCore/ESPEasyEth.h" -# include "../ESPEasyCore/ESPEasyNetwork.h" -# include "../ESPEasyCore/ESPEasyWifi.h" // LogConnectionStatus - -# include "../Globals/ESPEasyEthEvent.h" -# include "../Globals/ESPEasyWiFiEvent.h" -# include "../Globals/ESPEasy_Scheduler.h" -# include "../Globals/ESPEasy_time.h" -# include "../Globals/EventQueue.h" -# include "../Globals/MQTT.h" -# include "../Globals/NetworkState.h" -# include "../Globals/Settings.h" - -# include "../Helpers/LongTermTimer.h" -# include "../Helpers/Network.h" -# include "../Helpers/Networking.h" -# include "../Helpers/PeriodicalActions.h" -# include "../Helpers/StringConverter.h" - -# include - -void handle_unprocessedEthEvents() { - if (EthEventData.unprocessedEthEvents()) { - // Process disconnect events before connect events. -#if FEATURE_USE_IPV6 - if (!EthEventData.processedGotIP6) { - processEthernetGotIPv6(); - } -#endif - - if (!EthEventData.processedDisconnect) { - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("Eth : Entering processDisconnect()")); - # endif // ifndef BUILD_NO_DEBUG - processEthernetDisconnected(); - } - - // Must process the Ethernet Connected event regardless the active network medium. - // It may happen by plugging in the cable while WiFi was active. - if (!EthEventData.processedConnect) { - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("Eth : Entering processConnect()")); - # endif // ifndef BUILD_NO_DEBUG - processEthernetConnected(); - } - } - - if (active_network_medium == NetworkMedium_t::Ethernet) { - const bool EthServices_was_initialized = EthEventData.EthServicesInitialized(); - - if (!EthEventData.EthServicesInitialized() || EthEventData.unprocessedEthEvents()) - { - if (!EthEventData.unprocessedEthEvents() && EthEventData.EthConnectAllowed()) { - NetworkConnectRelaxed(); - } - - if (!EthEventData.processedGotIP) { - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("Eth : Entering processGotIP()")); - # endif // ifndef BUILD_NO_DEBUG - processEthernetGotIP(); - } - - if (!EthEventData.processedDHCPTimeout) { - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("Eth : DHCP timeout, Calling disconnect()")); - # endif // ifndef BUILD_NO_DEBUG - EthEventData.processedDHCPTimeout = true; - - // WifiDisconnect(); - } - } - EthEventData.setEthServicesInitialized(); - - if (!EthServices_was_initialized && EthEventData.setEthServicesInitialized()) { - registerEthEventHandler(); - } - } -} - -void check_Eth_DNS_valid() { - // Check if DNS is still valid, as this may have been reset by the WiFi module turned off. - // DNS records are shared between WiFi and Ethernet IP stack. - // Turning off WiFi will clead DNS records for Ethernet - if (EthEventData.EthServicesInitialized() && - (active_network_medium == NetworkMedium_t::Ethernet) && - EthEventData.ethInitSuccess && - !ethUseStaticIP()) { - const bool has_cache = - valid_DNS_address(EthEventData.dns0_cache) || - valid_DNS_address(EthEventData.dns1_cache); - - if (has_cache) { - const IPAddress dns0 = ETH.dnsIP(0); - const IPAddress dns1 = ETH.dnsIP(1); - - if (!valid_DNS_address(dns0) && !valid_DNS_address(dns1)) { - static uint32_t lastLog = 0; - if (timePassedSince(lastLog) > 1000) { - addLogMove(LOG_LEVEL_ERROR, concat( - F("ETH : DNS server was cleared, use cached DNS IP: "), - formatIP(EthEventData.dns0_cache))); - lastLog = millis(); - } - setDNS(0, EthEventData.dns0_cache); - setDNS(1, EthEventData.dns1_cache); - } - } - } -} - -void processEthernetConnected() { - if (EthEventData.processedConnect) { return; } - - // FIXME TD-er: Must differentiate among reconnects for WiFi and Ethernet. - ++WiFiEventData.wifi_reconnects; - addLog(LOG_LEVEL_INFO, F("processEthernetConnected()")); - EthEventData.setEthConnected(); - EthEventData.processedConnect = true; - - if (Settings.UseRules) - { - eventQueue.add(F("Ethernet#LinkUp")); - } - setNetworkMedium(Settings.NetworkMedium); - - if (ethUseStaticIP()) { EthEventData.processedGotIP = false; } -} - -void processEthernetDisconnected() { - if (EthEventData.processedDisconnect) { return; } - EthEventData.setEthDisconnected(); - EthEventData.processedDisconnect = true; - EthEventData.ethConnectAttemptNeeded = true; - - if (Settings.UseRules) - { - eventQueue.add(F("Ethernet#Disconnected")); - } -} - -void processEthernetGotIP() { - if (EthEventData.processedGotIP || !EthEventData.ethInitSuccess) { - return; - } - - if (ethUseStaticIP()) { - ethSetupStaticIPconfig(); - } - const IPAddress ip = NetworkLocalIP(); - - if (!ip) { - return; - } - - const IPAddress gw = NetworkGatewayIP(); - const IPAddress subnet = NetworkSubnetMask(); - - IPAddress dns0 = ETH.dnsIP(0); - IPAddress dns1 = ETH.dnsIP(1); - const LongTermTimer::Duration dhcp_duration = EthEventData.lastConnectMoment.timeDiff(EthEventData.lastGetIPmoment); - #if ESP_IDF_VERSION_MAJOR >= 5 - const bool mustRequestDHCP = (!dns0 && !dns1) && !ethUseStaticIP(); - #endif - - if (!ethUseStaticIP()) { - if (!dns0 && !dns1) { - addLog(LOG_LEVEL_ERROR, F("ETH : No DNS server received via DHCP, use cached DNS IP")); - if (EthEventData.dns0_cache) setDNS(0, EthEventData.dns0_cache); - if (EthEventData.dns1_cache) setDNS(1, EthEventData.dns1_cache); - } else { - EthEventData.dns0_cache = dns0; - EthEventData.dns1_cache = dns1; - } - } - - if (loglevelActiveFor(LOG_LEVEL_INFO)) - { - String log; - - if (log.reserve(160)) { - log = F("ETH MAC: "); - log += NetworkMacAddress().toString(); - log += ' '; - - if (ethUseStaticIP()) { - log += F("Static"); - } else { - log += F("DHCP"); - } - log += F(" IP: "); - log += formatIP(ip); - log += ' '; - log += wrap_braces(NetworkGetHostname()); - log += F(" GW: "); - log += formatIP(gw); - log += F(" SN: "); - log += formatIP(subnet); - log += F(" DNS: "); - log += formatIP(dns0); - log += '/'; - log += formatIP(dns1); - - if (EthLinkUp()) { - if (EthFullDuplex()) { - log += F(" FULL_DUPLEX"); - } - log += ' '; - log += EthLinkSpeed(); - log += F("Mbps"); - } else { - log += F(" Link Down"); - } - - if ((dhcp_duration > 0ll) && (dhcp_duration < 30000000ll)) { - // Just log times when they make sense. - log += F(" duration: "); - log += static_cast(dhcp_duration / 1000); - log += F(" ms"); - } - - addLogMove(LOG_LEVEL_INFO, log); - } - } - - // First try to get the time, since that may be used in logs - if (node_time.systemTimePresent()) { - node_time.initTime(); - } -# if FEATURE_MQTT - mqtt_reconnect_count = 0; - MQTTclient_should_reconnect = true; - timermqtt_interval = 100; - Scheduler.setIntervalTimer(SchedulerIntervalTimer_e::TIMER_MQTT); - scheduleNextMQTTdelayQueue(); -# endif // if FEATURE_MQTT - Scheduler.sendGratuitousARP_now(); - - if (Settings.UseRules) - { - eventQueue.add(F("Ethernet#Connected")); - } - statusLED(true); - logConnectionStatus(); - - EthEventData.processedGotIP = true; -#if ESP_IDF_VERSION_MAJOR >= 5 - if (mustRequestDHCP /*&& EthEventData.lastConnectMoment.millisPassedSince() < 10000*/) { - // FIXME TD-er: Must add some check other than fixed timeout here to not constantly make DHCP requests. - // Force new DHCP request. - ETH.config(); - } -#endif - - EthEventData.setEthGotIP(); - CheckRunningServices(); -} - -#if FEATURE_USE_IPV6 -void processEthernetGotIPv6() { - if (!EthEventData.processedGotIP6) { - if (loglevelActiveFor(LOG_LEVEL_INFO)) - addLog(LOG_LEVEL_INFO, String(F("ETH event: Got IP6 ")) + EthEventData.unprocessed_IP6.toString(true)); - EthEventData.processedGotIP6 = true; -#if FEATURE_ESPEASY_P2P -// updateUDPport(true); -#endif - - } -} -#endif - -#endif // if FEATURE_ETHERNET diff --git a/src/src/ESPEasyCore/ESPEasyEth_ProcessEvent.h b/src/src/ESPEasyCore/ESPEasyEth_ProcessEvent.h deleted file mode 100644 index 40c10dcf8..000000000 --- a/src/src/ESPEasyCore/ESPEasyEth_ProcessEvent.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef ESPEASYCORE_ESPEASYETH_PROCESSEVENT_H -#define ESPEASYCORE_ESPEASYETH_PROCESSEVENT_H - -#include "../../ESPEasy_common.h" - -#if FEATURE_ETHERNET -void handle_unprocessedEthEvents(); - -void check_Eth_DNS_valid(); - -void processEthernetConnected(); -void processEthernetDisconnected(); -void processEthernetGotIP(); -#if FEATURE_USE_IPV6 -void processEthernetGotIPv6(); -#endif -#endif // if FEATURE_ETHERNET - -#endif // ifndef ESPEASYCORE_ESPEASYETH_PROCESSEVENT_H diff --git a/src/src/ESPEasyCore/ESPEasyNetwork.cpp b/src/src/ESPEasyCore/ESPEasyNetwork.cpp deleted file mode 100644 index a8fe36709..000000000 --- a/src/src/ESPEasyCore/ESPEasyNetwork.cpp +++ /dev/null @@ -1,432 +0,0 @@ -#include "../ESPEasyCore/ESPEasyNetwork.h" - -#include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/ESPEasyEth.h" -#include "../ESPEasyCore/ESPEasyWifi.h" -#include "../Globals/ESPEasy_time.h" -#include "../Globals/ESPEasyWiFiEvent.h" -#include "../Globals/NetworkState.h" -#include "../Globals/Settings.h" - -#include "../Helpers/Network.h" -#include "../Helpers/Networking.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/MDNS_Helper.h" - -#if FEATURE_ETHERNET -#include "../Globals/ESPEasyEthEvent.h" -#include -#endif - - -#if FEATURE_USE_IPV6 -#include - -// ----------------------------------------------------------------------------------------------------------------------- -// ---------------------------------------------------- Private functions ------------------------------------------------ -// ----------------------------------------------------------------------------------------------------------------------- - -esp_netif_t* get_esp_interface_netif(esp_interface_t interface); -#endif - - -void setNetworkMedium(NetworkMedium_t new_medium) { -#if !(FEATURE_ETHERNET) - if (new_medium == NetworkMedium_t::Ethernet) { - new_medium = NetworkMedium_t::WIFI; - } -#endif - if (active_network_medium == new_medium) { - return; - } - switch (active_network_medium) { - case NetworkMedium_t::Ethernet: - #if FEATURE_ETHERNET - // FIXME TD-er: How to 'end' ETH? -// ETH.end(); - if (new_medium == NetworkMedium_t::WIFI) { - WiFiEventData.clearAll(); -#if ESP_IDF_VERSION_MAJOR >= 5 - WiFi.STA.setDefault(); -#endif - } - #endif - break; - case NetworkMedium_t::WIFI: - WiFiEventData.timerAPoff.setMillisFromNow(WIFI_AP_OFF_TIMER_DURATION); - WiFiEventData.timerAPstart.clear(); - if (new_medium == NetworkMedium_t::Ethernet) { -#if ESP_IDF_VERSION_MAJOR >= 5 -#if FEATURE_ETHERNET - ETH.setDefault(); -#endif -#endif - WifiDisconnect(); - } - break; - case NetworkMedium_t::NotSet: - break; - } - statusLED(true); - active_network_medium = new_medium; - #ifndef BUILD_MINIMAL_OTA - addLog(LOG_LEVEL_INFO, concat(F("Set Network mode: "), toString(active_network_medium))); - #endif -} - - -/*********************************************************************************************\ - Ethernet or Wifi Support for ESP32 Build flag FEATURE_ETHERNET -\*********************************************************************************************/ -void NetworkConnectRelaxed() { - if (NetworkConnected()) return; -#if FEATURE_ETHERNET - if(active_network_medium == NetworkMedium_t::Ethernet) { - if (ETHConnectRelaxed()) { - return; - } - // Failed to start the Ethernet network, probably not present of wrong parameters. - // So set the runtime active medium to WiFi to try connecting to WiFi or at least start the AP. - setNetworkMedium(NetworkMedium_t::WIFI); - } -#endif - // Failed to start the Ethernet network, probably not present of wrong parameters. - // So set the runtime active medium to WiFi to try connecting to WiFi or at least start the AP. - WiFiConnectRelaxed(); -} - -bool NetworkConnected() { - #if FEATURE_ETHERNET - if(active_network_medium == NetworkMedium_t::Ethernet) { - return ETHConnected(); - } - #endif - return WiFiConnected(); -} - -IPAddress NetworkLocalIP() { - #if FEATURE_ETHERNET - if(active_network_medium == NetworkMedium_t::Ethernet) { - if(EthEventData.ethInitSuccess) { - return ETH.localIP(); - } else { - addLog(LOG_LEVEL_ERROR, F("Call NetworkLocalIP() only on connected Ethernet!")); - return IPAddress(); - } - } - #endif - return WiFi.localIP(); -} - -IPAddress NetworkSubnetMask() { - #if FEATURE_ETHERNET - if(active_network_medium == NetworkMedium_t::Ethernet) { - if(EthEventData.ethInitSuccess) { - return ETH.subnetMask(); - } else { - addLog(LOG_LEVEL_ERROR, F("Call NetworkSubnetMask() only on connected Ethernet!")); - return IPAddress(); - } - } - #endif - return WiFi.subnetMask(); -} - -IPAddress NetworkGatewayIP() { - #if FEATURE_ETHERNET - if(active_network_medium == NetworkMedium_t::Ethernet) { - if(EthEventData.ethInitSuccess) { - return ETH.gatewayIP(); - } else { - addLog(LOG_LEVEL_ERROR, F("Call NetworkGatewayIP() only on connected Ethernet!")); - return IPAddress(); - } - } - #endif - return WiFi.gatewayIP(); -} - -IPAddress NetworkDnsIP(uint8_t dns_no) { - scrubDNS(); - #if FEATURE_ETHERNET - if(active_network_medium == NetworkMedium_t::Ethernet) { - if(EthEventData.ethInitSuccess) { - return ETH.dnsIP(dns_no); - } else { - addLog(LOG_LEVEL_ERROR, F("Call NetworkDnsIP(uint8_t dns_no) only on connected Ethernet!")); - return IPAddress(); - } - } - #endif - return WiFi.dnsIP(dns_no); -} - -#if FEATURE_USE_IPV6 -esp_netif_t * getActiveNetworkMediumInterface() { - esp_interface_t iface = ESP_IF_MAX; - #if FEATURE_ETHERNET - if(active_network_medium == NetworkMedium_t::Ethernet) { - if(EthEventData.ethInitSuccess) { - esp_netif_t *res = ETH.netif(); - if (res == nullptr) { - res = get_esp_interface_netif(ESP_IF_ETH); - } - if (res != nullptr) - return res; - } - } else - #endif - { - if (WifiIsSTA(WiFi.getMode())) { - iface = ESP_IF_WIFI_STA; - } - } - if (ESP_IF_MAX == iface) - return nullptr; - return get_esp_interface_netif(iface); -} - -IPAddress NetworkLocalIP6() { - esp_netif_t * iface = getActiveNetworkMediumInterface(); - esp_ip6_addr_t addr; - if (nullptr == iface || - esp_netif_get_ip6_linklocal(iface, &addr)) - { - return IN6ADDR_ANY; - } - - IPAddress res(IPv6, (const uint8_t*)addr.addr, addr.zone); - return res; -} - -IPAddress NetworkGlobalIP6() { - esp_netif_t * iface = getActiveNetworkMediumInterface(); - esp_ip6_addr_t addr; - if (nullptr == iface || - esp_netif_get_ip6_global(iface, &addr)) - { - return IN6ADDR_ANY; - } - - IPAddress res(IPv6, (const uint8_t*)addr.addr, addr.zone); - return res; -} - -IP6Addresses_t NetworkAllIPv6() { - IP6Addresses_t addresses; - esp_netif_t * iface = getActiveNetworkMediumInterface(); - if (nullptr != iface) { - esp_ip6_addr_t esp_ip6_addr[LWIP_IPV6_NUM_ADDRESSES]{}; - - int count = esp_netif_get_all_ip6(iface, esp_ip6_addr); - for (int i = 0; i < count; ++i) { - addresses.emplace_back(IPv6, (const uint8_t*)esp_ip6_addr[i].addr, esp_ip6_addr[i].zone); - } - } - - return addresses; -} - -bool IPv6_from_MAC(const MAC_address& mac, IPAddress& ipv6) -{ - if (ipv6 == IN6ADDR_ANY) { return false; } - int index_offset = 8; - - for (int i = 0; i < 6; ++i, ++index_offset) { - ipv6[index_offset] = mac.mac[i]; - - if (i == 0) { - // invert bit 2 - bitToggle(ipv6[index_offset], 1); - } - - if (i == 2) { - ipv6[++index_offset] = 0xFF; - ipv6[++index_offset] = 0xFE; - } - } -/* - addLog(LOG_LEVEL_INFO, strformat( - F("IPv6_from_MAC: Mac %s IP %s"), - mac.toString().c_str(), - ipv6.toString(true).c_str() - )); -*/ - return true; -} - -bool is_IPv6_based_on_MAC(const MAC_address& mac, const IPAddress& ipv6) -{ - IPAddress tmp = ipv6; - - if (IPv6_from_MAC(mac, tmp)) { - return ipv6 == tmp; - } - return false; -} - -bool IPv6_link_local_from_MAC(const MAC_address& mac, IPAddress& ipv6) -{ - ipv6 = NetworkLocalIP6(); - return IPv6_from_MAC(mac, ipv6); -} - -bool is_IPv6_link_local_from_MAC(const MAC_address& mac) -{ - return is_IPv6_based_on_MAC(mac, NetworkLocalIP6()); -} - -// Assume we're in the same subnet, thus use our own IPv6 global address -bool IPv6_global_from_MAC(const MAC_address& mac, IPAddress& ipv6) -{ - ipv6 = NetworkGlobalIP6(); - return IPv6_from_MAC(mac, ipv6); -} - -bool is_IPv6_global_from_MAC(const MAC_address& mac) -{ - return is_IPv6_based_on_MAC(mac, NetworkGlobalIP6()); -} - -#endif // if FEATURE_USE_IPV6 - - - -MAC_address NetworkMacAddress() { - #if FEATURE_ETHERNET - if(active_network_medium == NetworkMedium_t::Ethernet) { - return ETHMacAddress(); - } - #endif - MAC_address mac; - WiFi.macAddress(mac.mac); - return mac; -} - -String NetworkGetHostname() { - #ifdef ESP32 - #if FEATURE_ETHERNET - if(Settings.NetworkMedium == NetworkMedium_t::Ethernet && EthEventData.ethInitSuccess) { - return String(ETH.getHostname()); - } - #endif - return String(WiFi.getHostname()); - #else - return String(WiFi.hostname()); - #endif -} - -// ******************************************************************************** -// Determine Wifi AP name to set. (also used for mDNS) -// ******************************************************************************** -String NetworkGetHostNameFromSettings(bool force_add_unitnr) -{ - if (force_add_unitnr) return Settings.getHostname(true); - return Settings.getHostname(); -} - -String NetworkCreateRFCCompliantHostname(bool force_add_unitnr) { - // Create hostname with - instead of spaces - return makeRFCCompliantName(NetworkGetHostNameFromSettings(force_add_unitnr)); -} - -String makeRFCCompliantName(const String& name, const char replaceChar, const char allowedChar, const size_t maxlength) { - String hostname(name); - // See RFC952. (when using the default arguments: '-', '-', 24) - // Allowed chars: - // * letters (a-z, A-Z) - // * numerals (0-9) - // * Hyphen (-) - // * Max length 24 - replaceUnicodeByChar(hostname, replaceChar); - for (size_t i = 0; i < hostname.length(); ++i) { - const char c = hostname[i]; - if (!isAlphaNumeric(c) && (c != allowedChar)) { - hostname[i] = replaceChar; - } - } - - // May not start or end with a hyphen - const String dash(replaceChar); - while (hostname.startsWith(dash)) { - hostname = hostname.substring(1); - } - while (hostname.endsWith(dash)) { - hostname = hostname.substring(0, hostname.length() - 1); - } - - // May not contain only numerals - bool onlyNumerals = true; - for (size_t i = 0; onlyNumerals && i < hostname.length(); ++i) { - const char c = hostname[i]; - if (!isdigit(c)) { - onlyNumerals = false; - } - } - if (onlyNumerals) { - hostname = strformat(F("ESPEasy%c%s"), replaceChar, hostname.c_str()); - } - - if ((maxlength > 0) && (hostname.length() > maxlength)) { - hostname = hostname.substring(0, maxlength); - } - - return hostname; -} - -MAC_address WifiSoftAPmacAddress() { - MAC_address mac; - WiFi.softAPmacAddress(mac.mac); - return mac; -} - -MAC_address WifiSTAmacAddress() { - MAC_address mac; - WiFi.macAddress(mac.mac); - return mac; -} - -void CheckRunningServices() { - // First try to get the time, since that may be used in logs - if (Settings.UseNTP() && node_time.getTimeSource() > timeSource_t::NTP_time_source) { - node_time.lastNTPSyncTime_ms = 0; - node_time.initTime(); - } -#if FEATURE_SET_WIFI_TX_PWR - if (active_network_medium == NetworkMedium_t::WIFI) - { - SetWiFiTXpower(); - } -#endif - set_mDNS(); -} - -#if FEATURE_ETHERNET -bool EthFullDuplex() -{ - if (EthEventData.ethInitSuccess) - return ETH.fullDuplex(); - return false; -} - -bool EthLinkUp() -{ - if (EthEventData.ethInitSuccess) { - #if ESP_IDF_VERSION_MAJOR < 5 - // FIXME TD-er: See: https://github.com/espressif/arduino-esp32/issues/6105 - return EthEventData.EthConnected(); - #else - return ETH.linkUp(); - #endif - } - return false; -} - -uint8_t EthLinkSpeed() -{ - if (EthEventData.ethInitSuccess) { - return ETH.linkSpeed(); - } - return 0; -} -#endif diff --git a/src/src/ESPEasyCore/ESPEasyNetwork.h b/src/src/ESPEasyCore/ESPEasyNetwork.h deleted file mode 100644 index 3c47aa6eb..000000000 --- a/src/src/ESPEasyCore/ESPEasyNetwork.h +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef ESPEASY_NETWORK_H -#define ESPEASY_NETWORK_H - -#include "../../ESPEasy_common.h" - -#include "../DataStructs/MAC_address.h" - -#include - -#if FEATURE_USE_IPV6 -#include -//typedef uint8_t ip6_addr_type_t; -//typedef std::vector> IP6Addresses_t; -typedef std::vector IP6Addresses_t; -#endif - -void setNetworkMedium(NetworkMedium_t medium); - -void NetworkConnectRelaxed(); -bool NetworkConnected(); -IPAddress NetworkLocalIP(); -IPAddress NetworkSubnetMask(); -IPAddress NetworkGatewayIP(); -IPAddress NetworkDnsIP (uint8_t dns_no); -#if FEATURE_USE_IPV6 - -IPAddress NetworkLocalIP6(); -IPAddress NetworkGlobalIP6(); -IP6Addresses_t NetworkAllIPv6(); - -bool IPv6_link_local_from_MAC(const MAC_address& mac, IPAddress &ipv6); -bool is_IPv6_link_local_from_MAC(const MAC_address& mac); - -// Assume we're in the same subnet, thus use our own IPv6 global address -bool IPv6_global_from_MAC(const MAC_address& mac, IPAddress &ipv6); -bool is_IPv6_global_from_MAC(const MAC_address& mac); - -#endif -MAC_address NetworkMacAddress(); -String NetworkGetHostNameFromSettings(bool force_add_unitnr = false); -String NetworkGetHostname(); -String NetworkCreateRFCCompliantHostname(bool force_add_unitnr = false); -String makeRFCCompliantName(const String& name, const char replaceChar = '-', const char allowedChar = '-', const size_t maxlength = 24); -MAC_address WifiSoftAPmacAddress(); -MAC_address WifiSTAmacAddress(); - -void CheckRunningServices(); - -#if FEATURE_ETHERNET -bool EthFullDuplex(); -bool EthLinkUp(); -uint8_t EthLinkSpeed(); -#endif // if FEATURE_ETHERNET - - -#endif \ No newline at end of file diff --git a/src/src/ESPEasyCore/ESPEasyRules.cpp b/src/src/ESPEasyCore/ESPEasyRules.cpp index 01165a722..477497d35 100644 --- a/src/src/ESPEasyCore/ESPEasyRules.cpp +++ b/src/src/ESPEasyCore/ESPEasyRules.cpp @@ -98,11 +98,11 @@ void rulesProcessing(const String& event) { #ifndef BUILD_NO_DEBUG const unsigned long timer = millis(); #endif // ifndef BUILD_NO_DEBUG - +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove(LOG_LEVEL_INFO, concat(F("EVENT: "), event)); } - +#endif if (Settings.OldRulesEngine()) { bool eventHandled = false; @@ -1049,13 +1049,13 @@ void processMatchedRule(String& action, const String& event, substitute_eventvalue(action, event); const bool executeRestricted = equals(parseString(action, 1), F("restrict")); - +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { String actionlog = executeRestricted ? F("ACT : (restricted) ") : F("ACT : "); actionlog += action; addLogMove(LOG_LEVEL_INFO, actionlog); } - +#endif if (executeRestricted) { ExecuteCommand_all({EventValueSource::Enum::VALUE_SOURCE_RULES_RESTRICTED, parseStringToEndKeepCase(action, 2)}); } else { diff --git a/src/src/ESPEasyCore/ESPEasyWiFiEvent.cpp b/src/src/ESPEasyCore/ESPEasyWiFiEvent.cpp deleted file mode 100644 index ea4d769fd..000000000 --- a/src/src/ESPEasyCore/ESPEasyWiFiEvent.cpp +++ /dev/null @@ -1,440 +0,0 @@ -#include "../ESPEasyCore/ESPEasyWiFiEvent.h" - -#if FEATURE_ETHERNET -#include -#endif - -#include "../DataStructs/RTCStruct.h" - -#include "../DataTypes/ESPEasyTimeSource.h" - -#include "../ESPEasyCore/ESPEasyEth.h" -#include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyWifi.h" -#include "../ESPEasyCore/ESPEasyWifi_ProcessEvent.h" - -#include "../Globals/ESPEasyWiFiEvent.h" -#include "../Globals/NetworkState.h" -#include "../Globals/RTC.h" -#include "../Globals/WiFi_AP_Candidates.h" - -#include "../Helpers/ESPEasy_time_calc.h" - - -#if FEATURE_ETHERNET -#include "../Globals/ESPEasyEthEvent.h" -#endif - - -#ifdef ESP32 -void setUseStaticIP(bool enabled) { -} - -#endif // ifdef ESP32 -#ifdef ESP8266 -void WiFi_Access_Static_IP::set_use_static_ip(bool enabled) { - _useStaticIp = enabled; -} -void setUseStaticIP(bool enabled) { - WiFi_Access_Static_IP tmp_wifi; - - tmp_wifi.set_use_static_ip(enabled); -} - -#endif // ifdef ESP8266 - - - - -// ******************************************************************************** -// Functions called on events. -// Make sure not to call anything in these functions that result in delay() or yield() -// ******************************************************************************** -#ifdef ESP32 -#include - -static bool ignoreDisconnectEvent = false; - -#if ESP_IDF_VERSION_MAJOR > 3 -void WiFiEvent(WiFiEvent_t event, arduino_event_info_t info) { - switch (event) { - case ARDUINO_EVENT_WIFI_READY: - // ESP32 WiFi ready - break; - case ARDUINO_EVENT_WIFI_STA_START: - # ifndef BUILD_NO_DEBUG - //addLog(LOG_LEVEL_INFO, F("WiFi : Event STA Started")); - #endif - break; - case ARDUINO_EVENT_WIFI_STA_STOP: - # ifndef BUILD_NO_DEBUG - //addLog(LOG_LEVEL_INFO, F("WiFi : Event STA Stopped")); - #endif - break; - case ARDUINO_EVENT_WIFI_AP_START: - # ifndef BUILD_NO_DEBUG - //addLog(LOG_LEVEL_INFO, F("WiFi : Event AP Started")); - #endif - break; - case ARDUINO_EVENT_WIFI_AP_STOP: - # ifndef BUILD_NO_DEBUG - //addLog(LOG_LEVEL_INFO, F("WiFi : Event AP Stopped")); - #endif - break; - case ARDUINO_EVENT_WIFI_STA_LOST_IP: - // ESP32 station lost IP and the IP is reset to 0 - #if FEATURE_ETHERNET - if (active_network_medium == NetworkMedium_t::Ethernet) { - // DNS records are shared among WiFi and Ethernet (very bad design!) - // So we must restore the DNS records for Ethernet in case we started with WiFi and then plugged in Ethernet. - // As soon as WiFi is turned off, the DNS entry for Ethernet is cleared. - EthEventData.markLostIP(); - } - #endif // if FEATURE_ETHERNET - WiFiEventData.markLostIP(); - # ifndef BUILD_NO_DEBUG - //addLog(LOG_LEVEL_INFO, - /* - active_network_medium == NetworkMedium_t::Ethernet ? - F("ETH : Event Lost IP") : - */ -// F("WiFi : Event Lost IP")); - #endif - break; - - case ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED: - // Receive probe request packet in soft-AP interface - // TODO TD-er: Must implement like onProbeRequestAPmode for ESP8266 - # ifndef BUILD_NO_DEBUG - //addLog(LOG_LEVEL_INFO, F("WiFi : Event AP got probed")); - #endif - break; - - case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE: - #if ESP_IDF_VERSION_MAJOR > 3 - WiFiEventData.setAuthMode(info.wifi_sta_authmode_change.new_mode); - #else - WiFiEventData.setAuthMode(info.auth_change.new_mode); - #endif - break; - - case ARDUINO_EVENT_WIFI_STA_CONNECTED: - { - char ssid_copy[33]; // Ensure space for maximum len SSID (32) plus trailing 0 - #if ESP_IDF_VERSION_MAJOR > 3 - memcpy(ssid_copy, info.wifi_sta_connected.ssid, info.wifi_sta_connected.ssid_len); - ssid_copy[32] = 0; // Potentially add 0-termination if none present earlier - WiFiEventData.markConnected((const char*) ssid_copy, info.wifi_sta_connected.bssid, info.wifi_sta_connected.channel); - WiFiEventData.setAuthMode(info.wifi_sta_connected.authmode); - //addLog(LOG_LEVEL_INFO, F("WiFi : Event WIFI_STA_CONNECTED")); - #else - memcpy(ssid_copy, info.connected.ssid, info.connected.ssid_len); - ssid_copy[32] = 0; // Potentially add 0-termination if none present earlier - WiFiEventData.markConnected((const char*) ssid_copy, info.connected.bssid, info.connected.channel); - #endif - break; - } - case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: - if (!ignoreDisconnectEvent) { - ignoreDisconnectEvent = true; - #if ESP_IDF_VERSION_MAJOR > 3 - WiFiEventData.markDisconnect(static_cast(info.wifi_sta_disconnected.reason)); - if (info.wifi_sta_disconnected.reason == WIFI_REASON_AUTH_EXPIRE) { - // See: https://github.com/espressif/arduino-esp32/issues/8877#issuecomment-1807677897 - #if ESP_IDF_VERSION_MAJOR >= 5 - // FIXME TD-er: Should no longer be needed. - WiFi.STA._setStatus(WL_CONNECTION_LOST); - #else - WiFiSTAClass::_setStatus(WL_CONNECTION_LOST); - #endif - } - #else - WiFiEventData.markDisconnect(static_cast(info.disconnected.reason)); - if (info.disconnected.reason == WIFI_REASON_AUTH_EXPIRE) { - // See: https://github.com/espressif/arduino-esp32/issues/8877#issuecomment-1807677897 - WiFiSTAClass::_setStatus(WL_CONNECTION_LOST); - } - #endif - WiFi.persistent(false); - WiFi.disconnect(true); - } - break; - case ARDUINO_EVENT_WIFI_STA_GOT_IP: - ignoreDisconnectEvent = false; - WiFiEventData.markGotIP(); - break; - #if FEATURE_USE_IPV6 - case ARDUINO_EVENT_WIFI_STA_GOT_IP6: - { - ip_event_got_ip6_t * event = static_cast(&info.got_ip6); - const IPAddress ip(IPv6, (const uint8_t*)event->ip6_info.ip.addr, event->ip6_info.ip.zone); - WiFiEventData.markGotIPv6(ip); - break; - } - case ARDUINO_EVENT_WIFI_AP_GOT_IP6: - addLog(LOG_LEVEL_INFO, F("WIFI : AP got IP6")); - break; - #endif - case ARDUINO_EVENT_WIFI_AP_STACONNECTED: - #if ESP_IDF_VERSION_MAJOR > 3 - WiFiEventData.markConnectedAPmode(info.wifi_ap_staconnected.mac); - #else - WiFiEventData.markConnectedAPmode(info.sta_connected.mac); - #endif - break; - case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED: - #if ESP_IDF_VERSION_MAJOR > 3 - WiFiEventData.markDisconnectedAPmode(info.wifi_ap_stadisconnected.mac); - #else - WiFiEventData.markDisconnectedAPmode(info.sta_disconnected.mac); - #endif - break; - case ARDUINO_EVENT_WIFI_SCAN_DONE: - WiFiEventData.processedScanDone = false; - break; -#if FEATURE_ETHERNET - case ARDUINO_EVENT_ETH_START: - case ARDUINO_EVENT_ETH_CONNECTED: - case ARDUINO_EVENT_ETH_GOT_IP: - case ARDUINO_EVENT_ETH_DISCONNECTED: - case ARDUINO_EVENT_ETH_STOP: - #if ESP_IDF_VERSION_MAJOR > 3 - case ARDUINO_EVENT_ETH_GOT_IP6: - #else - case ARDUINO_EVENT_GOT_IP6: - #endif - // Handled in EthEvent - break; -#endif //FEATURE_ETHERNET - default: - { - - // addLogMove(LOG_LEVEL_ERROR, concat(F("UNKNOWN WIFI/ETH EVENT: "), event)); - } - break; - } -} -#else -void WiFiEvent(system_event_id_t event, system_event_info_t info) { - switch (event) { - case SYSTEM_EVENT_WIFI_READY: - // ESP32 WiFi ready - break; - case SYSTEM_EVENT_STA_START: - # ifndef BUILD_NO_DEBUG - //addLog(LOG_LEVEL_INFO, F("WiFi : Event STA Started")); - #endif - break; - case SYSTEM_EVENT_STA_STOP: - # ifndef BUILD_NO_DEBUG - //addLog(LOG_LEVEL_INFO, F("WiFi : Event STA Stopped")); - #endif - break; - case SYSTEM_EVENT_AP_START: - # ifndef BUILD_NO_DEBUG - //addLog(LOG_LEVEL_INFO, F("WiFi : Event AP Started")); - #endif - break; - case SYSTEM_EVENT_AP_STOP: - # ifndef BUILD_NO_DEBUG - //addLog(LOG_LEVEL_INFO, F("WiFi : Event AP Stopped")); - #endif - break; - case SYSTEM_EVENT_STA_LOST_IP: - // ESP32 station lost IP and the IP is reset to 0 - #if FEATURE_ETHERNET - if (active_network_medium == NetworkMedium_t::Ethernet) { - EthEventData.markLostIP(); - } - else - #endif // if FEATURE_ETHERNET - WiFiEventData.markLostIP(); - # ifndef BUILD_NO_DEBUG - /* - addLog(LOG_LEVEL_INFO, - active_network_medium == NetworkMedium_t::Ethernet ? - F("ETH : Event Lost IP") : F("WiFi : Event Lost IP")); - */ - #endif - break; - - case SYSTEM_EVENT_AP_PROBEREQRECVED: - // Receive probe request packet in soft-AP interface - // TODO TD-er: Must implement like onProbeRequestAPmode for ESP8266 - # ifndef BUILD_NO_DEBUG - //addLog(LOG_LEVEL_INFO, F("WiFi : Event AP got probed")); - #endif - break; - - case SYSTEM_EVENT_STA_AUTHMODE_CHANGE: - #if ESP_IDF_VERSION_MAJOR > 3 - WiFiEventData.setAuthMode(info.wifi_sta_authmode_change.new_mode); - #else - WiFiEventData.setAuthMode(info.auth_change.new_mode); - #endif - break; - - case SYSTEM_EVENT_STA_CONNECTED: - { - char ssid_copy[33] = { 0 }; // Ensure space for maximum len SSID (32) plus trailing 0 - #if ESP_IDF_VERSION_MAJOR > 3 - memcpy(ssid_copy, info.wifi_sta_connected.ssid, info.wifi_sta_connected.ssid_len); - ssid_copy[32] = 0; // Potentially add 0-termination if none present earlier - WiFiEventData.markConnected((const char*) ssid_copy, info.wifi_sta_connected.bssid, info.wifi_sta_connected.channel); - #else - memcpy(ssid_copy, info.connected.ssid, info.connected.ssid_len); - ssid_copy[32] = 0; // Potentially add 0-termination if none present earlier - WiFiEventData.markConnected((const char*) ssid_copy, info.connected.bssid, info.connected.channel); - #endif - break; - } - case SYSTEM_EVENT_STA_DISCONNECTED: - if (!ignoreDisconnectEvent) { - ignoreDisconnectEvent = true; - #if ESP_IDF_VERSION_MAJOR > 3 - WiFiEventData.markDisconnect(static_cast(info.wifi_sta_disconnected.reason)); - #else - WiFiEventData.markDisconnect(static_cast(info.disconnected.reason)); - #endif - WiFi.persistent(false); - WiFi.disconnect(true); - } - break; - case SYSTEM_EVENT_STA_GOT_IP: - ignoreDisconnectEvent = false; - WiFiEventData.markGotIP(); - break; - case SYSTEM_EVENT_AP_STACONNECTED: - #if ESP_IDF_VERSION_MAJOR > 3 - WiFiEventData.markConnectedAPmode(info.wifi_ap_staconnected.mac); - #else - WiFiEventData.markConnectedAPmode(info.sta_connected.mac); - #endif - break; - case SYSTEM_EVENT_AP_STADISCONNECTED: - #if ESP_IDF_VERSION_MAJOR > 3 - WiFiEventData.markDisconnectedAPmode(info.wifi_ap_stadisconnected.mac); - #else - WiFiEventData.markDisconnectedAPmode(info.sta_disconnected.mac); - #endif - break; - case SYSTEM_EVENT_SCAN_DONE: - WiFiEventData.processedScanDone = false; - break; -#if FEATURE_ETHERNET - case SYSTEM_EVENT_ETH_START: - if (ethPrepare()) { - //addLog(LOG_LEVEL_INFO, F("ETH event: Started")); - } else { - //addLog(LOG_LEVEL_ERROR, F("ETH event: Could not prepare ETH!")); - } - break; - case SYSTEM_EVENT_ETH_CONNECTED: - //addLog(LOG_LEVEL_INFO, F("ETH event: Connected")); - EthEventData.markConnected(); - break; - case SYSTEM_EVENT_ETH_GOT_IP: - EthEventData.markGotIP(); - //addLog(LOG_LEVEL_INFO, F("ETH event: Got IP")); - break; - case SYSTEM_EVENT_ETH_DISCONNECTED: - //addLog(LOG_LEVEL_ERROR, F("ETH event: Disconnected")); - EthEventData.markDisconnect(); - break; - case SYSTEM_EVENT_ETH_STOP: - //addLog(LOG_LEVEL_INFO, F("ETH event: Stopped")); - break; - case SYSTEM_EVENT_GOT_IP6: - //addLog(LOG_LEVEL_INFO, F("ETH event: Got IP6")); - break; -#endif //FEATURE_ETHERNET - default: - { - //addLogMove(LOG_LEVEL_ERROR, concat(F("UNKNOWN WIFI/ETH EVENT: "), event)); - } - break; - } -} - -#endif - -#endif // ifdef ESP32 - -#ifdef ESP8266 - -void onConnected(const WiFiEventStationModeConnected& event) { - WiFiEventData.markConnected(event.ssid, event.bssid, event.channel); -} - -void onDisconnect(const WiFiEventStationModeDisconnected& event) { - WiFiEventData.markDisconnect(event.reason); - if (WiFi.status() == WL_CONNECTED) { - // See https://github.com/esp8266/Arduino/issues/5912 - WiFi.persistent(false); - WiFi.disconnect(false); - delay(0); - } -} - -void onGotIP(const WiFiEventStationModeGotIP& event) { - WiFiEventData.markGotIP(); -} - -void onDHCPTimeout() { - WiFiEventData.processedDHCPTimeout = false; -} - -void onConnectedAPmode(const WiFiEventSoftAPModeStationConnected& event) { - WiFiEventData.markConnectedAPmode(event.mac); -} - -void onDisconnectedAPmode(const WiFiEventSoftAPModeStationDisconnected& event) { - WiFiEventData.markDisconnectedAPmode(event.mac); -} - -void onStationModeAuthModeChanged(const WiFiEventStationModeAuthModeChanged& event) { - WiFiEventData.setAuthMode(event.newMode); -} - -#if FEATURE_ESP8266_DIRECT_WIFI_SCAN -void onWiFiScanDone(void *arg, STATUS status) { - if (status == OK) { - auto *head = reinterpret_cast(arg); - int scanCount = 0; - for (bss_info *it = head; it != nullptr; it = STAILQ_NEXT(it, next)) { - WiFi_AP_Candidates.process_WiFiscan(*it); - ++scanCount; - } - WiFi_AP_Candidates.after_process_WiFiscan(); - WiFiEventData.lastGetScanMoment.setNow(); -// WiFiEventData.processedScanDone = true; -# ifndef BUILD_NO_DEBUG -/* - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, concat(F("WiFi : Scan finished (ESP8266), found: "), scanCount)); - } -*/ -#endif - WiFi_AP_Candidates.load_knownCredentials(); - if (WiFi_AP_Candidates.addedKnownCandidate() || !NetworkConnected()) { - WiFiEventData.wifiConnectAttemptNeeded = true; - # ifndef BUILD_NO_DEBUG - if (WiFi_AP_Candidates.addedKnownCandidate()) { - //addLog(LOG_LEVEL_INFO, F("WiFi : Added known candidate, try to connect")); - } - #endif - NetworkConnectRelaxed(); - } - - } - - WiFiMode_t mode = WiFi.getMode(); - setWifiMode(WIFI_OFF); - delay(1); - setWifiMode(mode); - delay(1); -} -#endif - -#endif // ifdef ESP8266 diff --git a/src/src/ESPEasyCore/ESPEasyWiFiEvent.h b/src/src/ESPEasyCore/ESPEasyWiFiEvent.h deleted file mode 100644 index 996692c62..000000000 --- a/src/src/ESPEasyCore/ESPEasyWiFiEvent.h +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef ESPEASY_WIFI_EVENT_H -#define ESPEASY_WIFI_EVENT_H - -#include "../../ESPEasy_common.h" - -#include - -// ******************************************************************************** - -// Work-around for setting _useStaticIP -// See reported issue: https://github.com/esp8266/Arduino/issues/4114 -// ******************************************************************************** -#ifdef ESP32 -#include -#include -#include -class WiFi_Access_Static_IP : public WiFiSTAClass { -public: - - void set_use_static_ip(bool enabled); -}; -#endif - -#ifdef ESP8266 -#include -#include -class WiFi_Access_Static_IP : public ESP8266WiFiSTAClass { -public: - - void set_use_static_ip(bool enabled); -}; -#endif - - -void setUseStaticIP(bool enabled); - - -// ******************************************************************************** -// Functions called on events. -// Make sure not to call anything in these functions that result in delay() or yield() -// ******************************************************************************** -#ifdef ESP32 - #if ESP_IDF_VERSION_MAJOR > 3 - #include - void WiFiEvent(WiFiEvent_t event, arduino_event_info_t info); - #else - void WiFiEvent(system_event_id_t event, system_event_info_t info); - #endif -#endif - -#ifdef ESP8266 - -void onConnected(const WiFiEventStationModeConnected& event); - -void onDisconnect(const WiFiEventStationModeDisconnected& event); - -void onGotIP(const WiFiEventStationModeGotIP& event); - -void onDHCPTimeout(); - -void onConnectedAPmode(const WiFiEventSoftAPModeStationConnected& event); - -void onDisconnectedAPmode(const WiFiEventSoftAPModeStationDisconnected& event); - -void onStationModeAuthModeChanged(const WiFiEventStationModeAuthModeChanged& event); - -#if FEATURE_ESP8266_DIRECT_WIFI_SCAN -void onWiFiScanDone(void *arg, STATUS status); -#endif - -#endif - - -#endif // ESPEASY_WIFI_EVENT_H \ No newline at end of file diff --git a/src/src/ESPEasyCore/ESPEasyWifi.cpp b/src/src/ESPEasyCore/ESPEasyWifi.cpp deleted file mode 100644 index 398fbdda2..000000000 --- a/src/src/ESPEasyCore/ESPEasyWifi.cpp +++ /dev/null @@ -1,1728 +0,0 @@ -#include "../ESPEasyCore/ESPEasyWifi.h" - -#include "../../ESPEasy-Globals.h" -#include "../DataStructs/TimingStats.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyWiFiEvent.h" -#include "../ESPEasyCore/ESPEasyWifi_ProcessEvent.h" -#include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/Serial.h" -#include "../Globals/ESPEasyWiFiEvent.h" -#include "../Globals/EventQueue.h" -#include "../Globals/NetworkState.h" -#include "../Globals/Nodes.h" -#include "../Globals/RTC.h" -#include "../Globals/SecuritySettings.h" -#include "../Globals/Services.h" -#include "../Globals/Settings.h" -#include "../Globals/WiFi_AP_Candidates.h" -#include "../Helpers/ESPEasy_time_calc.h" -#include "../Helpers/Hardware_defines.h" -#include "../Helpers/Misc.h" -#include "../Helpers/Networking.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/StringGenerator_WiFi.h" -#include "../Helpers/StringProvider.h" - -#ifdef ESP32 -#include -#include // Needed to call ESP-IDF functions like esp_wifi_.... - -#include -#endif - -// FIXME TD-er: Cleanup of WiFi code -#ifdef ESPEASY_WIFI_CLEANUP_WORK_IN_PROGRESS -bool ESPEasyWiFi_t::begin() { - return true; -} - -void ESPEasyWiFi_t::end() { - - -} - - -void ESPEasyWiFi_t::loop() { - switch (_state) { - case WiFiState_e::OFF: - break; - case WiFiState_e::AP_only: - break; - case WiFiState_e::ErrorRecovery: - // Wait for timeout to expire - // Start again from scratch - break; - case WiFiState_e::STA_Scanning: - case WiFiState_e::STA_AP_Scanning: - // Check if scanning is finished - // When scanning per channel, call for scanning next channel - break; - case WiFiState_e::STA_Connecting: - case WiFiState_e::STA_Reconnecting: - // Check if (re)connecting has finished - break; - case WiFiState_e::STA_Connected: - // Check if still connected - // Reconnect if not. - // Else mark last timestamp seen as connected - break; - } - - - { - // Check if we need to start AP - // Flag captive portal in webserver and/or whether we might be in setup mode - } - -#ifdef USE_IMPROV - { - // Check for Improv mode. - } -#endif - - -} - - -IPAddress ESPEasyWiFi_t::getIP() const { - - IPAddress res; - - - return res; -} - -void ESPEasyWiFi_t::disconnect() { - -} - - -void ESPEasyWiFi_t::checkConnectProgress() { - -} - -void ESPEasyWiFi_t::startScanning() { - _state = WiFiState_e::STA_Scanning; - WifiScan(true); - _last_state_change.setNow(); -} - - -bool ESPEasyWiFi_t::connectSTA() { - if (!WiFi_AP_Candidates.hasCandidateCredentials()) { - if (!WiFiEventData.warnedNoValidWiFiSettings) { - addLog(LOG_LEVEL_ERROR, F("WIFI : No valid wifi settings")); - WiFiEventData.warnedNoValidWiFiSettings = true; - } - WiFiEventData.last_wifi_connect_attempt_moment.clear(); - WiFiEventData.wifi_connect_attempt = 1; - WiFiEventData.wifiConnectAttemptNeeded = false; - - // No need to wait longer to start AP mode. - if (!Settings.DoNotStartAP()) { - setAP(true); - } - return false; - } - WiFiEventData.warnedNoValidWiFiSettings = false; - setSTA(true); - #if defined(ESP8266) - wifi_station_set_hostname(NetworkCreateRFCCompliantHostname().c_str()); - - #endif // if defined(ESP8266) - #if defined(ESP32) - WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE); - #endif // if defined(ESP32) - setConnectionSpeed(); - setupStaticIPconfig(); - - - - // Start the process of connecting or starting AP - if (WiFi_AP_Candidates.getNext(true)) { - // Try to connect to AP - - } else { - // No (known) AP, start scanning - startScanning(); - } - - - return true; -} - -#endif // ESPEASY_WIFI_CLEANUP_WORK_IN_PROGRESS - - -// ******************************************************************************** -// WiFi state -// ******************************************************************************** - -/* - WiFi STA states: - 1 STA off => ESPEASY_WIFI_DISCONNECTED - 2 STA connecting - 3 STA connected => ESPEASY_WIFI_CONNECTED - 4 STA got IP => ESPEASY_WIFI_GOT_IP - 5 STA connected && got IP => ESPEASY_WIFI_SERVICES_INITIALIZED - - N.B. the states are flags, meaning both "connected" and "got IP" must be set - to be considered ESPEASY_WIFI_SERVICES_INITIALIZED - - The flag wifiConnectAttemptNeeded indicates whether a new connect attempt is needed. - This is set to true when: - - Security settings have been saved with AP mode enabled. FIXME TD-er, this may not be the best check. - - WiFi connect timeout reached & No client is connected to the AP mode of the node. - - Wifi is reset - - WiFi setup page has been loaded with SSID/pass values. - - - WiFi AP mode states: - 1 AP on => reset AP disable timer - 2 AP client connect/disconnect => reset AP disable timer - 3 AP off => AP disable timer = 0; - - AP mode will be disabled when both apply: - - AP disable timer (timerAPoff) expired - - No client is connected to the AP. - - AP mode will be enabled when at least one applies: - - No valid WiFi settings - - Start AP timer (timerAPstart) expired - - Start AP timer is set or cleared at: - - Set timerAPstart when "valid WiFi connection" state is observed. - - Disable timerAPstart when ESPEASY_WIFI_SERVICES_INITIALIZED wifi state is reached. - - For the first attempt to connect after a cold boot (RTC values are 0), a WiFi scan will be - performed to find the strongest known SSID. - This will set RTC.lastBSSID and RTC.lastWiFiChannel - - Quick reconnect (using BSSID/channel of last connection) when both apply: - - If wifi_connect_attempt < 3 - - RTC.lastBSSID is known - - RTC.lastWiFiChannel != 0 - - Change of wifi settings when both apply: - - "other" settings valid - - (wifi_connect_attempt % 2) == 0 - - Reset of wifi_connect_attempt to 0 when both apply: - - connection successful - - Connection stable (connected for > 5 minutes) - - */ - - -// ******************************************************************************** -// Check WiFi connected status -// This is basically the state machine to switch between states: -// - Initiate WiFi reconnect -// - Start/stop of AP mode -// ******************************************************************************** -bool WiFiConnected() { - START_TIMER; - - static bool recursiveCall = false; - - static uint32_t lastCheckedTime = 0; - static bool lastState = false; - -#if FEATURE_USE_IPV6 - if (!WiFiEventData.processedGotIP6) { - processGotIPv6(); - } -#endif - - if (!WifiIsSTA(WiFi.getMode())) { - lastState = false; - return lastState; - } - - - const int32_t timePassed = timePassedSince(lastCheckedTime); - if (lastCheckedTime != 0) { - if (timePassed < 100) { - if (WiFiEventData.lastDisconnectMoment.isSet() && - WiFiEventData.lastDisconnectMoment.millisPassedSince() > timePassed) - { - // Try to rate-limit the nr of calls to this function or else it will be called 1000's of times a second. - return lastState; - } - } - if (timePassed < 10) { - // Rate limit time spent in WiFiConnected() to max. 100x per sec to process the rest of this function - return lastState; - } - } - - - - if (WiFiEventData.unprocessedWifiEvents()) { return false; } - - bool wifi_isconnected = WiFi.isConnected(); - #ifdef ESP8266 - // Perform check on SDK function, see: https://github.com/esp8266/Arduino/issues/7432 - station_status_t status = wifi_station_get_connect_status(); - switch(status) { - case STATION_GOT_IP: - wifi_isconnected = true; - break; - case STATION_NO_AP_FOUND: - case STATION_CONNECT_FAIL: - case STATION_WRONG_PASSWORD: - wifi_isconnected = false; - break; - case STATION_IDLE: - case STATION_CONNECTING: - break; - - default: - wifi_isconnected = false; - break; - } - #endif - - if (recursiveCall) return wifi_isconnected; - recursiveCall = true; - - - // For ESP82xx, do not rely on WiFi.status() with event based wifi. - const int32_t wifi_rssi = WiFi.RSSI(); - bool validWiFi = (wifi_rssi < 0) && wifi_isconnected && hasIPaddr(); - /* - if (validWiFi && WiFi.channel() != WiFiEventData.usedChannel) { - validWiFi = false; - } - */ - if (validWiFi != WiFiEventData.WiFiServicesInitialized()) { - // else wifiStatus is no longer in sync. - if (checkAndResetWiFi()) { - // Wifi has been reset, so no longer valid WiFi - validWiFi = false; - } - } - - if (validWiFi) { - // Connected, thus disable any timer to start AP mode. (except when in WiFi setup mode) - if (!WiFiEventData.wifiSetupConnect) { - WiFiEventData.timerAPstart.clear(); - } - STOP_TIMER(WIFI_ISCONNECTED_STATS); - recursiveCall = false; - // Only return true after some time since it got connected. -#if FEATURE_SET_WIFI_TX_PWR - SetWiFiTXpower(); -#endif - lastState = WiFiEventData.wifi_considered_stable || WiFiEventData.lastConnectMoment.timeoutReached(100); - lastCheckedTime = millis(); - return lastState; - } - - if ((WiFiEventData.timerAPstart.isSet()) && WiFiEventData.timerAPstart.timeReached()) { - if (WiFiEventData.timerAPoff.isSet() && !WiFiEventData.timerAPoff.timeReached()) { - if (!Settings.DoNotStartAP()) { - // Timer reached, so enable AP mode. - if (!WifiIsAP(WiFi.getMode())) { - if (!WiFiEventData.wifiConnectAttemptNeeded) { - #ifndef BUILD_MINIMAL_OTA - addLog(LOG_LEVEL_INFO, F("WiFi : WiFiConnected(), start AP")); - #endif - WifiScan(false); - setSTA(false); // Force reset WiFi + reduce power consumption - setAP(true); - } - } - } - } else { - WiFiEventData.timerAPstart.clear(); - WiFiEventData.timerAPoff.clear(); - } - } - - - // When made this far in the code, we apparently do not have valid WiFi connection. - if (!WiFiEventData.timerAPstart.isSet() && !WifiIsAP(WiFi.getMode())) { - // First run we do not have WiFi connection any more, set timer to start AP mode - // Only allow the automatic AP mode in the first N minutes after boot. - if (getUptimeMinutes() < WIFI_ALLOW_AP_AFTERBOOT_PERIOD) { - WiFiEventData.timerAPstart.setMillisFromNow(WIFI_RECONNECT_WAIT); - // Fixme TD-er: Make this more elegant as it now needs to know about the extra time needed for the AP start timer. - WiFiEventData.timerAPoff.setMillisFromNow(WIFI_RECONNECT_WAIT + WIFI_AP_OFF_TIMER_DURATION); - } - } - - const bool timeoutReached = WiFiEventData.last_wifi_connect_attempt_moment.isSet() && - WiFiEventData.last_wifi_connect_attempt_moment.timeoutReached(2 * DEFAULT_WIFI_CONNECTION_TIMEOUT); - - if (timeoutReached && !WiFiEventData.wifiSetup) { - // It took too long to make a connection, set flag we need to try again - //if (!wifiAPmodeActivelyUsed()) { - WiFiEventData.wifiConnectAttemptNeeded = true; - //} - WiFiEventData.wifiConnectInProgress = false; - if (!WiFiEventData.WiFiDisconnected()) { - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_INFO, F("WiFi : wifiConnectTimeoutReached")); - #endif - WifiDisconnect(); - } - } - delay(0); - STOP_TIMER(WIFI_NOTCONNECTED_STATS); - recursiveCall = false; - return false; -} - -void WiFiConnectRelaxed() { - if (!WiFiEventData.processedDisconnect) { - processDisconnect(); - } - if (!WiFiEventData.WiFiConnectAllowed() || WiFiEventData.wifiConnectInProgress) { - if (WiFiEventData.wifiConnectInProgress) { - if (WiFiEventData.last_wifi_connect_attempt_moment.isSet()) { - if (WiFiEventData.last_wifi_connect_attempt_moment.timeoutReached(WIFI_PROCESS_EVENTS_TIMEOUT)) { - WiFiEventData.wifiConnectInProgress = false; - } - } - } - - if (WiFiEventData.wifiConnectInProgress) { - return; // already connected or connect attempt in progress need to disconnect first - } - } - if (!WiFiEventData.processedScanDone) { - // Scan is still active, so do not yet connect. - return; - } - - if (WiFiEventData.unprocessedWifiEvents()) { - # ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - String log = F("WiFi : Connecting not possible, unprocessed WiFi events: "); - if (!WiFiEventData.processedConnect) { - log += F(" conn"); - } - if (!WiFiEventData.processedDisconnect) { - log += F(" disconn"); - } - if (!WiFiEventData.processedGotIP) { - log += F(" gotIP"); - } -#if FEATURE_USE_IPV6 - if (!WiFiEventData.processedGotIP6) { - log += F(" gotIP6"); - } -#endif - - if (!WiFiEventData.processedDHCPTimeout) { - log += F(" DHCP_t/o"); - } - - addLogMove(LOG_LEVEL_ERROR, log); - logConnectionStatus(); - } - #endif - return; - } - - if (!WiFiEventData.wifiSetupConnect && wifiAPmodeActivelyUsed()) { - return; - } - - - // FIXME TD-er: Should not try to prepare when a scan is still busy. - // This is a logic error which may lead to strange issues if some kind of timeout happens and/or RF calibration was not OK. - // Split this function into separate parts, with the last part being the actual connect attempt either after a scan is complete or quick connect is possible. - - AttemptWiFiConnect(); -} - -void AttemptWiFiConnect() { - if (!WiFiEventData.wifiConnectAttemptNeeded) { - return; - } - - if (WiFiEventData.wifiConnectInProgress) { - return; - } - - setNetworkMedium(NetworkMedium_t::WIFI); - if (active_network_medium != NetworkMedium_t::WIFI) - { - return; - } - - - if (WiFiEventData.wifiSetupConnect) { - // wifiSetupConnect is when run from the setup page. - RTC.clearLastWiFi(); // Force slow connect - WiFiEventData.wifi_connect_attempt = 0; - WiFiEventData.wifiSetupConnect = false; - if (WiFiEventData.timerAPoff.isSet()) { - WiFiEventData.timerAPoff.setMillisFromNow(WIFI_RECONNECT_WAIT + WIFI_AP_OFF_TIMER_DURATION); - } - } - - if (WiFiEventData.last_wifi_connect_attempt_moment.isSet()) { - if (!WiFiEventData.last_wifi_connect_attempt_moment.timeoutReached(DEFAULT_WIFI_CONNECTION_TIMEOUT)) { - return; - } - } - - if (WiFiEventData.unprocessedWifiEvents()) { - return; - } - setSTA(false); - - setSTA(true); - - if (WiFi_AP_Candidates.getNext(WiFiScanAllowed())) { - const WiFi_AP_Candidate candidate = WiFi_AP_Candidates.getCurrent(); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, strformat( - F("WIFI : Connecting %s attempt #%u"), - candidate.toString().c_str(), - WiFiEventData.wifi_connect_attempt)); - } - WiFiEventData.markWiFiBegin(); - if (prepareWiFi()) { - setNetworkMedium(NetworkMedium_t::WIFI); - RTC.clearLastWiFi(); - RTC.lastWiFiSettingsIndex = candidate.index; - - float tx_pwr = 0; // Will be set higher based on RSSI when needed. - // FIXME TD-er: Must check WiFiEventData.wifi_connect_attempt to increase TX power -#if FEATURE_SET_WIFI_TX_PWR - if (Settings.UseMaxTXpowerForSending()) { - tx_pwr = Settings.getWiFi_TX_power(); - } - SetWiFiTXpower(tx_pwr, candidate.rssi); -#endif - // Start connect attempt now, so no longer needed to attempt new connection. - WiFiEventData.wifiConnectAttemptNeeded = false; - WiFiEventData.wifiConnectInProgress = true; - const String key = WiFi_AP_CandidatesList::get_key(candidate.index); - -#if FEATURE_USE_IPV6 - if (Settings.EnableIPv6()) { - WiFi.enableIPv6(true); - } -#endif - -#ifdef ESP32 - if (Settings.IncludeHiddenSSID()) { - wifi_country_t config = { - .cc = "01", - .schan = 1, - .nchan = 14, - .policy = WIFI_COUNTRY_POLICY_MANUAL, - }; - esp_wifi_set_country(&config); - } -#endif - - - if ((Settings.HiddenSSID_SlowConnectPerBSSID() || !candidate.bits.isHidden) - && candidate.allowQuickConnect()) { - WiFi.begin(candidate.ssid.c_str(), key.c_str(), candidate.channel, candidate.bssid.mac); - } else { - WiFi.begin(candidate.ssid.c_str(), key.c_str()); - } -#ifdef ESP32 - // Always wait for a second on ESP32 - WiFi.waitForConnectResult(1000); // https://github.com/arendst/Tasmota/issues/14985 -#else - if (Settings.WaitWiFiConnect() || candidate.bits.isHidden) { -// WiFi.waitForConnectResult(candidate.isHidden ? 3000 : 1000); // https://github.com/arendst/Tasmota/issues/14985 - WiFi.waitForConnectResult(1000); // https://github.com/arendst/Tasmota/issues/14985 - } -#endif - delay(1); - } else { - WiFiEventData.wifiConnectInProgress = false; - } - } else { - if (!wifiAPmodeActivelyUsed() || WiFiEventData.wifiSetupConnect) { - if (!prepareWiFi()) { - //return; - } - - if (WiFiScanAllowed()) { - // Maybe not scan async to give the ESP some slack in power consumption? - const bool async = false; - WifiScan(async); - } - // Limit nr of attempts as we don't have any AP candidates. - WiFiEventData.last_wifi_connect_attempt_moment.setMillisFromNow(60000); - WiFiEventData.timerAPstart.setNow(); - } - } - - logConnectionStatus(); -} - -// ******************************************************************************** -// Set Wifi config -// ******************************************************************************** -bool prepareWiFi() { - #if defined(ESP32) - registerWiFiEventHandler(); - #endif - - if (!WiFi_AP_Candidates.hasCandidateCredentials()) { - if (!WiFiEventData.warnedNoValidWiFiSettings) { - addLog(LOG_LEVEL_ERROR, F("WIFI : No valid wifi settings")); - WiFiEventData.warnedNoValidWiFiSettings = true; - } -// WiFiEventData.last_wifi_connect_attempt_moment.clear(); - WiFiEventData.wifi_connect_attempt = 1; - WiFiEventData.wifiConnectAttemptNeeded = false; - - // No need to wait longer to start AP mode. - if (!Settings.DoNotStartAP()) { - WifiScan(false); -// setAP(true); - } - return false; - } - WiFiEventData.warnedNoValidWiFiSettings = false; - setSTA(true); - - #if defined(ESP8266) - wifi_station_set_hostname(NetworkCreateRFCCompliantHostname().c_str()); - - #endif // if defined(ESP8266) - #if defined(ESP32) - WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE); - #endif // if defined(ESP32) - setConnectionSpeed(); - setupStaticIPconfig(); - WiFiEventData.wifiConnectAttemptNeeded = true; - - return true; -} - -bool checkAndResetWiFi() { - #ifdef ESP8266 - station_status_t status = wifi_station_get_connect_status(); - - switch(status) { - case STATION_GOT_IP: - if (WiFi.RSSI() < 0 && WiFi.localIP().isSet()) { - //if (WiFi.channel() == WiFiEventData.usedChannel || WiFiEventData.usedChannel == 0) { - // This is a valid status, no need to reset - return false; - //} - } - break; - case STATION_NO_AP_FOUND: - case STATION_CONNECT_FAIL: - case STATION_WRONG_PASSWORD: - // Reason to reset WiFi - break; - case STATION_IDLE: - case STATION_CONNECTING: - if (WiFiEventData.last_wifi_connect_attempt_moment.isSet() && !WiFiEventData.last_wifi_connect_attempt_moment.timeoutReached(DEFAULT_WIFI_CONNECTION_TIMEOUT)) { - return false; - } - break; - } - #endif - #ifdef ESP32 - if (WiFi.isConnected()) { - //if (WiFi.channel() == WiFiEventData.usedChannel || WiFiEventData.usedChannel == 0) { - return false; - //} - } - if (WiFiEventData.last_wifi_connect_attempt_moment.isSet() && !WiFiEventData.last_wifi_connect_attempt_moment.timeoutReached(DEFAULT_WIFI_CONNECTION_TIMEOUT)) { - return false; - } - #endif - # ifndef BUILD_NO_DEBUG - String log = F("WiFi : WiFiConnected() out of sync: "); - log += WiFiEventData.ESPeasyWifiStatusToString(); - log += F(" RSSI: "); - log += String(WiFi.RSSI()); - #ifdef ESP8266 - log += F(" status: "); - log += SDKwifiStatusToString(status); - #endif - #endif - - // Call for reset first, to make sure a syslog call will not try to send. - resetWiFi(); - # ifndef BUILD_NO_DEBUG - addLogMove(LOG_LEVEL_INFO, log); - #endif - return true; -} - - -void resetWiFi() { - //if (wifiAPmodeActivelyUsed()) return; - if (WiFiEventData.lastWiFiResetMoment.isSet() && !WiFiEventData.lastWiFiResetMoment.timeoutReached(1000)) { - // Don't reset WiFi too often - return; - } - FeedSW_watchdog(); - WiFiEventData.clearAll(); - WifiDisconnect(); - - // Send this log only after WifiDisconnect() or else sending to syslog may cause issues - #ifndef BUILD_MINIMAL_OTA - addLog(LOG_LEVEL_INFO, F("Reset WiFi.")); - #endif - - // setWifiMode(WIFI_OFF); - - initWiFi(); -} - -#ifdef ESP32 -void removeWiFiEventHandler() -{ - WiFi.removeEvent(WiFiEventData.wm_event_id); - WiFiEventData.wm_event_id = 0; -} - -void registerWiFiEventHandler() -{ - if (WiFiEventData.wm_event_id != 0) { - removeWiFiEventHandler(); - } - WiFiEventData.wm_event_id = WiFi.onEvent(WiFiEvent); -} -#endif - - -void initWiFi() -{ -#ifdef ESP8266 - - // See https://github.com/esp8266/Arduino/issues/5527#issuecomment-460537616 - // FIXME TD-er: Do not destruct WiFi object, it may cause crashes with queued UDP traffic. -// WiFi.~ESP8266WiFiClass(); -// WiFi = ESP8266WiFiClass(); -#endif // ifdef ESP8266 -#ifdef ESP32 - removeWiFiEventHandler(); -#endif - - - WiFi.persistent(false); // Do not use SDK storage of SSID/WPA parameters - // The WiFi.disconnect() ensures that the WiFi is working correctly. If this is not done before receiving WiFi connections, - // those WiFi connections will take a long time to make or sometimes will not work at all. - WiFi.disconnect(false); - delay(1); - if (active_network_medium != NetworkMedium_t::NotSet) { - setSTA(true); - WifiScan(false); - } - setWifiMode(WIFI_OFF); - -#if defined(ESP32) - registerWiFiEventHandler(); -#endif -#ifdef ESP8266 - // WiFi event handlers - static bool handlers_initialized = false; - if (!handlers_initialized) { - stationConnectedHandler = WiFi.onStationModeConnected(onConnected); - stationDisconnectedHandler = WiFi.onStationModeDisconnected(onDisconnect); - stationGotIpHandler = WiFi.onStationModeGotIP(onGotIP); - stationModeDHCPTimeoutHandler = WiFi.onStationModeDHCPTimeout(onDHCPTimeout); - stationModeAuthModeChangeHandler = WiFi.onStationModeAuthModeChanged(onStationModeAuthModeChanged); - APModeStationConnectedHandler = WiFi.onSoftAPModeStationConnected(onConnectedAPmode); - APModeStationDisconnectedHandler = WiFi.onSoftAPModeStationDisconnected(onDisconnectedAPmode); - handlers_initialized = true; - } -#endif - delay(100); -} - -// ******************************************************************************** -// Configure WiFi TX power -// ******************************************************************************** -#if FEATURE_SET_WIFI_TX_PWR -void SetWiFiTXpower() { - SetWiFiTXpower(0); // Just some minimal value, will be adjusted in SetWiFiTXpower -} - -void SetWiFiTXpower(float dBm) { - SetWiFiTXpower(dBm, WiFi.RSSI()); -} - -void SetWiFiTXpower(float dBm, float rssi) { - const WiFiMode_t cur_mode = WiFi.getMode(); - if (cur_mode == WIFI_OFF) { - return; - } - - if (Settings.UseMaxTXpowerForSending()) { - dBm = 30; // Just some max, will be limited later - } - - // Range ESP32 : -1dBm - 20dBm - // Range ESP8266: 0dBm - 20.5dBm - float maxTXpwr; - float threshold = GetRSSIthreshold(maxTXpwr); - #ifdef ESP8266 - float minTXpwr{}; - #endif - #ifdef ESP32 - float minTXpwr = -1.0f; - #endif - - threshold += Settings.WiFi_sensitivity_margin; // Margin in dBm on top of threshold - - // Assume AP sends with max set by ETSI standard. - // 2.4 GHz: 100 mWatt (20 dBm) - // US and some other countries allow 1000 mW (30 dBm) - // We cannot send with over 20 dBm, thus it makes no sense to force higher TX power all the time. - const float newrssi = rssi - 20; - if (newrssi < threshold) { - minTXpwr = threshold - newrssi; - } - if (minTXpwr > maxTXpwr) { - minTXpwr = maxTXpwr; - } - if (dBm > maxTXpwr) { - dBm = maxTXpwr; - } else if (dBm < minTXpwr) { - dBm = minTXpwr; - } - - #ifdef ESP32 - int8_t power = dBm * 4; - if (esp_wifi_set_max_tx_power(power) == ESP_OK) { - if (esp_wifi_get_max_tx_power(&power) == ESP_OK) { - dBm = static_cast(power) / 4.0f; - } - } - #endif - - #ifdef ESP8266 - WiFi.setOutputPower(dBm); - #endif - - if (WiFiEventData.wifi_TX_pwr < dBm) { - // Will increase the TX power, give power supply of the unit some rest - delay(1); - } - - WiFiEventData.wifi_TX_pwr = dBm; - - delay(0); - #ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - const int TX_pwr_int = WiFiEventData.wifi_TX_pwr * 4; - const int maxTXpwr_int = maxTXpwr * 4; - if (TX_pwr_int != maxTXpwr_int) { - static int last_log = -1; - if (TX_pwr_int != last_log) { - last_log = TX_pwr_int; - String log = strformat( - F("WiFi : Set TX power to %ddBm sensitivity: %ddBm"), - static_cast(dBm), - static_cast(threshold)); - if (rssi < 0) { - log += strformat(F(" RSSI: %ddBm"), static_cast(rssi)); - } - addLogMove(LOG_LEVEL_DEBUG, log); - } - } - } - #endif -} -#endif - - - - -float GetRSSIthreshold(float& maxTXpwr) { - maxTXpwr = Settings.getWiFi_TX_power(); - float threshold = WIFI_SENSITIVITY_n; - switch (getConnectionProtocol()) { - case WiFiConnectionProtocol::WiFi_Protocol_11b: - threshold = WIFI_SENSITIVITY_11b; - if (maxTXpwr > MAX_TX_PWR_DBM_11b) maxTXpwr = MAX_TX_PWR_DBM_11b; - break; - case WiFiConnectionProtocol::WiFi_Protocol_11g: - threshold = WIFI_SENSITIVITY_54g; - if (maxTXpwr > MAX_TX_PWR_DBM_54g) maxTXpwr = MAX_TX_PWR_DBM_54g; - break; -#ifdef ESP8266 - case WiFiConnectionProtocol::WiFi_Protocol_11n: -#else - case WiFiConnectionProtocol::WiFi_Protocol_HT20: - case WiFiConnectionProtocol::WiFi_Protocol_HT40: - case WiFiConnectionProtocol::WiFi_Protocol_HE20: -#endif - - threshold = WIFI_SENSITIVITY_n; - if (maxTXpwr > MAX_TX_PWR_DBM_n) maxTXpwr = MAX_TX_PWR_DBM_n; - break; -#ifdef ESP32 -#if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(5, 2, 0) - case WiFiConnectionProtocol::WiFi_Protocol_11a: - case WiFiConnectionProtocol::WiFi_Protocol_VHT20: - // FIXME TD-er: Must determine max. TX power for these 5 GHz modi -#endif - case WiFiConnectionProtocol::WiFi_Protocol_LR: -#endif - case WiFiConnectionProtocol::Unknown: - break; - } - return threshold; -} - -int GetRSSI_quality() { - long rssi = WiFi.RSSI(); - - if (-50 < rssi) { return 10; } - - if (rssi <= -98) { return 0; } - rssi = rssi + 97; // Range 0..47 => 1..9 - return (rssi / 5) + 1; -} - -WiFiConnectionProtocol getConnectionProtocol() { - if (WiFi.RSSI() < 0) { - #ifdef ESP8266 - switch (wifi_get_phy_mode()) { - case PHY_MODE_11B: - return WiFiConnectionProtocol::WiFi_Protocol_11b; - case PHY_MODE_11G: - return WiFiConnectionProtocol::WiFi_Protocol_11g; - case PHY_MODE_11N: - return WiFiConnectionProtocol::WiFi_Protocol_11n; - } - #endif - #ifdef ESP32 - - wifi_phy_mode_t phymode; - esp_wifi_sta_get_negotiated_phymode(&phymode); - switch (phymode) { - case WIFI_PHY_MODE_11B: return WiFiConnectionProtocol::WiFi_Protocol_11b; - case WIFI_PHY_MODE_11G: return WiFiConnectionProtocol::WiFi_Protocol_11g; - case WIFI_PHY_MODE_HT20: return WiFiConnectionProtocol::WiFi_Protocol_HT20; - case WIFI_PHY_MODE_HT40: return WiFiConnectionProtocol::WiFi_Protocol_HT40; - case WIFI_PHY_MODE_HE20: return WiFiConnectionProtocol::WiFi_Protocol_HE20; - case WIFI_PHY_MODE_LR: return WiFiConnectionProtocol::WiFi_Protocol_LR; -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) - // 5 GHz - case WIFI_PHY_MODE_11A: return WiFiConnectionProtocol::WiFi_Protocol_11a; - case WIFI_PHY_MODE_VHT20: return WiFiConnectionProtocol::WiFi_Protocol_VHT20; -#endif - } - #endif - } - return WiFiConnectionProtocol::Unknown; -} - -#ifdef ESP32 -int64_t WiFi_get_TSF_time() -{ - return esp_wifi_get_tsf_time(WIFI_IF_STA); -} -#endif - - -// ******************************************************************************** -// Disconnect from Wifi AP -// ******************************************************************************** -void WifiDisconnect() -{ - if (!WiFiEventData.processedDisconnect || - WiFiEventData.processingDisconnect.isSet()) { - return; - } - if (WiFi.status() == WL_DISCONNECTED) { - return; - } - // Prevent recursion - static LongTermTimer processingDisconnectTimer; - if (processingDisconnectTimer.isSet() && - !processingDisconnectTimer.timeoutReached(200)) return; - processingDisconnectTimer.setNow(); - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_INFO, F("WiFi : WifiDisconnect()")); - #endif - #ifdef ESP32 - removeWiFiEventHandler(); - WiFi.disconnect(); - delay(100); - { - const IPAddress ip; - const IPAddress gw; - const IPAddress subnet; - const IPAddress dns; - WiFi.config(ip, gw, subnet, dns); - } - #endif - #ifdef ESP8266 - // Only call disconnect when STA is active - if (WifiIsSTA(WiFi.getMode())) { - wifi_station_disconnect(); - } - station_config conf{}; - memset(&conf, 0, sizeof(conf)); - ETS_UART_INTR_DISABLE(); - wifi_station_set_config_current(&conf); - ETS_UART_INTR_ENABLE(); - #endif - WiFiEventData.setWiFiDisconnected(); - WiFiEventData.markDisconnect(WIFI_DISCONNECT_REASON_UNSPECIFIED); - /* - if (!Settings.UseLastWiFiFromRTC()) { - RTC.clearLastWiFi(); - } - */ - delay(100); - WiFiEventData.processingDisconnect.clear(); - WiFiEventData.processedDisconnect = false; - processDisconnect(); - processingDisconnectTimer.clear(); -} - -// ******************************************************************************** -// Scan WiFi network -// ******************************************************************************** -bool WiFiScanAllowed() { - if (WiFi_AP_Candidates.scanComplete() == WIFI_SCAN_RUNNING) { - return false; - } - if (!WiFiEventData.processedScanDone) { - processScanDone(); - } - if (!WiFiEventData.processedDisconnect) { - processDisconnect(); - } - - if (WiFiEventData.wifiConnectInProgress) { - return false; - } - - if (WiFiEventData.intent_to_reboot) { - return false; - } - - if (WiFiEventData.unprocessedWifiEvents()) { - # ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - String log = F("WiFi : Scan not allowed, unprocessed WiFi events: "); - if (!WiFiEventData.processedConnect) { - log += F(" conn"); - } - if (!WiFiEventData.processedDisconnect) { - log += F(" disconn"); - } - if (!WiFiEventData.processedGotIP) { - log += F(" gotIP"); - } - if (!WiFiEventData.processedDHCPTimeout) { - log += F(" DHCP_t/o"); - } - - addLogMove(LOG_LEVEL_ERROR, log); - logConnectionStatus(); - } - #endif - return false; - } - /* - if (!wifiAPmodeActivelyUsed() && !NetworkConnected()) { - return true; - } - */ - WiFi_AP_Candidates.purge_expired(); - if (WiFiEventData.wifiConnectInProgress) { - return false; - } - if (WiFiEventData.lastScanMoment.isSet()) { - if (NetworkConnected() && WiFi_AP_Candidates.getBestCandidate().usable()) { - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_ERROR, F("WiFi : Scan not needed, good candidate present")); - #endif - return false; - } - } - - if (WiFiEventData.lastDisconnectMoment.isSet() && WiFiEventData.lastDisconnectMoment.millisPassedSince() < WIFI_RECONNECT_WAIT) { - if (!NetworkConnected()) { - return WiFiEventData.processedConnect; - } - } - if (WiFiEventData.lastScanMoment.isSet()) { - const LongTermTimer::Duration scanInterval = wifiAPmodeActivelyUsed() ? WIFI_SCAN_INTERVAL_AP_USED : WIFI_SCAN_INTERVAL_MINIMAL; - if (WiFiEventData.lastScanMoment.millisPassedSince() < scanInterval) { - return false; - } - } - return WiFiEventData.processedConnect; -} - - -void WifiScan(bool async, uint8_t channel) { - setSTA(true); - if (!WiFiScanAllowed()) { - return; - } -#ifdef ESP32 - // TD-er: Don't run async scan on ESP32. - // Since IDF 4.4 it seems like the active channel may be messed up when running async scan - // Perform a disconnect after scanning. - // See: https://github.com/letscontrolit/ESPEasy/pull/3579#issuecomment-967021347 - async = false; - - if (Settings.IncludeHiddenSSID()) { - wifi_country_t config = { - .cc = "01", - .schan = 1, - .nchan = 14, - .policy = WIFI_COUNTRY_POLICY_MANUAL, - }; - esp_wifi_set_country(&config); - } - - -#endif - - START_TIMER; - WiFiEventData.lastScanMoment.setNow(); - # ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - if (channel == 0) { - addLog(LOG_LEVEL_INFO, F("WiFi : Start network scan all channels")); - } else { - addLogMove(LOG_LEVEL_INFO, strformat(F("WiFi : Start network scan ch: %d "), channel)); - } - } - #endif - bool show_hidden = true; - WiFiEventData.processedScanDone = false; - WiFiEventData.lastGetScanMoment.setNow(); - WiFiEventData.lastScanChannel = channel; - - unsigned int nrScans = 1 + (async ? 0 : Settings.NumberExtraWiFiScans); - while (nrScans > 0) { - if (!async) { - WiFi_AP_Candidates.begin_sync_scan(); - FeedSW_watchdog(); - } - --nrScans; -#ifdef ESP8266 -#if FEATURE_ESP8266_DIRECT_WIFI_SCAN - { - static bool FIRST_SCAN = true; - - struct scan_config config; - memset(&config, 0, sizeof(config)); - config.ssid = nullptr; - config.bssid = nullptr; - config.channel = channel; - config.show_hidden = show_hidden ? 1 : 0;; - config.scan_type = WIFI_SCAN_TYPE_ACTIVE; - if (FIRST_SCAN) { - config.scan_time.active.min = 100; - config.scan_time.active.max = 200; - } else { - config.scan_time.active.min = 400; - config.scan_time.active.max = 500; - } - FIRST_SCAN = false; - wifi_station_scan(&config, &onWiFiScanDone); - if (!async) { - // will resume when SYSTEM_EVENT_SCAN_DONE event is fired - do { - delay(0); - } while (!WiFiEventData.processedScanDone); - } - - } -#else - WiFi.scanNetworks(async, show_hidden, channel); -#endif -#endif -#ifdef ESP32 - const bool passive = Settings.PassiveWiFiScan(); - const uint32_t max_ms_per_chan = 120; - WiFi.setScanTimeout(14 * max_ms_per_chan * 2); - WiFi.scanNetworks(async, show_hidden, passive, max_ms_per_chan /*, channel */); -#endif - if (!async) { - FeedSW_watchdog(); - processScanDone(); - } - } -#if FEATURE_TIMING_STATS - if (async) { - STOP_TIMER(WIFI_SCAN_ASYNC); - } else { - STOP_TIMER(WIFI_SCAN_SYNC); - } -#endif - -#ifdef ESP32 -#if ESP_IDF_VERSION_MAJOR<5 - RTC.clearLastWiFi(); - if (WiFiConnected()) { - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_INFO, F("WiFi : Disconnect after scan")); - #endif - - const bool needReconnect = WiFiEventData.wifiConnectAttemptNeeded; - WifiDisconnect(); - WiFiEventData.wifiConnectAttemptNeeded = needReconnect; - } -#endif -#endif - -} - -// ******************************************************************************** -// Scan all Wifi Access Points -// ******************************************************************************** -void WiFiScan_log_to_serial() -{ - // Direct Serial is allowed here, since this function will only be called from serial input. - serialPrintln(F("WIFI : SSID Scan start")); - if (WiFi_AP_Candidates.scanComplete() <= 0) { - WiFiMode_t cur_wifimode = WiFi.getMode(); - WifiScan(false); - setWifiMode(cur_wifimode); - } - - const int8_t scanCompleteStatus = WiFi_AP_Candidates.scanComplete(); - if (scanCompleteStatus <= 0) { - serialPrintln(F("WIFI : No networks found")); - } - else - { - serialPrint(F("WIFI : ")); - serialPrint(String(scanCompleteStatus)); - serialPrintln(F(" networks found")); - - int i = 0; - - for (auto it = WiFi_AP_Candidates.scanned_begin(); it != WiFi_AP_Candidates.scanned_end(); ++it) - { - ++i; - // Print SSID and RSSI for each network found - serialPrint(F("WIFI : ")); - serialPrint(String(i)); - serialPrint(": "); - serialPrintln(it->toString()); - delay(10); - } - } - serialPrintln(""); -} - -// ******************************************************************************** -// Manage Wifi Modes -// ******************************************************************************** -void setSTA(bool enable) { - switch (WiFi.getMode()) { - case WIFI_OFF: - - if (enable) { setWifiMode(WIFI_STA); } - break; - case WIFI_STA: - - if (!enable) { setWifiMode(WIFI_OFF); } - break; - case WIFI_AP: - - if (enable) { setWifiMode(WIFI_AP_STA); } - break; - case WIFI_AP_STA: - - if (!enable) { setWifiMode(WIFI_AP); } - break; - default: - break; - } -} - -void setAP(bool enable) { - WiFiMode_t wifimode = WiFi.getMode(); - - switch (wifimode) { - case WIFI_OFF: - - if (enable) { - setWifiMode(WIFI_AP); - } - break; - case WIFI_STA: - - if (enable) { setWifiMode(WIFI_AP_STA); } - break; - case WIFI_AP: - - if (!enable) { setWifiMode(WIFI_OFF); } - break; - case WIFI_AP_STA: - - if (!enable) { setWifiMode(WIFI_STA); } - break; - default: - break; - } -} - -// Only internal scope -void setAPinternal(bool enable) -{ - if (enable) { - // create and store unique AP SSID/PW to prevent ESP from starting AP mode with default SSID and No password! - // setup ssid for AP Mode when needed - String softAPSSID = NetworkCreateRFCCompliantHostname(); - String pwd = SecuritySettings.WifiAPKey; - IPAddress subnet(DEFAULT_AP_SUBNET); - - if (!WiFi.softAPConfig(apIP, apIP, subnet)) { - addLog(LOG_LEVEL_ERROR, strformat( - ("WIFI : [AP] softAPConfig failed! IP: %s, GW: %s, SN: %s"), - apIP.toString().c_str(), - apIP.toString().c_str(), - subnet.toString().c_str() - ) - ); - } - - int channel = 1; - if (WifiIsSTA(WiFi.getMode()) && WiFiConnected()) { - channel = WiFi.channel(); - } - - if (WiFi.softAP(softAPSSID.c_str(), pwd.c_str(), channel)) { - eventQueue.add(F("WiFi#APmodeEnabled")); - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, strformat( - F("WIFI : AP Mode enabled. SSID: %s IP: %s ch: %d"), - softAPSSID.c_str(), - formatIP(WiFi.softAPIP()).c_str(), - channel)); - } - } else { - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - addLogMove(LOG_LEVEL_ERROR, strformat( - F("WIFI : Error while starting AP Mode with SSID: %s IP: %s"), - softAPSSID.c_str(), - formatIP(apIP).c_str())); - } - } - #ifdef ESP32 - - #else // ifdef ESP32 - - if (wifi_softap_dhcps_status() != DHCP_STARTED) { - if (!wifi_softap_dhcps_start()) { - addLog(LOG_LEVEL_ERROR, F("WIFI : [AP] wifi_softap_dhcps_start failed!")); - } - } - #endif // ifdef ESP32 - WiFiEventData.timerAPoff.setMillisFromNow(WIFI_AP_OFF_TIMER_DURATION); - } else { - #if FEATURE_DNS_SERVER - if (dnsServerActive) { - dnsServerActive = false; - dnsServer.stop(); - } - #endif // if FEATURE_DNS_SERVER - } -} - -const __FlashStringHelper * getWifiModeString(WiFiMode_t wifimode) -{ - switch (wifimode) { - case WIFI_OFF: return F("OFF"); - case WIFI_STA: return F("STA"); - case WIFI_AP: return F("AP"); - case WIFI_AP_STA: return F("AP+STA"); - default: - break; - } - return F("Unknown"); -} - -void setWifiMode(WiFiMode_t new_mode) { - const WiFiMode_t cur_mode = WiFi.getMode(); - static WiFiMode_t processing_wifi_mode = cur_mode; - if (cur_mode == new_mode) { - return; - } - if (processing_wifi_mode == new_mode) { - // Prevent loops - return; - } - processing_wifi_mode = new_mode; - - if (cur_mode == WIFI_OFF) { - #if defined(ESP32) - // Needs to be set while WiFi is off - WiFi.hostname(NetworkCreateRFCCompliantHostname()); - #endif - WiFiEventData.markWiFiTurnOn(); - } - if (new_mode != WIFI_OFF) { - #ifdef ESP8266 - // See: https://github.com/esp8266/Arduino/issues/6172#issuecomment-500457407 - WiFi.forceSleepWake(); // Make sure WiFi is really active. - #endif - delay(100); - } else { - WifiDisconnect(); -// delay(100); - processDisconnect(); - WiFiEventData.clear_processed_flags(); - } - - addLog(LOG_LEVEL_INFO, concat(F("WIFI : Set WiFi to "), getWifiModeString(new_mode))); - - int retry = 2; - while (!WiFi.mode(new_mode) && retry > 0) { - #ifndef BUILD_MINIMAL_OTA - addLog(LOG_LEVEL_INFO, F("WIFI : Cannot set mode!!!!!")); - #endif - delay(100); - --retry; - } - retry = 2; - while (WiFi.getMode() != new_mode && retry > 0) { - #ifndef BUILD_MINIMAL_OTA - addLog(LOG_LEVEL_INFO, F("WIFI : mode not yet set")); - #endif - delay(100); - --retry; - } - - - if (new_mode == WIFI_OFF) { - WiFiEventData.markWiFiTurnOn(); - #if defined(ESP32) - // Needs to be set while WiFi is off - WiFi.hostname(NetworkCreateRFCCompliantHostname()); - #endif - delay(100); - #if defined(ESP32) - esp_wifi_set_ps(WIFI_PS_NONE); -// esp_wifi_set_ps(WIFI_PS_MAX_MODEM); - #endif - #ifdef ESP8266 - WiFi.forceSleepBegin(); - #endif // ifdef ESP8266 - delay(1); - } else { - #ifdef ESP32 - if (cur_mode == WIFI_OFF) { - registerWiFiEventHandler(); - } - #endif - // Only set power mode when AP is not enabled - // When AP is enabled, the sleep mode is already set to WIFI_NONE_SLEEP - if (!WifiIsAP(new_mode)) { - if (Settings.WifiNoneSleep()) { - #ifdef ESP8266 - WiFi.setSleepMode(WIFI_NONE_SLEEP); - #endif - #ifdef ESP32 - WiFi.setSleep(WIFI_PS_NONE); - #endif - } else if (Settings.EcoPowerMode()) { - // Allow light sleep during idle times - #ifdef ESP8266 - WiFi.setSleepMode(WIFI_LIGHT_SLEEP); - #endif - #ifdef ESP32 - // Maximum modem power saving. - // In this mode, interval to receive beacons is determined by the listen_interval parameter in wifi_sta_config_t - // FIXME TD-er: Must test if this is desired behavior in ESP32. - WiFi.setSleep(WIFI_PS_MAX_MODEM); - #endif - } else { - // Default - #ifdef ESP8266 - WiFi.setSleepMode(WIFI_MODEM_SLEEP); - #endif - #ifdef ESP32 - // Minimum modem power saving. - // In this mode, station wakes up to receive beacon every DTIM period - WiFi.setSleep(WIFI_PS_MIN_MODEM); - #endif - } - } -#if FEATURE_SET_WIFI_TX_PWR - SetWiFiTXpower(); -#endif - if (WifiIsSTA(new_mode)) { -// WiFi.setAutoConnect(Settings.SDK_WiFi_autoreconnect()); - WiFi.setAutoReconnect(Settings.SDK_WiFi_autoreconnect()); - } - delay(100); // Must allow for some time to init. - } - const bool new_mode_AP_enabled = WifiIsAP(new_mode); - - if (WifiIsAP(cur_mode) && !new_mode_AP_enabled) { - eventQueue.add(F("WiFi#APmodeDisabled")); - } - - if (WifiIsAP(cur_mode) != new_mode_AP_enabled) { - // Mode has changed - setAPinternal(new_mode_AP_enabled); - } - #if FEATURE_MDNS - #ifdef ESP8266 - // notifyAPChange() is not present in the ESP32 MDNSResponder - MDNS.notifyAPChange(); - #endif - #endif -} - -bool WifiIsAP(WiFiMode_t wifimode) -{ - #if defined(ESP32) - return (wifimode == WIFI_MODE_AP) || (wifimode == WIFI_MODE_APSTA); - #else // if defined(ESP32) - return (wifimode == WIFI_AP) || (wifimode == WIFI_AP_STA); - #endif // if defined(ESP32) -} - -bool WifiIsSTA(WiFiMode_t wifimode) -{ - #if defined(ESP32) - return (wifimode & WIFI_MODE_STA) != 0; - #else // if defined(ESP32) - return (wifimode & WIFI_STA) != 0; - #endif // if defined(ESP32) -} - -bool WiFiUseStaticIP() { - return Settings.IP[0] != 0 && Settings.IP[0] != 255; -} - -bool wifiAPmodeActivelyUsed() -{ - if (!WifiIsAP(WiFi.getMode()) || (!WiFiEventData.timerAPoff.isSet())) { - // AP not active or soon to be disabled in processDisableAPmode() - return false; - } - return WiFi.softAPgetStationNum() != 0; - - // FIXME TD-er: is effectively checking for AP active enough or must really check for connected clients to prevent automatic wifi - // reconnect? -} - -void setConnectionSpeed() { - #ifdef ESP8266 - // ESP8266 only supports 802.11g mode when running in STA+AP - const bool forcedByAPmode = WifiIsAP(WiFi.getMode()); - WiFiPhyMode_t phyMode = (Settings.ForceWiFi_bg_mode() || forcedByAPmode) ? WIFI_PHY_MODE_11G : WIFI_PHY_MODE_11N; - if (!forcedByAPmode) { - const WiFi_AP_Candidate candidate = WiFi_AP_Candidates.getCurrent(); - if (candidate.phy_known() && (candidate.bits.phy_11g != candidate.bits.phy_11n)) { - if ((WIFI_PHY_MODE_11G == phyMode) && !candidate.bits.phy_11g) { - phyMode = WIFI_PHY_MODE_11N; - #ifndef BUILD_MINIMAL_OTA - addLog(LOG_LEVEL_INFO, F("WIFI : AP is set to 802.11n only")); - #endif - } else if ((WIFI_PHY_MODE_11N == phyMode) && !candidate.bits.phy_11n) { - phyMode = WIFI_PHY_MODE_11G; - #ifndef BUILD_MINIMAL_OTA - addLog(LOG_LEVEL_INFO, F("WIFI : AP is set to 802.11g only")); - #endif - } - } else { - bool useAlternate = WiFiEventData.connectionFailures > 10; - if (useAlternate) { - phyMode = (WIFI_PHY_MODE_11G == phyMode) ? WIFI_PHY_MODE_11N : WIFI_PHY_MODE_11G; - } - } - } else { - // No need to perform a next attempt. - WiFi_AP_Candidates.markAttempt(); - } - - if (WiFi.getPhyMode() == phyMode) { - return; - } - #ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = concat(F("WIFI : Set to 802.11"), (WIFI_PHY_MODE_11G == phyMode) ? 'g' : 'n'); - if (forcedByAPmode) { - log += (F(" (AP+STA mode)")); - } - if (Settings.ForceWiFi_bg_mode()) { - log += F(" Force B/G mode"); - } - addLogMove(LOG_LEVEL_INFO, log); - } - #endif - WiFi.setPhyMode(phyMode); - #endif // ifdef ESP8266 - - // Does not (yet) work, so commented out. - #ifdef ESP32 - - // HT20 = 20 MHz channel width. - // HT40 = 40 MHz channel width. - // In theory, HT40 can offer upto 150 Mbps connection speed. - // However since HT40 is using nearly all channels on 2.4 GHz WiFi, - // Thus you are more likely to experience disturbances. - // The response speed and stability is better at HT20 for ESP units. - esp_wifi_set_bandwidth(WIFI_IF_STA, WIFI_BW_HT20); - - uint8_t protocol = WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G; // Default to BG - - if (!Settings.ForceWiFi_bg_mode() || (WiFiEventData.connectionFailures > 10)) { - // Set to use BGN - protocol |= WIFI_PROTOCOL_11N; - #ifdef ESP32C6 - protocol |= WIFI_PROTOCOL_11AX; - #endif - } - - const WiFi_AP_Candidate candidate = WiFi_AP_Candidates.getCurrent(); - if (candidate.phy_known()) { - // Check to see if the access point is set to "N-only" - if ((protocol & WIFI_PROTOCOL_11N) == 0) { - if (!candidate.bits.phy_11b && !candidate.bits.phy_11g && candidate.bits.phy_11n) { - if (candidate.bits.phy_11n) { - // Set to use BGN - protocol |= WIFI_PROTOCOL_11N; - addLog(LOG_LEVEL_INFO, F("WIFI : AP is set to 802.11n only")); - } -#ifdef ESP32C6 - if (candidate.bits.phy_11ax) { - // Set to use WiFi6 - protocol |= WIFI_PROTOCOL_11AX; - addLog(LOG_LEVEL_INFO, F("WIFI : AP is set to 802.11ax")); - } -#endif - } - } - } - - - if (WifiIsSTA(WiFi.getMode())) { - // Set to use "Long GI" making it more resilliant to reflections - // See: https://www.tp-link.com/us/configuration-guides/q_a_basic_wireless_concepts/?configurationId=2958#_idTextAnchor038 - esp_wifi_config_80211_tx_rate(WIFI_IF_STA, WIFI_PHY_RATE_MCS3_LGI); - esp_wifi_set_protocol(WIFI_IF_STA, protocol); - } - - if (WifiIsAP(WiFi.getMode())) { - esp_wifi_set_protocol(WIFI_IF_AP, protocol); - } - #endif // ifdef ESP32 -} - -void setupStaticIPconfig() { - setUseStaticIP(WiFiUseStaticIP()); - - if (!WiFiUseStaticIP()) { return; } - const IPAddress ip (Settings.IP); - const IPAddress gw (Settings.Gateway); - const IPAddress subnet (Settings.Subnet); - const IPAddress dns (Settings.DNS); - - WiFiEventData.dns0_cache = dns; - - WiFi.config(ip, gw, subnet, dns); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, strformat( - F("IP : Static IP : %s GW: %s SN: %s DNS: %s"), - formatIP(ip).c_str(), - formatIP(gw).c_str(), - formatIP(subnet).c_str(), - getValue(LabelType::DNS).c_str())); - } -} - -// ******************************************************************************** -// Formatting WiFi related strings -// ******************************************************************************** -String formatScanResult(int i, const String& separator) { - int32_t rssi = 0; - - return formatScanResult(i, separator, rssi); -} - -String formatScanResult(int i, const String& separator, int32_t& rssi) { - WiFi_AP_Candidate tmp(i); - rssi = tmp.rssi; - return tmp.toString(separator); -} - - -void logConnectionStatus() { - static unsigned long lastLog = 0; - if (lastLog != 0 && timePassedSince(lastLog) < 1000) { - return; - } - lastLog = millis(); -#ifndef BUILD_NO_DEBUG - #ifdef ESP8266 - const uint8_t arduino_corelib_wifistatus = WiFi.status(); - const uint8_t sdk_wifistatus = wifi_station_get_connect_status(); - - if ((arduino_corelib_wifistatus == WL_CONNECTED) != (sdk_wifistatus == STATION_GOT_IP)) { - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - String log = F("WiFi : SDK station status differs from Arduino status. SDK-status: "); - log += SDKwifiStatusToString(sdk_wifistatus); - log += F(" Arduino status: "); - log += ArduinoWifiStatusToString(arduino_corelib_wifistatus); - addLogMove(LOG_LEVEL_ERROR, log); - } - } - #endif - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, strformat( - F("WIFI : Arduino wifi status: %s ESPeasy internal wifi status: %s"), - ArduinoWifiStatusToString(WiFi.status()).c_str(), - WiFiEventData.ESPeasyWifiStatusToString().c_str())); - } -/* - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; - - switch (WiFi.status()) { - case WL_NO_SSID_AVAIL: { - log = F("WIFI : No SSID found matching: "); - break; - } - case WL_CONNECT_FAILED: { - log = F("WIFI : Connection failed to: "); - break; - } - case WL_DISCONNECTED: { - log = F("WIFI : WiFi.status() = WL_DISCONNECTED SSID: "); - break; - } - case WL_IDLE_STATUS: { - log = F("WIFI : Connection in IDLE state: "); - break; - } - case WL_CONNECTED: { - break; - } - default: - break; - } - - if (log.length() > 0) { - const char *ssid = getLastWiFiSettingsSSID(); - log += ssid; - addLog(LOG_LEVEL_INFO, log); - } - } - */ -#endif // ifndef BUILD_NO_DEBUG -} diff --git a/src/src/ESPEasyCore/ESPEasyWifi.h b/src/src/ESPEasyCore/ESPEasyWifi.h deleted file mode 100644 index 9b680a4fb..000000000 --- a/src/src/ESPEasyCore/ESPEasyWifi.h +++ /dev/null @@ -1,157 +0,0 @@ -#ifndef ESPEASY_WIFI_H -#define ESPEASY_WIFI_H - -#include "../../ESPEasy_common.h" - -#if defined(ESP8266) - # include -#endif // if defined(ESP8266) -#if defined(ESP32) - # include -#endif // if defined(ESP32) - -#include "../DataTypes/WiFiConnectionProtocol.h" -#include "../DataStructs/WiFi_AP_Candidate.h" - -#include "../Helpers/LongTermTimer.h" - -#define WIFI_RECONNECT_WAIT 30000 // in milliSeconds -#define WIFI_AP_OFF_TIMER_DURATION 300000 // in milliSeconds -#if FEATURE_CUSTOM_PROVISIONING -#define WIFI_CONNECTION_CONSIDERED_STABLE 60000 // in milliSeconds -#else -#define WIFI_CONNECTION_CONSIDERED_STABLE 60000 // in milliSeconds -#endif -#define WIFI_ALLOW_AP_AFTERBOOT_PERIOD 5 // in minutes -#define WIFI_SCAN_INTERVAL_AP_USED 125000 // in milliSeconds -#define WIFI_SCAN_INTERVAL_MINIMAL 60000 // in milliSeconds - - -#ifdef ESPEASY_WIFI_CLEANUP_WORK_IN_PROGRESS - -enum class WiFiState_e { - // WiFi radio off - OFF, - // Only running in AP mode - AP_only, - // WiFi was in some kind of error state, waiting period - ErrorRecovery, - // STA mode + scanning - STA_Scanning, - // STA+AP mode + scanning, - // needs some careful handling to prevent disconnecting the connected stations - STA_AP_Scanning, - // Connecting to an AP - STA_Connecting, - // Reconnecting to an AP - // May need to handle some specific disconnect reasons differently from connecting for the first time. - STA_Reconnecting, - // Connected to an AP - STA_Connected -}; - - -class ESPEasyWiFi_t { -public: - - // Start the process of connecting or start AP, depending on the existing configuration. - bool begin(); - - // Terminate WiFi activity - void end(); - - // Process the state machine for managing WiFi connection - void loop(); - - WiFiState_e getState() const { return _state; } - - // Get the IP-address in this order: - // - STA interface if connected, - // - AP interface if active - // - 0.0.0.0 if neither connected nor active. - IPAddress getIP() const; - - void disconnect(); - - - - -private: - - // Handle timeouts + start of AP mode - void checkConnectProgress(); - - // Check to see if we already have some AP to connect to. - void checkScanningProgress(); - - void startScanning(); - - bool connectSTA(); - - - WiFi_AP_Candidate _active_sta; - WiFi_AP_Candidate _AP_conf; - - String _last_ssid; - MAC_address _last_bssid; - uint8_t _last_channel = 0; - WiFiState_e _state = WiFiState_e::OFF; - - LongTermTimer _last_state_change; - LongTermTimer _last_seen_connected; -}; - - -#endif // ESPEASY_WIFI_CLEANUP_WORK_IN_PROGRESS - -bool WiFiConnected(); -void WiFiConnectRelaxed(); -void AttemptWiFiConnect(); -bool prepareWiFi(); -bool checkAndResetWiFi(); -void resetWiFi(); -void initWiFi(); - -#ifdef ESP32 -void removeWiFiEventHandler(); -void registerWiFiEventHandler(); -#endif - -#if FEATURE_SET_WIFI_TX_PWR -void SetWiFiTXpower(); -void SetWiFiTXpower(float dBm); // 0-20.5 -void SetWiFiTXpower(float dBm, float rssi); -#endif -float GetRSSIthreshold(float& maxTXpwr); -// Return some quality based on RSSI. -// <-97 => 0 , >-50 => 10 -// -97 ... -50 => 1 ... 9 -int GetRSSI_quality(); -WiFiConnectionProtocol getConnectionProtocol(); -#ifdef ESP32 -// TSF time is 64-bit timer in usec, sent by the AP along with other packets. -// On tested access points, this seems to be the uptime in usec. -// Could be used among nodes connected to the same AP to increase time sync accuracy. -int64_t WiFi_get_TSF_time(); -#endif -void WifiDisconnect(); -bool WiFiScanAllowed(); -void WifiScan(bool async, uint8_t channel = 0); -void WiFiScan_log_to_serial(); -void setSTA(bool enable); -void setAP(bool enable); -const __FlashStringHelper * getWifiModeString(WiFiMode_t wifimode); -void setWifiMode(WiFiMode_t wifimode); -bool WifiIsAP(WiFiMode_t wifimode); -bool WifiIsSTA(WiFiMode_t wifimode); -bool WiFiUseStaticIP(); -bool wifiAPmodeActivelyUsed(); -void setConnectionSpeed(); -void setupStaticIPconfig(); -String formatScanResult(int i, const String& separator); -String formatScanResult(int i, const String& separator, int32_t& rssi); - -void logConnectionStatus(); - - -#endif // ESPEASY_WIFI_H \ No newline at end of file diff --git a/src/src/ESPEasyCore/ESPEasyWifi_ProcessEvent.cpp b/src/src/ESPEasyCore/ESPEasyWifi_ProcessEvent.cpp deleted file mode 100644 index ba4761597..000000000 --- a/src/src/ESPEasyCore/ESPEasyWifi_ProcessEvent.cpp +++ /dev/null @@ -1,611 +0,0 @@ -#include "../ESPEasyCore/ESPEasyWifi_ProcessEvent.h" - -#include "../../ESPEasy-Globals.h" - -#if FEATURE_ETHERNET -#include "../ESPEasyCore/ESPEasyEth_ProcessEvent.h" -#endif -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyWifi.h" - -#include "../Globals/ESPEasyWiFiEvent.h" -#include "../Globals/ESPEasy_Scheduler.h" -#include "../Globals/ESPEasy_time.h" -#include "../Globals/EventQueue.h" -#include "../Globals/MQTT.h" -#include "../Globals/NetworkState.h" -#include "../Globals/RTC.h" -#include "../Globals/SecuritySettings.h" -#include "../Globals/Services.h" -#include "../Globals/Settings.h" -#include "../Globals/WiFi_AP_Candidates.h" - -#include "../Helpers/Convert.h" -#include "../Helpers/ESPEasyRTC.h" -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/Network.h" -#include "../Helpers/Networking.h" -#include "../Helpers/PeriodicalActions.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/StringGenerator_WiFi.h" -#include "../Helpers/StringProvider.h" - -// #include "../ESPEasyCore/ESPEasyEth.h" -// #include "../ESPEasyCore/ESPEasyWiFiEvent.h" -// #include "../ESPEasyCore/ESPEasy_Log.h" -// #include "../Helpers/ESPEasy_time_calc.h" -// #include "../Helpers/Misc.h" -// #include "../Helpers/Scheduler.h" - -#include "../WebServer/ESPEasy_WebServer.h" - - -// ******************************************************************************** -// Called from the loop() to make sure events are processed as soon as possible. -// These functions are called from Setup() or Loop() and thus may call delay() or yield() -// ******************************************************************************** -void handle_unprocessedNetworkEvents() -{ -#if FEATURE_ETHERNET - handle_unprocessedEthEvents(); -#endif - - if (active_network_medium == NetworkMedium_t::WIFI) { - const bool should_be_initialized = (WiFiEventData.WiFiGotIP() && WiFiEventData.WiFiConnected()) || NetworkConnected(); - if (WiFiEventData.WiFiServicesInitialized() != should_be_initialized) - { - if (!WiFiEventData.WiFiServicesInitialized()) { - WiFiEventData.processedDHCPTimeout = true; // FIXME TD-er: Find out when this happens (happens on ESP32 sometimes) - if (WiFiConnected()) { - if (!WiFiEventData.WiFiGotIP()) { - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("WiFi : Missed gotIP event")); - #endif - WiFiEventData.processedGotIP = false; - processGotIP(); - } - if (!WiFiEventData.WiFiConnected()) { - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("WiFi : Missed connected event")); - #endif - WiFiEventData.processedConnect = false; - processConnect(); - } - // Apparently we are connected, so no need to process any late disconnect event - WiFiEventData.processedDisconnect = true; - } - WiFiEventData.setWiFiServicesInitialized(); -//#ifdef ESP32 - setWebserverRunning(false); - delay(1); - setWebserverRunning(true); - delay(1); -/* -#else - CheckRunningServices(); -#endif -*/ - } - } - } - - if (WiFiEventData.unprocessedWifiEvents()) { - // Process disconnect events before connect events. - if (!WiFiEventData.processedDisconnect) { - #ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("WIFI : Entering processDisconnect()")); - #endif // ifndef BUILD_NO_DEBUG - processDisconnect(); - } - } - - if (active_network_medium == NetworkMedium_t::WIFI) { - if ((!WiFiEventData.WiFiServicesInitialized()) || WiFiEventData.unprocessedWifiEvents() || WiFiEventData.wifiConnectAttemptNeeded) { - // WiFi connection is not yet available, so introduce some extra delays to - // help the background tasks managing wifi connections - delay(0); - - if (!WiFiEventData.processedConnect) { - #ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("WIFI : Entering processConnect()")); - #endif // ifndef BUILD_NO_DEBUG - processConnect(); - } - - if (!WiFiEventData.processedGotIP) { - #ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("WIFI : Entering processGotIP()")); - #endif // ifndef BUILD_NO_DEBUG - processGotIP(); - } - - if (!WiFiEventData.processedDHCPTimeout) { - #ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_INFO, F("WIFI : DHCP timeout, Calling disconnect()")); - #endif // ifndef BUILD_NO_DEBUG - WiFiEventData.processedDHCPTimeout = true; - WifiDisconnect(); - } - - if (WiFi.status() == WL_DISCONNECTED && WiFiEventData.wifiConnectInProgress) { - if (WiFiEventData.last_wifi_connect_attempt_moment.isSet() && - WiFiEventData.last_wifi_connect_attempt_moment.timeoutReached(DEFAULT_WIFI_CONNECTION_TIMEOUT)) { - logConnectionStatus(); - resetWiFi(); - } - if (!WiFiEventData.last_wifi_connect_attempt_moment.isSet()) { - WiFiEventData.wifiConnectInProgress = false; - } - delay(10); - } - - if (!WiFiEventData.wifiConnectInProgress) { - WiFiEventData.wifiConnectAttemptNeeded = true; - NetworkConnectRelaxed(); - } - } - - - if (WiFiEventData.WiFiDisconnected()) { - #ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - static LongTermTimer lastDisconnectMoment_log; - static uint8_t lastWiFiStatus_log = 0; - uint8_t cur_wifi_status = WiFi.status(); - if (WiFiEventData.lastDisconnectMoment.get() != lastDisconnectMoment_log.get() || - lastWiFiStatus_log != cur_wifi_status) { - lastDisconnectMoment_log.set(WiFiEventData.lastDisconnectMoment.get()); - lastWiFiStatus_log = cur_wifi_status; - String wifilog = F("WIFI : Disconnected: WiFi.status() = "); - wifilog += WiFiEventData.ESPeasyWifiStatusToString(); - wifilog += F(" RSSI: "); - wifilog += String(WiFi.RSSI()); - wifilog += F(" status: "); - #ifdef ESP8266 - station_status_t status = wifi_station_get_connect_status(); - wifilog += SDKwifiStatusToString(status); - #endif - #ifdef ESP32 - wifilog += ArduinoWifiStatusToString(WiFi.status()); - #endif - addLogMove(LOG_LEVEL_DEBUG, wifilog); - } - } - #endif // ifndef BUILD_NO_DEBUG - - // While connecting to WiFi make sure the device has ample time to do so - delay(10); - } - - if (!WiFiEventData.processedDisconnectAPmode) { processDisconnectAPmode(); } - - if (!WiFiEventData.processedConnectAPmode) { processConnectAPmode(); } - - if (WiFiEventData.timerAPoff.isSet()) { processDisableAPmode(); } - - if (!WiFiEventData.processedScanDone) { processScanDone(); } - - if (WiFiEventData.wifi_connect_attempt > 0) { - // We only want to clear this counter if the connection is currently stable. - if (WiFiEventData.WiFiServicesInitialized()) { - if (WiFiEventData.lastConnectMoment.isSet() && WiFiEventData.lastConnectMoment.timeoutReached(WIFI_CONNECTION_CONSIDERED_STABLE)) { - // Connection considered stable - WiFiEventData.wifi_connect_attempt = 0; - WiFiEventData.wifi_considered_stable = true; - WiFi_AP_Candidates.markCurrentConnectionStable(); - - if (WiFi.getAutoReconnect() != Settings.SDK_WiFi_autoreconnect()) { - WiFi.setAutoReconnect(Settings.SDK_WiFi_autoreconnect()); - delay(1); - } - } else { - if (WiFi.getAutoReconnect()) { - WiFi.setAutoReconnect(false); - delay(1); - } - } - } - } - } -#if FEATURE_ETHERNET - check_Eth_DNS_valid(); -#endif // if FEATURE_ETHERNET - -#if FEATURE_ESPEASY_P2P - updateUDPport(false); -#endif -} - -// ******************************************************************************** -// Functions to process the data gathered from the events. -// These functions are called from Setup() or Loop() and thus may call delay() or yield() -// ******************************************************************************** -void processDisconnect() { - if (WiFiEventData.processedDisconnect) { return; } - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = strformat( - F("WIFI : Disconnected! Reason: '%s'"), - getLastDisconnectReason().c_str()); - - if (WiFiEventData.lastConnectedDuration_us > 0) { - log += concat( - F(" Connected for "), - format_msec_duration(WiFiEventData.lastConnectedDuration_us / 1000ll)); - } - addLogMove(LOG_LEVEL_INFO, log); - } - logConnectionStatus(); - - if (WiFiEventData.processingDisconnect.isSet()) { - if (WiFiEventData.processingDisconnect.millisPassedSince() > 5000 || WiFiEventData.processedDisconnect) { - WiFiEventData.processedDisconnect = true; - WiFiEventData.processingDisconnect.clear(); - } - } - - - if (WiFiEventData.processedDisconnect || - WiFiEventData.processingDisconnect.isSet()) { return; } - WiFiEventData.processingDisconnect.setNow(); - WiFiEventData.setWiFiDisconnected(); - WiFiEventData.wifiConnectAttemptNeeded = true; - delay(100); // FIXME TD-er: See https://github.com/letscontrolit/ESPEasy/issues/1987#issuecomment-451644424 - - if (Settings.UseRules) { - eventQueue.add(F("WiFi#Disconnected")); - } - - - // FIXME TD-er: With AutoReconnect enabled, WiFi must be reset or else we completely loose track of the actual WiFi state - bool mustRestartWiFi = Settings.WiFiRestart_connection_lost() || WiFi.getAutoReconnect(); - if (WiFiEventData.lastConnectedDuration_us > 0 && (WiFiEventData.lastConnectedDuration_us / 1000) < 5000) { - if (!WiFi_AP_Candidates.getBestCandidate().usable()) -// addLog(LOG_LEVEL_INFO, F("WIFI : !getBestCandidate().usable() => mustRestartWiFi = true")); - - mustRestartWiFi = true; - } - - if (WiFi.status() == WL_IDLE_STATUS) { -// addLog(LOG_LEVEL_INFO, F("WIFI : WiFi.status() == WL_IDLE_STATUS => mustRestartWiFi = true")); - mustRestartWiFi = true; - } - - - #ifdef USES_ESPEASY_NOW - if (use_EspEasy_now) { -// mustRestartWiFi = true; - } - #endif - //WifiDisconnect(); // Needed or else node may not reconnect reliably. - - if (mustRestartWiFi) { - WiFiEventData.processedDisconnect = true; - resetWiFi(); -// WifiScan(false); -// delay(100); -// setWifiMode(WIFI_OFF); -// initWiFi(); -// delay(100); - } -// delay(500); - logConnectionStatus(); - WiFiEventData.processedDisconnect = true; - WiFiEventData.processingDisconnect.clear(); -} - -void processConnect() { - if (WiFiEventData.processedConnect) { return; } - //delay(100); // FIXME TD-er: See https://github.com/letscontrolit/ESPEasy/issues/1987#issuecomment-451644424 - if (checkAndResetWiFi()) { - return; - } - WiFiEventData.processedConnect = true; - if (WiFi.status() == WL_DISCONNECTED) { - // Apparently not really connected - return; - } - - WiFiEventData.setWiFiConnected(); - ++WiFiEventData.wifi_reconnects; - - if (WiFi_AP_Candidates.getCurrent().bits.isEmergencyFallback) { - #ifdef CUSTOM_EMERGENCY_FALLBACK_RESET_CREDENTIALS - const bool mustResetCredentials = CUSTOM_EMERGENCY_FALLBACK_RESET_CREDENTIALS; - #else - const bool mustResetCredentials = false; - #endif - #ifdef CUSTOM_EMERGENCY_FALLBACK_START_AP - const bool mustStartAP = CUSTOM_EMERGENCY_FALLBACK_START_AP; - #else - const bool mustStartAP = false; - #endif - if (mustStartAP) { - int allowedUptimeMinutes = 10; - #ifdef CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME - allowedUptimeMinutes = CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME; - #endif - if (getUptimeMinutes() < allowedUptimeMinutes) { - WiFiEventData.timerAPstart.setNow(); - } - } - if (mustResetCredentials && !WiFiEventData.performedClearWiFiCredentials) { - WiFiEventData.performedClearWiFiCredentials = true; - SecuritySettings.clearWiFiCredentials(); - SaveSecuritySettings(); - WiFiEventData.markDisconnect(WIFI_DISCONNECT_REASON_AUTH_EXPIRE); - WiFi_AP_Candidates.force_reload(); - } - } - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - const LongTermTimer::Duration connect_duration = WiFiEventData.last_wifi_connect_attempt_moment.timeDiff(WiFiEventData.lastConnectMoment); - String log = strformat( - F("WIFI : Connected! AP: %s (%s) Ch: %d"), - WiFi.SSID().c_str(), - WiFi.BSSIDstr().c_str(), - RTC.lastWiFiChannel); - - if ((connect_duration > 0ll) && (connect_duration < 30000000ll)) { - // Just log times when they make sense. - log += strformat( - F(" Duration: %d ms"), - static_cast(connect_duration / 1000)); - } - addLogMove(LOG_LEVEL_INFO, log); - } - -// WiFiEventData.last_wifi_connect_attempt_moment.clear(); - - if (Settings.UseRules) { - if (WiFiEventData.bssid_changed) { - eventQueue.add(F("WiFi#ChangedAccesspoint")); - } - - if (WiFiEventData.channel_changed) { - eventQueue.add(F("WiFi#ChangedWiFichannel")); - } - } - - if (useStaticIP()) { - WiFiEventData.markGotIP(); // in static IP config the got IP event is never fired. - } - saveToRTC(); - - logConnectionStatus(); -} - -void processGotIP() { - if (WiFiEventData.processedGotIP) { - return; - } - if (checkAndResetWiFi()) { - return; - } - - IPAddress ip = NetworkLocalIP(); - - if (!useStaticIP()) { - #ifdef ESP8266 - if (!ip.isSet()) { - #else - if (ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0) { - #endif - return; - } - } - const IPAddress gw = WiFi.gatewayIP(); - const IPAddress subnet = WiFi.subnetMask(); - const LongTermTimer::Duration dhcp_duration = WiFiEventData.lastConnectMoment.timeDiff(WiFiEventData.lastGetIPmoment); - WiFiEventData.dns0_cache = WiFi.dnsIP(0); - WiFiEventData.dns1_cache = WiFi.dnsIP(1); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = strformat( - F("WIFI : %s (%s) GW: %s SN: %s DNS: %s"), - concat(useStaticIP() ? F("Static IP: ") : F("DHCP IP: "), formatIP(ip)).c_str(), - NetworkGetHostname().c_str(), - formatIP(gw).c_str(), - formatIP(subnet).c_str(), - getValue(LabelType::DNS).c_str()); - - if ((dhcp_duration > 0ll) && (dhcp_duration < 30000000ll)) { - // Just log times when they make sense. - log += strformat(F(" duration: %d ms"), static_cast(dhcp_duration / 1000)); - } - addLogMove(LOG_LEVEL_INFO, log); - } - - // Might not work in core 2.5.0 - // See https://github.com/esp8266/Arduino/issues/5839 - if ((Settings.IP_Octet != 0) && (Settings.IP_Octet != 255)) - { - ip[3] = Settings.IP_Octet; - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, concat(F("IP : Fixed IP octet:"), formatIP(ip))); - } - WiFi.config(ip, gw, subnet, WiFiEventData.dns0_cache, WiFiEventData.dns1_cache); - } - -#if FEATURE_MQTT - mqtt_reconnect_count = 0; - MQTTclient_should_reconnect = true; - timermqtt_interval = 100; - Scheduler.setIntervalTimer(SchedulerIntervalTimer_e::TIMER_MQTT); - scheduleNextMQTTdelayQueue(); -#endif // if FEATURE_MQTT - Scheduler.sendGratuitousARP_now(); - - if (Settings.UseRules) - { - eventQueue.add(F("WiFi#Connected")); - } - statusLED(true); - - // WiFi.scanDelete(); - - if (WiFiEventData.wifiSetup) { - // Wifi setup was active, Apparently these settings work. - WiFiEventData.wifiSetup = false; - SaveSecuritySettings(); - } - - if ((WiFiEventData.WiFiConnected() || WiFi.isConnected()) && hasIPaddr()) { - WiFiEventData.setWiFiGotIP(); - } - #if FEATURE_ESPEASY_P2P - refreshNodeList(); - #endif - logConnectionStatus(); -} - -#if FEATURE_USE_IPV6 -void processGotIPv6() { - if (!WiFiEventData.processedGotIP6) { - WiFiEventData.processedGotIP6 = true; - if (loglevelActiveFor(LOG_LEVEL_INFO)) - addLog(LOG_LEVEL_INFO, String(F("WIFI : STA got IP6 ")) + WiFiEventData.unprocessed_IP6.toString(true)); -#if FEATURE_ESPEASY_P2P -// updateUDPport(true); -#endif - } -} -#endif - -// A client disconnected from the AP on this node. -void processDisconnectAPmode() { - if (WiFiEventData.processedDisconnectAPmode) { return; } - WiFiEventData.processedDisconnectAPmode = true; - -#ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - const int nrStationsConnected = WiFi.softAPgetStationNum(); - String log = F("AP Mode: Client disconnected: "); - log += WiFiEventData.lastMacDisconnectedAPmode.toString(); - log += F(" Connected devices: "); - log += nrStationsConnected; - addLogMove(LOG_LEVEL_INFO, log); - } -#endif -} - -// Client connects to AP on this node -void processConnectAPmode() { - if (WiFiEventData.processedConnectAPmode) { return; } - WiFiEventData.processedConnectAPmode = true; - // Extend timer to switch off AP. - WiFiEventData.timerAPoff.setMillisFromNow(WIFI_AP_OFF_TIMER_DURATION); -#ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("AP Mode: Client connected: "); - log += WiFiEventData.lastMacConnectedAPmode.toString(); - log += F(" Connected devices: "); - log += WiFi.softAPgetStationNum(); - addLogMove(LOG_LEVEL_INFO, log); - } -#endif - - #if FEATURE_DNS_SERVER - // Start DNS, only used if the ESP has no valid WiFi config - // It will reply with it's own address on all DNS requests - // (captive portal concept) - if (!dnsServerActive) { - dnsServerActive = true; - dnsServer.start(DNS_PORT, "*", apIP); - } - #endif // if FEATURE_DNS_SERVER -} - -// Switch of AP mode when timeout reached and no client connected anymore. -void processDisableAPmode() { - if (!WiFiEventData.timerAPoff.isSet()) { return; } - - if (!WifiIsAP(WiFi.getMode())) { - return; - } - // disable AP after timeout and no clients connected. - if (WiFiEventData.timerAPoff.timeReached() && (WiFi.softAPgetStationNum() == 0)) { - setAP(false); - } - - if (!WifiIsAP(WiFi.getMode())) { - WiFiEventData.timerAPoff.clear(); - if (WiFiEventData.wifiConnectAttemptNeeded) { - // Force a reconnect cycle - WifiDisconnect(); - } - } -} - -void processScanDone() { - WiFi_AP_Candidates.load_knownCredentials(); - if (WiFiEventData.processedScanDone) { return; } - - - - // Better act on the scan done event, as it may get triggered for normal wifi begin calls. - int8_t scanCompleteStatus = WiFi.scanComplete(); - switch (scanCompleteStatus) { - case 0: // Nothing (yet) found - if (WiFiEventData.lastGetScanMoment.timeoutReached(5000)) { - WiFi.scanDelete(); - WiFiEventData.processedScanDone = true; - } - return; - case -1: // WIFI_SCAN_RUNNING - // FIXME TD-er: Set timeout... - if (WiFiEventData.lastGetScanMoment.timeoutReached(5000)) { - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_ERROR, F("WiFi : Scan Running Timeout")); - #endif - WiFi.scanDelete(); - WiFiEventData.processedScanDone = true; - } - return; - case -2: // WIFI_SCAN_FAILED - addLog(LOG_LEVEL_ERROR, F("WiFi : Scan failed")); - WiFi.scanDelete(); - WiFiEventData.processedScanDone = true; - return; - } - - WiFiEventData.lastGetScanMoment.setNow(); - WiFiEventData.processedScanDone = true; -# ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, concat(F("WiFi : Scan finished, found: "), scanCompleteStatus)); - } -#endif - -#if !FEATURE_ESP8266_DIRECT_WIFI_SCAN - WiFi_AP_Candidates.process_WiFiscan(scanCompleteStatus); -#endif - WiFi_AP_Candidates.load_knownCredentials(); - - if (WiFi_AP_Candidates.addedKnownCandidate() && !NetworkConnected()) { - if (!WiFiEventData.wifiConnectInProgress) { - WiFiEventData.wifiConnectAttemptNeeded = true; - # ifndef BUILD_NO_DEBUG - if (WiFi_AP_Candidates.addedKnownCandidate()) { - addLog(LOG_LEVEL_INFO, F("WiFi : Added known candidate, try to connect")); - } - #endif -#ifdef ESP32 -// setSTA(false); -#endif - NetworkConnectRelaxed(); -#ifdef USES_ESPEASY_NOW - temp_disable_EspEasy_now_timer = millis() + 20000; -#endif - } - } else if (!WiFiEventData.wifiConnectInProgress && !NetworkConnected()) { - WiFiEventData.timerAPstart.setNow(); - } - -} - - - - diff --git a/src/src/ESPEasyCore/ESPEasyWifi_ProcessEvent.h b/src/src/ESPEasyCore/ESPEasyWifi_ProcessEvent.h deleted file mode 100644 index 826bda61d..000000000 --- a/src/src/ESPEasyCore/ESPEasyWifi_ProcessEvent.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef ESPEASYCORE_ESPEASYWIFI_PROCESSEVENT_H -#define ESPEASYCORE_ESPEASYWIFI_PROCESSEVENT_H - -#include "../../ESPEasy_common.h" - -void handle_unprocessedNetworkEvents(); -void processDisconnect(); -void processConnect(); -void processGotIP(); -#if FEATURE_USE_IPV6 -void processGotIPv6(); -#endif -void processDisconnectAPmode(); -void processConnectAPmode(); -void processDisableAPmode(); -void processScanDone(); - -#endif // ifndef ESPEASYCORE_ESPEASYWIFI_PROCESSEVENT_H diff --git a/src/src/ESPEasyCore/ESPEasy_Console.cpp b/src/src/ESPEasyCore/ESPEasy_Console.cpp index d0ee0202c..8b4ff8384 100644 --- a/src/src/ESPEasyCore/ESPEasy_Console.cpp +++ b/src/src/ESPEasyCore/ESPEasy_Console.cpp @@ -16,8 +16,18 @@ #include +#ifdef ESP32 +#include +#endif -EspEasy_Console_t::EspEasy_Console_t() + +EspEasy_Console_t::EspEasy_Console_t() : + _mainSerial(LOG_TO_SERIAL) +#if FEATURE_DEFINE_SERIAL_CONSOLE_PORT +# if USES_ESPEASY_CONSOLE_FALLBACK_PORT + , _fallbackSerial(LOG_TO_SERIAL_EXTRA) +# endif +#endif // if FEATURE_DEFINE_SERIAL_CONSOLE_PORT { #if FEATURE_DEFINE_SERIAL_CONSOLE_PORT const ESPEasySerialPort port = static_cast(_console_serial_port); @@ -121,7 +131,7 @@ void EspEasy_Console_t::reInit() _fallbackSerial._serial->end(); delete _fallbackSerial._serial; _fallbackSerial._serial = nullptr; - somethingChanged = true; + somethingChanged = true; } } # endif // if USES_ESPEASY_CONSOLE_FALLBACK_PORT @@ -136,7 +146,7 @@ void EspEasy_Console_t::reInit() _mainSerial._serial->end(); delete _mainSerial._serial; _mainSerial._serial = nullptr; - somethingChanged = true; + somethingChanged = true; } _console_serial_port = Settings.console_serial_port; @@ -153,16 +163,17 @@ void EspEasy_Console_t::reInit() const ESPEasySerialPort mainSerialPort = static_cast(_console_serial_port); -#if USES_HWCDC +# if USES_HWCDC + if (mainSerialPort == ESPEasySerialPort::usb_hw_cdc) { buffsize = 2048; } -#endif // if USES_HWCDC +# endif // if USES_HWCDC _mainSerial._serial = new (std::nothrow) ESPeasySerial( mainSerialPort, _console_serial_rxpin, - _console_serial_txpin, + _console_serial_txpin, false, buffsize); somethingChanged = true; @@ -191,6 +202,7 @@ void EspEasy_Console_t::reInit() _mainSerial._serialWriteBuffer.clear(); } #endif // if FEATURE_DEFINE_SERIAL_CONSOLE_PORT + if (somethingChanged) { begin(Settings.BaudRate); } @@ -208,9 +220,9 @@ void EspEasy_Console_t::begin(uint32_t baudrate) #else // if FEATURE_DEFINE_SERIAL_CONSOLE_PORT # ifdef ESP8266 _mainSerial._serial->begin(baudrate); - #ifndef BUILD_MINIMAL_OTA +# ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("ESPEasy console using HW Serial")); - #endif +# endif # endif // ifdef ESP8266 # ifdef ESP32 @@ -228,6 +240,13 @@ void EspEasy_Console_t::begin(uint32_t baudrate) if (_fallbackSerial._serial != nullptr) { _fallbackSerial._serial->begin(baudrate); + +#ifdef ESP32 + // Need to have this string as C-string, not F-string + perimanSetPinBusExtraType(SOC_RX0, "Console"); + perimanSetPinBusExtraType(SOC_TX0, "Console"); +#endif + addLog(LOG_LEVEL_INFO, F("ESPEasy console fallback enabled")); } #endif // if USES_ESPEASY_CONSOLE_FALLBACK_PORT @@ -260,7 +279,7 @@ void EspEasy_Console_t::init() { void EspEasy_Console_t::loop() { - if (!Settings.UseSerial) return; + if (!Settings.UseSerial) { return; } START_TIMER; @@ -296,40 +315,6 @@ void EspEasy_Console_t::loop() STOP_TIMER(CONSOLE_LOOP); } -void EspEasy_Console_t::addToSerialBuffer(const __FlashStringHelper *line) -{ - addToSerialBuffer(String(line)); -} - -void EspEasy_Console_t::addToSerialBuffer(char c) -{ - _mainSerial.addToSerialBuffer(c); -#if USES_ESPEASY_CONSOLE_FALLBACK_PORT - - _fallbackSerial.addToSerialBuffer(c); -#endif // if USES_ESPEASY_CONSOLE_FALLBACK_PORT - process_serialWriteBuffer(); -} - -void EspEasy_Console_t::addToSerialBuffer(const String& line) { - process_serialWriteBuffer(); - - _mainSerial.addToSerialBuffer(line); -#if USES_ESPEASY_CONSOLE_FALLBACK_PORT - - _fallbackSerial.addToSerialBuffer(line); -#endif // if USES_ESPEASY_CONSOLE_FALLBACK_PORT - process_serialWriteBuffer(); -} - -void EspEasy_Console_t::addNewlineToSerialBuffer() { - _mainSerial.addNewlineToSerialBuffer(); -#if USES_ESPEASY_CONSOLE_FALLBACK_PORT - _fallbackSerial.addNewlineToSerialBuffer(); -#endif // if USES_ESPEASY_CONSOLE_FALLBACK_PORT - process_serialWriteBuffer(); -} - bool EspEasy_Console_t::process_serialWriteBuffer() { START_TIMER; bool res = false; @@ -364,6 +349,7 @@ String EspEasy_Console_t::getPortDescription() const } #if USES_ESPEASY_CONSOLE_FALLBACK_PORT + String EspEasy_Console_t::getFallbackPortDescription() const { return _fallbackSerial.getPortDescription(); @@ -420,6 +406,7 @@ void EspEasy_Console_t::readInput(EspEasy_Console_Port& port) } #if FEATURE_DEFINE_SERIAL_CONSOLE_PORT + ESPeasySerial * EspEasy_Console_t::getPort() { if (_mainSerial._serial != nullptr) { @@ -435,6 +422,7 @@ ESPeasySerial * EspEasy_Console_t::getPort() } #else // if FEATURE_DEFINE_SERIAL_CONSOLE_PORT + HardwareSerial * EspEasy_Console_t::getPort() { if (_mainSerial._serial != nullptr) { @@ -445,7 +433,6 @@ HardwareSerial * EspEasy_Console_t::getPort() #endif // if FEATURE_DEFINE_SERIAL_CONSOLE_PORT - void EspEasy_Console_t::endPort() { _mainSerial.endPort(); diff --git a/src/src/ESPEasyCore/ESPEasy_Console.h b/src/src/ESPEasyCore/ESPEasy_Console.h index afbf9144c..a3c31c5cb 100644 --- a/src/src/ESPEasyCore/ESPEasy_Console.h +++ b/src/src/ESPEasyCore/ESPEasy_Console.h @@ -21,12 +21,6 @@ public: // Process data from serial port void loop(); - void addToSerialBuffer(const __FlashStringHelper *line); - void addToSerialBuffer(const String& line); - void addToSerialBuffer(char c); - - void addNewlineToSerialBuffer(); - // Return true when something got written, or when the buffer was already empty bool process_serialWriteBuffer(); diff --git a/src/src/ESPEasyCore/ESPEasy_Console_Port.cpp b/src/src/ESPEasyCore/ESPEasy_Console_Port.cpp index dc91f0872..5f36d2aa2 100644 --- a/src/src/ESPEasyCore/ESPEasy_Console_Port.cpp +++ b/src/src/ESPEasyCore/ESPEasy_Console_Port.cpp @@ -12,16 +12,30 @@ #include "../Globals/Settings.h" #include "../Helpers/Memory.h" +#include "../Helpers/StringConverter.h" #include +#ifdef ESP32 +#define CONSOLE_INPUT_BUFFER_SIZE 1280 +#else +#define CONSOLE_INPUT_BUFFER_SIZE 128 +#endif + + /* #if FEATURE_DEFINE_SERIAL_CONSOLE_PORT # include "../Helpers/_Plugin_Helper_serial.h" #endif // if FEATURE_DEFINE_SERIAL_CONSOLE_PORT */ +EspEasy_Console_Port::EspEasy_Console_Port(LogDestination log_destination) +: _serialWriteBuffer(log_destination) +{ + InputBuffer_Serial = (char*)calloc(1, CONSOLE_INPUT_BUFFER_SIZE); +} + EspEasy_Console_Port::~EspEasy_Console_Port() { #if FEATURE_DEFINE_SERIAL_CONSOLE_PORT @@ -30,6 +44,7 @@ EspEasy_Console_Port::~EspEasy_Console_Port() _serial = nullptr; } #endif + free(InputBuffer_Serial); } EspEasy_Console_Port::operator bool() const @@ -96,35 +111,14 @@ void EspEasy_Console_Port::endPort() } } -void EspEasy_Console_Port::addToSerialBuffer(char c) -{ - if (_serial != nullptr) { - _serialWriteBuffer.add(c); - } -} - -void EspEasy_Console_Port::addToSerialBuffer(const String& line) -{ - if (_serial != nullptr) { - _serialWriteBuffer.add(line); - } -} - -void EspEasy_Console_Port::addNewlineToSerialBuffer() -{ - if (_serial != nullptr) { - _serialWriteBuffer.addNewline(); - } -} bool EspEasy_Console_Port::process_serialWriteBuffer() { if (_serial != nullptr) { - const int snip = _serial->availableForWrite(); - - if (snip > 0) { - return _serialWriteBuffer.write(*_serial, snip) != 0; - } +#ifdef ESP32 + if (!xPortCanYield()) return false; +#endif + return _serialWriteBuffer.process(_serial, _serial->availableForWrite()); } return false; } @@ -147,10 +141,12 @@ bool EspEasy_Console_Port::process_consoleInput(uint8_t SerialInByte) // Ignore empty command if (SerialInByteCounter != 0) { InputBuffer_Serial[SerialInByteCounter] = 0; // serial data completed - addToSerialBuffer('>'); + String cmd(InputBuffer_Serial); - addToSerialBuffer(cmd); - addToSerialBuffer('\n'); +#if !FEATURE_COLORIZE_CONSOLE_LOGS + Logging.consolePrintln(concat('>', cmd)); +#endif + ExecuteCommand_all({EventValueSource::Enum::VALUE_SOURCE_SERIAL, std::move(cmd)}, true); SerialInByteCounter = 0; InputBuffer_Serial[0] = 0; // serial data processed, clear buffer diff --git a/src/src/ESPEasyCore/ESPEasy_Console_Port.h b/src/src/ESPEasyCore/ESPEasy_Console_Port.h index d92d027c6..ec7db6fda 100644 --- a/src/src/ESPEasyCore/ESPEasy_Console_Port.h +++ b/src/src/ESPEasyCore/ESPEasy_Console_Port.h @@ -17,9 +17,9 @@ #endif -#define CONSOLE_INPUT_BUFFER_SIZE 128 - struct EspEasy_Console_Port { + + EspEasy_Console_Port(LogDestination log_destination); ~EspEasy_Console_Port(); operator bool() const; @@ -29,9 +29,6 @@ struct EspEasy_Console_Port { void endPort(); - void addToSerialBuffer(char c); - void addToSerialBuffer(const String& line); - void addNewlineToSerialBuffer(); bool process_serialWriteBuffer(); bool process_consoleInput(uint8_t SerialInByte); @@ -39,10 +36,9 @@ struct EspEasy_Console_Port { String getPortDescription() const; int SerialInByteCounter{}; - char InputBuffer_Serial[CONSOLE_INPUT_BUFFER_SIZE + 2]{}; + char *InputBuffer_Serial{}; SerialWriteBuffer_t _serialWriteBuffer; - #if FEATURE_DEFINE_SERIAL_CONSOLE_PORT ESPeasySerial *_serial = nullptr; #else // if FEATURE_DEFINE_SERIAL_CONSOLE_PORT diff --git a/src/src/ESPEasyCore/ESPEasy_Log.cpp b/src/src/ESPEasyCore/ESPEasy_Log.cpp index bcf69137e..99bc7412a 100644 --- a/src/src/ESPEasyCore/ESPEasy_Log.cpp +++ b/src/src/ESPEasyCore/ESPEasy_Log.cpp @@ -1,89 +1,71 @@ #include "../ESPEasyCore/ESPEasy_Log.h" -#include "../DataStructs/LogStruct.h" +#include "../DataStructs/LogBuffer.h" #include "../ESPEasyCore/Serial.h" #include "../Globals/Cache.h" #include "../Globals/ESPEasy_Console.h" -#include "../Globals/ESPEasyWiFiEvent.h" +#include "../../ESPEasy/net/Globals/ESPEasyWiFiEvent.h" +#include "../../ESPEasy/net/ESPEasyNetwork.h" #include "../Globals/Logging.h" #include "../Globals/Settings.h" #include "../Helpers/Networking.h" #include "../Helpers/StringConverter.h" -#include - -#if FEATURE_SD -#include -#include "../Helpers/ESPEasy_Storage.h" -#endif #define UPDATE_LOGLEVEL_ACTIVE_CACHE_INTERVAL 5000 /********************************************************************************************\ - Init critical variables for logging (important during initial factory reset stuff ) - \*********************************************************************************************/ + Init critical variables for logging (important during initial factory reset stuff ) + \*********************************************************************************************/ void initLog() { - //make sure addLog doesnt do any stuff before initalisation of Settings is complete. - Settings.UseSerial=true; - Settings.SyslogFacility=0; - setLogLevelFor(LOG_TO_SYSLOG, 0); - setLogLevelFor(LOG_TO_SERIAL, 2); //logging during initialisation - setLogLevelFor(LOG_TO_WEBLOG, 2); - setLogLevelFor(LOG_TO_SDCARD, 0); -} + // make sure addLog doesnt do any stuff before initalisation of Settings is complete. + Settings.UseSerial = true; + Settings.SyslogFacility = 0; + #ifdef ESP32 + setLogLevelFor(LOG_TO_SYSLOG, 2); + setLogLevelFor(LOG_TO_WEBLOG, 2); + #else // ifdef ESP32 + setLogLevelFor(LOG_TO_SYSLOG, 0); + setLogLevelFor(LOG_TO_WEBLOG, 0); + #endif // ifdef ESP32 + + + setLogLevelFor(LOG_TO_SERIAL, 2); // logging during initialisation +#if FEATURE_SD + setLogLevelFor(LOG_TO_SDCARD, 0); +#endif +} /********************************************************************************************\ - Logging - \*********************************************************************************************/ -const __FlashStringHelper * getLogLevelDisplayString(int logLevel) { - switch (logLevel) { - case LOG_LEVEL_NONE: return F("None"); - case LOG_LEVEL_ERROR: return F("Error"); - case LOG_LEVEL_INFO: return F("Info"); -# ifndef BUILD_NO_DEBUG - case LOG_LEVEL_DEBUG: return F("Debug"); - case LOG_LEVEL_DEBUG_MORE: return F("Debug More"); - case LOG_LEVEL_DEBUG_DEV: return F("Debug dev"); -#endif - - default: - break; - } - return F(""); -} - -const __FlashStringHelper * getLogLevelDisplayStringFromIndex(uint8_t index, int& logLevel) { - switch (index) { - case 0: logLevel = LOG_LEVEL_ERROR; break; - case 1: logLevel = LOG_LEVEL_INFO; break; -# ifndef BUILD_NO_DEBUG - case 2: logLevel = LOG_LEVEL_DEBUG; break; - case 3: logLevel = LOG_LEVEL_DEBUG_MORE; break; - case 4: logLevel = LOG_LEVEL_DEBUG_DEV; break; -#endif - - default: logLevel = -1; return F(""); - } - return getLogLevelDisplayString(logLevel); -} - + Logging + \*********************************************************************************************/ void disableSerialLog() { log_to_serial_disabled = true; setLogLevelFor(LOG_TO_SERIAL, 0); } -void setLogLevelFor(uint8_t destination, uint8_t logLevel) { - switch (destination) { +void setLogLevelFor(LogDestination destination, uint8_t logLevel) { + switch (destination) + { case LOG_TO_SERIAL: - if (!log_to_serial_disabled || logLevel == 0) { - Settings.SerialLogLevel = logLevel; +#if USES_ESPEASY_CONSOLE_FALLBACK_PORT + case LOG_TO_SERIAL_EXTRA: +#endif + + if (!log_to_serial_disabled || (logLevel == 0)) { + Settings.SerialLogLevel = logLevel; } break; - case LOG_TO_SYSLOG: Settings.SyslogLevel = logLevel; break; - case LOG_TO_WEBLOG: Settings.WebLogLevel = logLevel; break; - case LOG_TO_SDCARD: Settings.SDLogLevel = logLevel; break; + case LOG_TO_SYSLOG: Settings.SyslogLevel = logLevel; + break; + case LOG_TO_WEBLOG: Settings.WebLogLevel = logLevel; + break; +#if FEATURE_SD + case LOG_TO_SDCARD: Settings.SDLogLevel = logLevel; + break; +#endif // if FEATURE_SD default: break; } @@ -92,8 +74,10 @@ void setLogLevelFor(uint8_t destination, uint8_t logLevel) { void updateLogLevelCache() { uint8_t max_lvl = 0; + // FIXME TD-er: Must add check whether SW serial may be using the same pins as Serial0 const bool useSerial = Settings.UseSerial && !activeTaskUseSerial0(); + if (log_to_serial_disabled) { if (useSerial) { ESPEasy_Console.setDebugOutput(false); @@ -101,13 +85,15 @@ void updateLogLevelCache() { } else { max_lvl = _max(max_lvl, Settings.SerialLogLevel); #ifndef BUILD_NO_DEBUG - if (useSerial && Settings.SerialLogLevel >= LOG_LEVEL_DEBUG_MORE) { + + if (useSerial && (Settings.SerialLogLevel >= LOG_LEVEL_DEBUG_MORE)) { ESPEasy_Console.setDebugOutput(true); } -#endif +#endif // ifndef BUILD_NO_DEBUG } max_lvl = _max(max_lvl, Settings.SyslogLevel); - if (Logging.logActiveRead()) { + + if (Logging.logActiveRead(LOG_TO_WEBLOG)) { max_lvl = _max(max_lvl, Settings.WebLogLevel); } #if FEATURE_SD @@ -118,17 +104,19 @@ void updateLogLevelCache() { bool loglevelActiveFor(uint8_t logLevel) { #ifdef ESP32 + if (xPortInIsrContext()) { // When called from an ISR, you should not send out logs. // Allocating memory from within an ISR is a big no-no. - // Also long-time blocking like sending logs (especially to a syslog server) + // Also long-time blocking like sending logs (especially to a syslog server) // is also really not a good idea from an ISR call. return false; } - #endif + #endif // ifdef ESP32 static uint32_t lastUpdateLogLevelCache = 0; - if (lastUpdateLogLevelCache == 0 || - timePassedSince(lastUpdateLogLevelCache) > UPDATE_LOGLEVEL_ACTIVE_CACHE_INTERVAL) + + if ((lastUpdateLogLevelCache == 0) || + (timePassedSince(lastUpdateLogLevelCache) > UPDATE_LOGLEVEL_ACTIVE_CACHE_INTERVAL)) { lastUpdateLogLevelCache = millis(); updateLogLevelCache(); @@ -138,12 +126,15 @@ bool loglevelActiveFor(uint8_t logLevel) { uint8_t getSerialLogLevel() { #if FEATURE_DEFINE_SERIAL_CONSOLE_PORT + // FIXME TD-er: Must add check whether SW serial may be using the same pins as Serial0 - if (log_to_serial_disabled || !Settings.UseSerial) return 0; -#else - if (log_to_serial_disabled || !Settings.UseSerial || activeTaskUseSerial0()) return 0; -#endif - if (!(WiFiEventData.WiFiServicesInitialized())){ + if (log_to_serial_disabled || !Settings.UseSerial) { return 0; } +#else // if FEATURE_DEFINE_SERIAL_CONSOLE_PORT + + if (log_to_serial_disabled || !Settings.UseSerial || activeTaskUseSerial0()) { return 0; } +#endif // if FEATURE_DEFINE_SERIAL_CONSOLE_PORT + + if (!ESPEasy::net::NetworkConnected()) { if (Settings.SerialLogLevel < LOG_LEVEL_INFO) { return LOG_LEVEL_INFO; } @@ -152,46 +143,63 @@ uint8_t getSerialLogLevel() { } uint8_t getWebLogLevel() { - if (Logging.logActiveRead()) { + if (Logging.logActiveRead(LOG_TO_WEBLOG)) { return Settings.WebLogLevel; - } + } + if (Settings.WebLogLevel != LOG_LEVEL_NONE) { updateLogLevelCache(); } return LOG_LEVEL_NONE; } -bool loglevelActiveFor(uint8_t destination, uint8_t logLevel) { +bool loglevelActiveFor(LogDestination destination, uint8_t logLevel) { #ifdef ESP32 + if (xPortInIsrContext()) { // When called from an ISR, you should not send out logs. // Allocating memory from within an ISR is a big no-no. - // Also long-time blocking like sending logs (especially to a syslog server) + // Also long-time blocking like sending logs (especially to a syslog server) // is also really not a good idea from an ISR call. return false; } - #endif + #endif // ifdef ESP32 + + if (!Logging.logActiveRead(destination)) { return false; } uint8_t logLevelSettings = 0; - switch (destination) { - case LOG_TO_SERIAL: { + + switch (destination) + { + case LOG_TO_SERIAL: +#if USES_ESPEASY_CONSOLE_FALLBACK_PORT + case LOG_TO_SERIAL_EXTRA: +#endif + { logLevelSettings = getSerialLogLevel(); break; } - case LOG_TO_SYSLOG: { + case LOG_TO_SYSLOG: + { + if (logLevel == LOG_LEVEL_NONE) { return false; } logLevelSettings = Settings.SyslogLevel; break; } - case LOG_TO_WEBLOG: { + case LOG_TO_WEBLOG: + { logLevelSettings = getWebLogLevel(); + + if (logLevel == LOG_LEVEL_NONE) { return logLevelSettings != LOG_LEVEL_NONE; } break; } - case LOG_TO_SDCARD: { - #if FEATURE_SD +#if FEATURE_SD + case LOG_TO_SDCARD: + { + if (logLevel == LOG_LEVEL_NONE) { return false; } logLevelSettings = Settings.SDLogLevel; - #endif break; } +#endif // if FEATURE_SD default: return false; } @@ -199,166 +207,62 @@ bool loglevelActiveFor(uint8_t destination, uint8_t logLevel) { } void addLog(uint8_t logLevel, const __FlashStringHelper *str) -{ - if (loglevelActiveFor(logLevel)) { - String copy; - if (!reserve_special(copy, strlen_P((PGM_P)str))) { - return; - } - copy = str; - addToLogMove(logLevel, std::move(copy)); - } -} - -void addLog(uint8_t logLevel, const char *line) -{ - // Please note all functions called from here handling line must be PROGMEM aware. - if (loglevelActiveFor(logLevel)) { - - String copy; - #ifdef USE_SECOND_HEAP - { - // Allow to store the logs in 2nd heap if present. - if (mmu_is_iram(line)) { - size_t length = 0; - const char* cur_char = line; - bool copying = false; - bool done = false; - while (!done) { - const uint8_t ch = mmu_get_uint8(cur_char++); - if (ch == 0) { - if (copying) { - done = true; - } else { - if (!reserve_special(copy, length)) { - return; - } - copying = true; - cur_char = line; - } - } else { - if (copying) { - copy += (char)ch; - } else { - ++length; - } - } - } - } else { - if (!reserve_special(copy, strlen_P((PGM_P)line))) { - return; - } - copy = line; - } - } - #else - if (!reserve_special(copy, strlen_P((PGM_P)line))) { - return; - } - copy = line; - #endif - addToLogMove(logLevel, std::move(copy)); - } -} - -void addLog(uint8_t logLevel, String&& string) -{ - addToLogMove(logLevel, std::move(string)); -} - - -#ifndef LIMIT_BUILD_SIZE -#include "../Helpers/Memory.h" -#endif - -void addToSerialLog(uint8_t logLevel, const String& string) -{ - if (loglevelActiveFor(LOG_TO_SERIAL, logLevel)) { - ESPEasy_Console.addToSerialBuffer(format_msec_duration(millis())); - #ifndef LIMIT_BUILD_SIZE - ESPEasy_Console.addToSerialBuffer(strformat(F(" : (%d) "), FreeMem())); - #endif - { - String loglevelDisplayString = getLogLevelDisplayString(logLevel); - while (loglevelDisplayString.length() < 6) { - loglevelDisplayString += ' '; - } - ESPEasy_Console.addToSerialBuffer(loglevelDisplayString); - } - ESPEasy_Console.addToSerialBuffer(F(" : ")); - ESPEasy_Console.addToSerialBuffer(string); - ESPEasy_Console.addNewlineToSerialBuffer(); - } -} - -void addToSysLog(uint8_t logLevel, const String& string) -{ - if (loglevelActiveFor(LOG_TO_SYSLOG, logLevel)) { - sendSyslog(logLevel, string); - } -} - -void addToSDLog(uint8_t logLevel, const String& string) -{ -#if FEATURE_SD - if (!string.isEmpty() && loglevelActiveFor(LOG_TO_SDCARD, logLevel)) { - String logName = patch_fname(F("log.txt")); - fs::File logFile = SD.open(logName, "a+"); - if (logFile) { - const size_t stringLength = string.length(); - for (size_t i = 0; i < stringLength; ++i) { - logFile.print(string[i]); - } - logFile.println(); - } - logFile.close(); - } -#endif -} - - -void addLog(uint8_t logLevel, const String& string) { #ifdef ESP32 + if (xPortInIsrContext()) { // When called from an ISR, you should not send out logs. // Allocating memory from within an ISR is a big no-no. - // Also long-time blocking like sending logs (especially to a syslog server) + // Also long-time blocking like sending logs (especially to a syslog server) // is also really not a good idea from an ISR call. return; } - #endif - - if (string.isEmpty()) return; - addToSerialLog(logLevel, string); - addToSysLog(logLevel, string); - addToSDLog(logLevel, string); - if (loglevelActiveFor(LOG_TO_WEBLOG, logLevel)) { - Logging.add(logLevel, string); - } + #endif // ifdef ESP32 + Logging.addLogEntry(LogEntry_t(logLevel, str)); } -void addToLogMove(uint8_t logLevel, String&& string) -{ +void addLog(uint8_t logLevel, const char *line) { #ifdef ESP32 + if (xPortInIsrContext()) { // When called from an ISR, you should not send out logs. // Allocating memory from within an ISR is a big no-no. - // Also long-time blocking like sending logs (especially to a syslog server) + // Also long-time blocking like sending logs (especially to a syslog server) // is also really not a good idea from an ISR call. return; } - #endif - - if (string.isEmpty()) return; - String tmp; - move_special(tmp, std::move(string)); - addToSerialLog(logLevel, tmp); - addToSysLog(logLevel, tmp); - addToSDLog(logLevel, tmp); - - // May clear the string, so call as last one. - if (loglevelActiveFor(LOG_TO_WEBLOG, logLevel)) { - Logging.add(logLevel, std::move(tmp)); + #endif // ifdef ESP32 + Logging.addLogEntry(LogEntry_t(logLevel, line)); +} + +void addLog(uint8_t logLevel, String&& str) { addToLogMove(logLevel, std::move(str)); } + +void addLog(uint8_t logLevel, const String& str) +{ + #ifdef ESP32 + + if (xPortInIsrContext()) { + // When called from an ISR, you should not send out logs. + // Allocating memory from within an ISR is a big no-no. + // Also long-time blocking like sending logs (especially to a syslog server) + // is also really not a good idea from an ISR call. + return; + } + #endif // ifdef ESP32 + Logging.addLogEntry(LogEntry_t(logLevel, str)); +} + +void addToLogMove(uint8_t logLevel, String&& str) +{ + #ifdef ESP32 + + if (xPortInIsrContext()) { + // When called from an ISR, you should not send out logs. + // Allocating memory from within an ISR is a big no-no. + // Also long-time blocking like sending logs (especially to a syslog server) + // is also really not a good idea from an ISR call. + return; } + #endif // ifdef ESP32 + Logging.addLogEntry(LogEntry_t(logLevel, std::move(str))); } diff --git a/src/src/ESPEasyCore/ESPEasy_Log.h b/src/src/ESPEasyCore/ESPEasy_Log.h index df5c25cb2..8d2377236 100644 --- a/src/src/ESPEasyCore/ESPEasy_Log.h +++ b/src/src/ESPEasyCore/ESPEasy_Log.h @@ -1,60 +1,49 @@ -#ifndef ESPEASYCORE_ESPEASY_LOG_H -#define ESPEASYCORE_ESPEASY_LOG_H +#pragma once #include "../../ESPEasy_common.h" +#include "../DataTypes/LogLevels.h" -#define LOG_LEVEL_NONE 0 -#define LOG_LEVEL_ERROR 1 -#define LOG_LEVEL_INFO 2 -# ifndef BUILD_NO_DEBUG -#define LOG_LEVEL_DEBUG 3 -#define LOG_LEVEL_DEBUG_MORE 4 -#define LOG_LEVEL_DEBUG_DEV 9 // use for testing/debugging only, not for regular use -#define LOG_LEVEL_NRELEMENTS 5 // Update this and getLogLevelDisplayString() when new log levels are added -#else -#define LOG_LEVEL_NRELEMENTS 2 // Update this and getLogLevelDisplayString() when new log levels are added -#endif +#include "../DataStructs/LogEntry.h" -#define LOG_TO_SERIAL 1 -#define LOG_TO_SYSLOG 2 -#define LOG_TO_WEBLOG 3 -#define LOG_TO_SDCARD 4 // Move the log String so it does not have to be copied in the web log -#define addLogMove(L, S) addToLogMove(L, std::move(S)) +#define addLogMove(L, S) addToLogMove(L, std::move(S)) + +enum LogDestination : uint8_t; /********************************************************************************************\ - Logging - \*********************************************************************************************/ -void initLog(); + Logging + \*********************************************************************************************/ +void initLog(); -const __FlashStringHelper * getLogLevelDisplayString(int logLevel); -const __FlashStringHelper * getLogLevelDisplayStringFromIndex(uint8_t index, int& logLevel); +void disableSerialLog(); -void disableSerialLog(); +void setLogLevelFor(LogDestination destination, + uint8_t logLevel); -void setLogLevelFor(uint8_t destination, uint8_t logLevel); +void updateLogLevelCache(); -void updateLogLevelCache(); - -bool loglevelActiveFor(uint8_t logLevel); +bool loglevelActiveFor(uint8_t logLevel); uint8_t getSerialLogLevel(); uint8_t getWebLogLevel(); -bool loglevelActiveFor(uint8_t destination, uint8_t logLevel); +bool loglevelActiveFor(LogDestination destination, + uint8_t logLevel); +void addLog(uint8_t logLevel, + const __FlashStringHelper *str); +void addLog(uint8_t logLevel, + const char *line); +void addLog(uint8_t logLevel, + String&& str); -void addLog(uint8_t logLevel, const __FlashStringHelper *str); -void addLog(uint8_t logLevel, const char *line); -void addLog(uint8_t logLevel, String&& string); +void addLog(uint8_t logLevel, + const String& str); -void addLog(uint8_t logLevel, const String& string); -void addToLogMove(uint8_t logLevel, String&& string); - - -#endif \ No newline at end of file +void addToLogMove(uint8_t logLevel, + String&& str); diff --git a/src/src/ESPEasyCore/ESPEasy_backgroundtasks.cpp b/src/src/ESPEasyCore/ESPEasy_backgroundtasks.cpp index db80122b2..41ebfdea4 100644 --- a/src/src/ESPEasyCore/ESPEasy_backgroundtasks.cpp +++ b/src/src/ESPEasyCore/ESPEasy_backgroundtasks.cpp @@ -4,16 +4,17 @@ #include "../../ESPEasy-Globals.h" #include "../DataStructs/TimingStats.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../../ESPEasy/net/ESPEasyNetwork.h" #include "../ESPEasyCore/Serial.h" -#include "../Globals/NetworkState.h" +#include "../../ESPEasy/net/Globals/NetworkState.h" +#include "../Globals/Logging.h" #include "../Globals/Services.h" #include "../Globals/Settings.h" #if FEATURE_RTTTL && FEATURE_ANYRTTTL_LIB && FEATURE_ANYRTTTL_ASYNC #include "../Helpers/Audio.h" #endif // if FEATURE_RTTTL && FEATURE_ANYRTTTL_LIB && FEATURE_ANYRTTTL_ASYNC #include "../Helpers/ESPEasy_time_calc.h" -#include "../Helpers/Network.h" +#include "../Helpers/NetworkStatusLED.h" #include "../Helpers/Networking.h" @@ -57,9 +58,9 @@ void backgroundtasks() START_TIMER #if FEATURE_MDNS || FEATURE_ESPEASY_P2P - const bool networkConnected = NetworkConnected(); + const bool networkConnected = ESPEasy::net::NetworkConnected(); #else - NetworkConnected(); + ESPEasy::net::NetworkConnected(); #endif runningBackgroundTasks = true; @@ -74,6 +75,10 @@ void backgroundtasks() */ process_serialWriteBuffer(); + #if FEATURE_SYSLOG + syslogWriter.process(); + #endif + Logging.loop(); if (!UseRTOSMultitasking) { serial(); diff --git a/src/src/ESPEasyCore/ESPEasy_loop.cpp b/src/src/ESPEasyCore/ESPEasy_loop.cpp index 1cc0059f6..81a88fad9 100644 --- a/src/src/ESPEasyCore/ESPEasy_loop.cpp +++ b/src/src/ESPEasyCore/ESPEasy_loop.cpp @@ -2,12 +2,14 @@ #include "../../ESPEasy-Globals.h" +#include "../../ESPEasy/net/ESPEasyNetwork.h" +#include "../../ESPEasy/net/Globals/NWPlugins.h" +#include "../../ESPEasy/net/wifi/ESPEasyWifi.h" #include "../Commands/ExecuteCommand.h" +#include "../Commands/InternalCommands_decoder.h" #include "../DataStructs/TimingStats.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyWifi_ProcessEvent.h" -#include "../ESPEasyCore/ESPEasy_backgroundtasks.h" #include "../ESPEasyCore/ESPEasy_Log.h" +#include "../ESPEasyCore/ESPEasy_backgroundtasks.h" #include "../Globals/ESPEasy_Scheduler.h" #include "../Globals/EventQueue.h" #include "../Globals/RTC.h" @@ -21,9 +23,7 @@ #include "../Helpers/Networking.h" #include "../Helpers/PeriodicalActions.h" #include "../Helpers/StringConverter.h" - - -#include "../Commands/InternalCommands_decoder.h" +#include "../WebServer/ESPEasy_WebServer.h" void updateLoopStats() { ++loopCounter; @@ -72,12 +72,14 @@ void ESPEasy_loop() updateLoopStats(); - handle_unprocessedNetworkEvents(); +// ESPEasy::net::wifi::loopWiFi(); - bool firstLoopConnectionsEstablished = NetworkConnected() && firstLoop; +// ESPEasy::net::wifi::handle_unprocessedNetworkEvents(); + + bool firstLoopConnectionsEstablished = firstLoop && ESPEasy::net::NetworkConnected(); if (firstLoopConnectionsEstablished) { - #ifndef BUILD_MINIMAL_OTA + #ifndef LIMIT_BUILD_SIZE addLog(LOG_LEVEL_INFO, F("firstLoopConnectionsEstablished")); #endif firstLoop = false; @@ -102,6 +104,10 @@ void ESPEasy_loop() sendSysInfoUDP(1); #endif } + + setWebserverRunning(ESPEasy::net::NWPluginCall(NWPlugin::Function::NWPLUGIN_WEBSERVER_SHOULD_RUN)); + // ESPEasy::net::processNetworkEvents(); + #if FEATURE_CLEAR_I2C_STUCK if (Settings.EnableClearHangingI2Cbus()) { diff --git a/src/src/ESPEasyCore/ESPEasy_setup.cpp b/src/src/ESPEasyCore/ESPEasy_setup.cpp index 23546c739..5fdbec68f 100644 --- a/src/src/ESPEasyCore/ESPEasy_setup.cpp +++ b/src/src/ESPEasyCore/ESPEasy_setup.cpp @@ -7,20 +7,21 @@ #include "../Commands/InternalCommands_decoder.h" #include "../CustomBuild/CompiletimeDefines.h" #include "../ESPEasyCore/ESPEasyGPIO.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../../ESPEasy/net/ESPEasyNetwork.h" #include "../ESPEasyCore/ESPEasyRules.h" -#include "../ESPEasyCore/ESPEasyWifi.h" -#include "../ESPEasyCore/ESPEasyWifi_ProcessEvent.h" +#include "../../ESPEasy/net/wifi/ESPEasyWifi.h" + #include "../ESPEasyCore/Serial.h" #include "../Globals/Cache.h" #include "../Globals/ESPEasy_Console.h" -#include "../Globals/ESPEasyWiFiEvent.h" +#include "../../ESPEasy/net/Globals/ESPEasyWiFiEvent.h" #include "../Globals/ESPEasy_time.h" -#include "../Globals/NetworkState.h" +#include "../../ESPEasy/net/Globals/NetworkState.h" #include "../Globals/RTC.h" #include "../Globals/Statistics.h" -#include "../Globals/WiFi_AP_Candidates.h" +#include "../../ESPEasy/net/Globals/WiFi_AP_Candidates.h" #include "../Helpers/_CPlugin_init.h" +#include "../../ESPEasy/net/Helpers/_NWPlugin_init.h" #include "../Helpers/_NPlugin_init.h" #include "../Helpers/_Plugin_init.h" #include "../Helpers/DeepSleep.h" @@ -128,6 +129,10 @@ void sw_watchdog_callback(void *arg) \*********************************************************************************************/ void ESPEasy_setup() { +# ifdef BOARD_HAS_PSRAM + psramInit(); +# endif // ifdef BOARD_HAS_PSRAM + #if defined(ESP8266_DISABLE_EXTRA4K) || defined(USE_SECOND_HEAP) // disable_extra4k_at_link_time(); @@ -143,9 +148,6 @@ void ESPEasy_setup() DisableBrownout(); // Workaround possible weak LDO resulting in brownout detection during Wifi connection # endif // DISABLE_ESP32_BROWNOUT -# ifdef BOARD_HAS_PSRAM - psramInit(); -# endif // ifdef BOARD_HAS_PSRAM # if CONFIG_IDF_TARGET_ESP32 @@ -232,13 +234,14 @@ void ESPEasy_setup() PluginSetup(); CPluginSetup(); + ESPEasy::net::NWPluginSetup(); - initWiFi(); - WiFiEventData.clearAll(); +// ESPEasy::net::wifi::initWiFi(); +// WiFiEventData.clearAll(); -#ifndef BUILD_MINIMAL_OTA +#ifndef LIMIT_BUILD_SIZE run_compiletime_checks(); -#endif // ifndef BUILD_MINIMAL_OTA +#endif #ifdef ESP8266 // ets_isr_attach(8, sw_watchdog_callback, nullptr); // Set a callback for feeding the watchdog. @@ -366,12 +369,12 @@ void ESPEasy_setup() #ifndef BUILD_NO_RAM_TRACKER logMemUsageAfter(F("LoadSettings()")); #endif // ifndef BUILD_NO_RAM_TRACKER - +#ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, concat(F("CPU Frequency: "), ESP.getCpuFreqMHz())); - +#endif #ifdef ESP32 -#ifndef CORE32SOLO1 +#if !defined(CORE32SOLO1) && !defined(ESP32P4) // Configure dynamic frequency scaling: // maximum and minimum frequencies are set in sdkconfig, @@ -415,6 +418,9 @@ void ESPEasy_setup() toDisable = disableNotification(toDisable); } #endif // if FEATURE_NOTIFIER + if (toDisable != 0) { + toDisable = disableNetwork(toDisable); + } if (toDisable != 0) { toDisable = disableRules(toDisable); @@ -433,6 +439,9 @@ void ESPEasy_setup() toDisable = disableAllNotifications(toDisable); } #endif // if FEATURE_NOTIFIER + if (toDisable != 0) { + toDisable = disableAllNetworkss(toDisable); + } } #if FEATURE_ETHERNET @@ -442,63 +451,19 @@ void ESPEasy_setup() active_network_medium = Settings.NetworkMedium; #else // if FEATURE_ETHERNET - if (Settings.NetworkMedium == NetworkMedium_t::Ethernet) { - Settings.NetworkMedium = NetworkMedium_t::WIFI; + if (Settings.NetworkMedium == ESPEasy::net::NetworkMedium_t::Ethernet) { + Settings.NetworkMedium = ESPEasy::net::NetworkMedium_t::WIFI; } #endif // if FEATURE_ETHERNET - setNetworkMedium(Settings.NetworkMedium); - - bool initWiFi = active_network_medium == NetworkMedium_t::WIFI; - - // FIXME TD-er: Must add another check for 'delayed start WiFi' for poorly designed ESP8266 nodes. - - - if (initWiFi) { -#ifdef ESP32 - // FIXME TD-er: Disabled for now, as this may not return and thus block the ESP forever. - // See: https://github.com/espressif/esp-idf/issues/15862 - //check_and_update_WiFi_Calibration(); -#endif - - WiFi_AP_Candidates.clearCache(); - WiFi_AP_Candidates.load_knownCredentials(); - setSTA(true); - - if (!WiFi_AP_Candidates.hasCandidates()) { - WiFiEventData.wifiSetup = true; - RTC.clearLastWiFi(); // Must scan all channels - // Wait until scan has finished to make sure as many as possible are found - // We're still in the setup phase, so nothing else is taking resources of the ESP. - WifiScan(false); - WiFiEventData.lastScanMoment.clear(); - } - - // Always perform WiFi scan - // It appears reconnecting from RTC may take just as long to be able to send first packet as performing a scan first and then connect. - // Perhaps the WiFi radio needs some time to stabilize first? - if (!WiFi_AP_Candidates.hasCandidates()) { - WifiScan(false, RTC.lastWiFiChannel); - } - WiFi_AP_Candidates.clearCache(); - processScanDone(); - WiFi_AP_Candidates.load_knownCredentials(); - - if (!WiFi_AP_Candidates.hasCandidates()) { - #ifndef BUILD_MINIMAL_OTA - addLog(LOG_LEVEL_INFO, F("Setup: Scan all channels")); - #endif - WifiScan(false); - } - - // setWifiMode(WIFI_OFF); - } + // FIXME TD-er: This network medium setting may be obsolete as we need a priority scale/order +// ESPEasy::net::setNetworkMedium(Settings.NetworkMedium); #ifndef BUILD_NO_RAM_TRACKER logMemUsageAfter(F("WifiScan()")); #endif // ifndef BUILD_NO_RAM_TRACKER - // setWifiMode(WIFI_STA); + // ESPEasy::net::wifi::setWifiMode(WIFI_STA); checkRuleSets(); #ifndef BUILD_NO_RAM_TRACKER logMemUsageAfter(F("checkRuleSets()")); @@ -523,11 +488,11 @@ void ESPEasy_setup() #ifndef BUILD_NO_RAM_TRACKER logMemUsageAfter(F("initSerial()")); #endif // ifndef BUILD_NO_RAM_TRACKER - +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove(LOG_LEVEL_INFO, concat(F("INIT : Free RAM:"), FreeMem())); } - +#endif #ifndef BUILD_NO_DEBUG if (Settings.UseSerial && (Settings.SerialLogLevel >= LOG_LEVEL_DEBUG_MORE)) { @@ -541,6 +506,10 @@ void ESPEasy_setup() #ifndef BUILD_NO_RAM_TRACKER logMemUsageAfter(F("CPluginInit()")); #endif // ifndef BUILD_NO_RAM_TRACKER + ESPEasy::net::NWPluginInit(); + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("NWPluginInit()")); + #endif // ifndef BUILD_NO_RAM_TRACKER #if FEATURE_NOTIFIER NPluginInit(); # ifndef BUILD_NO_RAM_TRACKER @@ -555,7 +524,7 @@ void ESPEasy_setup() #ifndef BUILD_NO_RAM_TRACKER logMemUsageAfter(F("PluginInit()")); #endif - +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { String log; if (reserve_special(log, 80)) { @@ -568,7 +537,7 @@ void ESPEasy_setup() addLogMove(LOG_LEVEL_INFO, log); } } - +#endif /* if ((getDeviceCount() + 1) >= PLUGIN_MAX) { addLog(LOG_LEVEL_ERROR, concat(F("Programming error! - Increase PLUGIN_MAX ("), getDeviceCount()) + ')'); @@ -621,25 +590,6 @@ void ESPEasy_setup() } #endif // ifdef ESP32 - #if FEATURE_ETHERNET - - if (Settings.ETH_Pin_power_rst != -1) { - GPIO_Write(PLUGIN_GPIO, Settings.ETH_Pin_power_rst, 1); - } - - #endif // if FEATURE_ETHERNET - - NetworkConnectRelaxed(); - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("NetworkConnectRelaxed()")); - #endif // ifndef BUILD_NO_RAM_TRACKER - - setWebserverRunning(true); - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("setWebserverRunning()")); - #endif // ifndef BUILD_NO_RAM_TRACKER - - #if FEATURE_REPORTING ReportStatus(); #endif // if FEATURE_REPORTING diff --git a/src/src/ESPEasyCore/Serial.cpp b/src/src/ESPEasyCore/Serial.cpp index 522a996a8..4380182d6 100644 --- a/src/src/ESPEasyCore/Serial.cpp +++ b/src/src/ESPEasyCore/Serial.cpp @@ -5,7 +5,7 @@ #include "../Globals/Cache.h" #include "../Globals/ESPEasy_Console.h" -//#include "../Globals/Logging.h" // For serialWriteBuffer +#include "../Globals/Logging.h" #include "../Globals/Settings.h" #include "../Helpers/ESPEasy_time_calc.h" @@ -31,26 +31,23 @@ bool process_serialWriteBuffer() // For now, only send it to the serial buffer and try to process it. // Later we may want to wrap it into a log. void serialPrint(const __FlashStringHelper *text) { - ESPEasy_Console.addToSerialBuffer(text); + Logging.consolePrint(text); } void serialPrint(const String& text) { - ESPEasy_Console.addToSerialBuffer(text); + Logging.consolePrint(text); } void serialPrintln(const __FlashStringHelper *text) { - ESPEasy_Console.addToSerialBuffer(text); - serialPrintln(); + Logging.consolePrintln(text); } void serialPrintln(const String& text) { - ESPEasy_Console.addToSerialBuffer(text); - serialPrintln(); + Logging.consolePrintln(text); } void serialPrintln() { - ESPEasy_Console.addNewlineToSerialBuffer(); - ESPEasy_Console.process_serialWriteBuffer(); + Logging.consolePrintln(); } // Do not add helper functions for other types, since those types can only be diff --git a/src/src/Globals/CPlugins.cpp b/src/src/Globals/CPlugins.cpp index 7ec9a0ac7..a9888a82c 100644 --- a/src/src/Globals/CPlugins.cpp +++ b/src/src/Globals/CPlugins.cpp @@ -43,8 +43,13 @@ bool CPluginCall(CPlugin::Function Function, struct EventStruct *event, String& // only called from CPluginSetup() directly using protocolIndex break; + case CPlugin::Function::CPLUGIN_GET_PROTOCOL_DISPLAY_NAME: + // Only called from _CPlugin_Helper_webform getControllerParameterName + break; + // calls to all active controllers case CPlugin::Function::CPLUGIN_INIT_ALL: + case CPlugin::Function::CPLUGIN_EXIT_ALL: case CPlugin::Function::CPLUGIN_UDP_IN: case CPlugin::Function::CPLUGIN_INTERVAL: // calls to send stats information case CPlugin::Function::CPLUGIN_GOT_CONNECTED: // calls to send autodetect information @@ -59,9 +64,16 @@ bool CPluginCall(CPlugin::Function Function, struct EventStruct *event, String& if (Function == CPlugin::Function::CPLUGIN_INIT_ALL) { Function = CPlugin::Function::CPLUGIN_INIT; } + if (Function == CPlugin::Function::CPLUGIN_EXIT_ALL) { + Function = CPlugin::Function::CPLUGIN_EXIT; + } for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) { - if ((Settings.Protocol[x] != 0) && Settings.ControllerEnabled[x]) { + const bool checkedEnabled = + Settings.ControllerEnabled[x] || + Function == CPlugin::Function::CPLUGIN_EXIT; + + if ((Settings.Protocol[x] != 0) && checkedEnabled) { event->ControllerIndex = x; String command; @@ -69,7 +81,7 @@ bool CPluginCall(CPlugin::Function Function, struct EventStruct *event, String& command = str; } - if (CPluginCall( + if (do_CPluginCall( getProtocolIndex_from_ControllerIndex(x), Function, event, @@ -84,29 +96,37 @@ bool CPluginCall(CPlugin::Function Function, struct EventStruct *event, String& return success; } - // calls to specific controller + // calls to specific controller which need to be enabled before calling case CPlugin::Function::CPLUGIN_INIT: - case CPlugin::Function::CPLUGIN_EXIT: - case CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE: case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: case CPlugin::Function::CPLUGIN_PROTOCOL_RECV: + case CPlugin::Function::CPLUGIN_TASK_CHANGE_NOTIFICATION: + if (!validControllerIndex(event->ControllerIndex) + || !Settings.ControllerEnabled[event->ControllerIndex]) + { + return false; + } + // fall through + + + // calls to specific controller which might not be enabled when calling this function + case CPlugin::Function::CPLUGIN_EXIT: case CPlugin::Function::CPLUGIN_GET_DEVICENAME: + case CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE: case CPlugin::Function::CPLUGIN_WEBFORM_LOAD: case CPlugin::Function::CPLUGIN_WEBFORM_SAVE: - case CPlugin::Function::CPLUGIN_GET_PROTOCOL_DISPLAY_NAME: - case CPlugin::Function::CPLUGIN_TASK_CHANGE_NOTIFICATION: case CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG: { const controllerIndex_t controllerindex = event->ControllerIndex; bool success = false; if (validControllerIndex(controllerindex)) { - if (Settings.ControllerEnabled[controllerindex] && supportedCPluginID(Settings.Protocol[controllerindex])) + if (supportedCPluginID(Settings.Protocol[controllerindex])) { if (Function == CPlugin::Function::CPLUGIN_PROTOCOL_SEND) { checkDeviceVTypeForTask(event); } - success = CPluginCall( + success = do_CPluginCall( getProtocolIndex_from_ControllerIndex(controllerindex), Function, event, @@ -114,13 +134,13 @@ bool CPluginCall(CPlugin::Function Function, struct EventStruct *event, String& } #ifdef ESP32 - if (Function == CPlugin::Function::CPLUGIN_EXIT) { + if (success && Function == CPlugin::Function::CPLUGIN_EXIT) { Cache.clearControllerSettings(controllerindex); } #endif // ifdef ESP32 #if FEATURE_MQTT_DISCOVER - if (Function == CPlugin::Function::CPLUGIN_EXIT) { + if (success && Function == CPlugin::Function::CPLUGIN_EXIT) { if (mqttDiscoveryController == controllerindex) { mqttDiscoveryController = INVALID_CONTROLLER_INDEX; // Reset mqttDiscoverOnlyTask = INVALID_TASK_INDEX; @@ -135,7 +155,7 @@ bool CPluginCall(CPlugin::Function Function, struct EventStruct *event, String& for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) { if (Settings.ControllerEnabled[x] && supportedCPluginID(Settings.Protocol[x])) { - CPluginCall( + do_CPluginCall( getProtocolIndex_from_ControllerIndex(x), Function, event, @@ -175,7 +195,7 @@ controllerIndex_t findFirstEnabledControllerWithId(cpluginID_t cpluginid) { bool validProtocolIndex(protocolIndex_t index) { - return validProtocolIndex_init(index); + return do_check_validProtocolIndex(index); } /* @@ -186,27 +206,27 @@ bool validProtocolIndex(protocolIndex_t index) */ bool validCPluginID(cpluginID_t cpluginID) { - return getProtocolIndex_from_CPluginID_(cpluginID) != INVALID_PROTOCOL_INDEX; + return do_getProtocolIndex_from_CPluginID(cpluginID) != INVALID_PROTOCOL_INDEX; } bool supportedCPluginID(cpluginID_t cpluginID) { - return validProtocolIndex(getProtocolIndex_from_CPluginID_(cpluginID)); + return validProtocolIndex(do_getProtocolIndex_from_CPluginID(cpluginID)); } protocolIndex_t getProtocolIndex_from_ControllerIndex(controllerIndex_t index) { if (validControllerIndex(index)) { - return getProtocolIndex_from_CPluginID_(Settings.Protocol[index]); + return do_getProtocolIndex_from_CPluginID(Settings.Protocol[index]); } return INVALID_PROTOCOL_INDEX; } protocolIndex_t getProtocolIndex_from_CPluginID(cpluginID_t cpluginID) { - return getProtocolIndex_from_CPluginID_(cpluginID); + return do_getProtocolIndex_from_CPluginID(cpluginID); } cpluginID_t getCPluginID_from_ProtocolIndex(protocolIndex_t index) { - return getCPluginID_from_ProtocolIndex_(index); + return do_getCPluginID_from_ProtocolIndex(index); } cpluginID_t getCPluginID_from_ControllerIndex(controllerIndex_t index) { @@ -219,13 +239,13 @@ String getCPluginNameFromProtocolIndex(protocolIndex_t ProtocolIndex) { String controllerName; if (validProtocolIndex(ProtocolIndex)) { - CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_GET_DEVICENAME, nullptr, controllerName); + do_CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_GET_DEVICENAME, nullptr, controllerName); } return controllerName; } String getCPluginNameFromCPluginID(cpluginID_t cpluginID) { - protocolIndex_t protocolIndex = getProtocolIndex_from_CPluginID_(cpluginID); + protocolIndex_t protocolIndex = do_getProtocolIndex_from_CPluginID(cpluginID); if (!validProtocolIndex(protocolIndex)) { return strformat(F("CPlugin %d not included in build"), cpluginID); diff --git a/src/src/Globals/CPlugins.h b/src/src/Globals/CPlugins.h index 31e9ab3ae..ace77949b 100644 --- a/src/src/Globals/CPlugins.h +++ b/src/src/Globals/CPlugins.h @@ -36,10 +36,6 @@ bool CPluginCall(CPlugin::Function Function, bool CPluginCall(CPlugin::Function Function, struct EventStruct *event, String & str); -bool CPluginCall(protocolIndex_t protocolIndex, - CPlugin::Function Function, - struct EventStruct *event, - String & str); bool anyControllerEnabled(); controllerIndex_t findFirstEnabledControllerWithId(cpluginID_t cpluginid); diff --git a/src/src/Globals/ESPEasyEthEvent.cpp b/src/src/Globals/ESPEasyEthEvent.cpp deleted file mode 100644 index ae30f4310..000000000 --- a/src/src/Globals/ESPEasyEthEvent.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "../Globals/ESPEasyEthEvent.h" - -#include "../../ESPEasy_common.h" - -#if FEATURE_ETHERNET -EthernetEventData_t EthEventData; -#endif - diff --git a/src/src/Globals/ESPEasyEthEvent.h b/src/src/Globals/ESPEasyEthEvent.h deleted file mode 100644 index 53c837efe..000000000 --- a/src/src/Globals/ESPEasyEthEvent.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef GLOBALS_ESPEASYETHEVENT_H -#define GLOBALS_ESPEASYETHEVENT_H - - -#include "../../ESPEasy_common.h" -#if FEATURE_ETHERNET - -# include "../DataStructs/EthernetEventData.h" - - -extern EthernetEventData_t EthEventData; - -#endif // if FEATURE_ETHERNET - -#endif // ifndef GLOBALS_ESPEASYETHEVENT_H diff --git a/src/src/Globals/ESPEasyWiFi.cpp b/src/src/Globals/ESPEasyWiFi.cpp deleted file mode 100644 index b4ab40207..000000000 --- a/src/src/Globals/ESPEasyWiFi.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "../Globals/ESPEasyWiFi.h" - -#ifdef ESPEASY_WIFI_CLEANUP_WORK_IN_PROGRESS -ESPEasyWiFi_t ESPEasyWiFi; -#endif // ifdef ESPEASY_WIFI_CLEANUP_WORK_IN_PROGRESS diff --git a/src/src/Globals/ESPEasyWiFi.h b/src/src/Globals/ESPEasyWiFi.h deleted file mode 100644 index c5a156839..000000000 --- a/src/src/Globals/ESPEasyWiFi.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef GLOBALS_ESPEASYWIFI_H -#define GLOBALS_ESPEASYWIFI_H - -#include "../ESPEasyCore/ESPEasyWifi.h" - - -#include "../../ESPEasy_common.h" - -#ifdef ESPEASY_WIFI_CLEANUP_WORK_IN_PROGRESS - -extern ESPEasyWiFi_t ESPEasyWiFi; -#endif // ifdef ESPEASY_WIFI_CLEANUP_WORK_IN_PROGRESS - -#endif // ifndef GLOBALS_ESPEASYWIFI_H diff --git a/src/src/Globals/ESPEasyWiFiEvent.h b/src/src/Globals/ESPEasyWiFiEvent.h deleted file mode 100644 index 068e3f773..000000000 --- a/src/src/Globals/ESPEasyWiFiEvent.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef GLOBALS_ESPEASYWIFIEVENT_H -#define GLOBALS_ESPEASYWIFIEVENT_H - - -#include "../../ESPEasy_common.h" - -#include "../DataStructs/WiFiEventData.h" - - -#include -#include - - -#ifdef ESP32 -# include -# include -# include - -#endif // ifdef ESP32 - -#ifdef ESP8266 -# include -# include -class IPAddress; - -extern WiFiEventHandler stationConnectedHandler; -extern WiFiEventHandler stationDisconnectedHandler; -extern WiFiEventHandler stationGotIpHandler; -extern WiFiEventHandler stationModeDHCPTimeoutHandler; -extern WiFiEventHandler stationModeAuthModeChangeHandler; -extern WiFiEventHandler APModeStationConnectedHandler; -extern WiFiEventHandler APModeStationDisconnectedHandler; -#endif // ifdef ESP8266 - - -extern WiFiEventData_t WiFiEventData; - - -#endif // GLOBALS_ESPEASYWIFIEVENT_H diff --git a/src/src/Globals/Logging.cpp b/src/src/Globals/Logging.cpp index affbd1450..b9b2465e3 100644 --- a/src/src/Globals/Logging.cpp +++ b/src/src/Globals/Logging.cpp @@ -1,10 +1,10 @@ #include "../Globals/Logging.h" -#include "../DataStructs/LogStruct.h" - - -LogStruct Logging; +LogHelper Logging; +#if FEATURE_SYSLOG +SyslogWriter syslogWriter(LOG_TO_SYSLOG); +#endif uint8_t highest_active_log_level = 0; bool log_to_serial_disabled = false; diff --git a/src/src/Globals/Logging.h b/src/src/Globals/Logging.h index 88f029ffc..7207901b4 100644 --- a/src/src/Globals/Logging.h +++ b/src/src/Globals/Logging.h @@ -4,11 +4,19 @@ #include #include +#include "../Helpers/Log_Helper.h" +#if FEATURE_SYSLOG +#include "../Helpers/SyslogWriter.h" +#endif + + + extern uint8_t highest_active_log_level; extern bool log_to_serial_disabled; -struct LogStruct; -extern LogStruct Logging; - +extern LogHelper Logging; +#if FEATURE_SYSLOG +extern SyslogWriter syslogWriter; +#endif #endif // GLOBALS_LOGGING_H \ No newline at end of file diff --git a/src/src/Globals/MQTT.cpp b/src/src/Globals/MQTT.cpp index 04d64d09a..123a045ba 100644 --- a/src/src/Globals/MQTT.cpp +++ b/src/src/Globals/MQTT.cpp @@ -4,7 +4,6 @@ #if FEATURE_MQTT - // MQTT client WiFiClient mqtt; # if FEATURE_MQTT_TLS @@ -12,23 +11,29 @@ String mqtt_tls_last_errorstr; int32_t mqtt_tls_last_error = 0; # ifdef ESP32 -BearSSL::WiFiClientSecure_light*mqtt_tls; +BearSSL::WiFiClientSecure_light*mqtt_tls{}; # endif // ifdef ESP32 # ifdef ESP8266 BearSSL::WiFiClientSecure*mqtt_tls; BearSSL::X509List mqtt_X509List; # endif // ifdef ESP8266 +int32_t mqtt_tls_last_cipher_suite{}; String mqtt_rootCA; String mqtt_fingerprint; # endif // if FEATURE_MQTT_TLS PubSubClient MQTTclient(mqtt); +LongTermOnOffTimer MQTTclient_connected_stats; bool MQTTclient_should_reconnect = true; bool MQTTclient_must_send_LWT_connected = false; bool MQTTclient_connected = false; int mqtt_reconnect_count = 0; LongTermTimer MQTTclient_next_connect_attempt; +# if FEATURE_MQTT_CONNECT_BACKGROUND +MQTT_connect_request MQTT_task_data; +# endif // if FEATURE_MQTT_CONNECT_BACKGROUND + # if FEATURE_MQTT_DISCOVER controllerIndex_t mqttDiscoveryController = INVALID_CONTROLLER_INDEX; diff --git a/src/src/Globals/MQTT.h b/src/src/Globals/MQTT.h index 59bf490f4..da293c6d1 100644 --- a/src/src/Globals/MQTT.h +++ b/src/src/Globals/MQTT.h @@ -9,6 +9,7 @@ # include "../DataTypes/TaskIndex.h" # include "../Helpers/LongTermTimer.h" +# include "../Helpers/LongTermOnOffTimer.h" # include # include @@ -17,12 +18,38 @@ # include # endif // if FEATURE_MQTT_TLS +# if FEATURE_MQTT_CONNECT_BACKGROUND +enum MQTT_connect_status_e : uint8_t { + Disconnected = 0, + Connecting = 1, + Connected = 2, + Failure = 3, + Ready = 4, +}; + +struct MQTT_connect_request { + MQTT_connect_status_e status = MQTT_connect_status_e::Disconnected; + controllerIndex_t ControllerIndex = INVALID_CONTROLLER_INDEX; + uint32_t startTime{}; + uint32_t loopTime{}; + uint32_t endTime{}; + uint32_t timeout{}; + bool result{}; + bool logged{}; + + // This is C-code, so not set to nullptr, but to NULL + TaskHandle_t taskHandle = NULL; +}; +extern MQTT_connect_request MQTT_task_data; +# endif // if FEATURE_MQTT_CONNECT_BACKGROUND + // MQTT client extern WiFiClient mqtt; # if FEATURE_MQTT_TLS extern String mqtt_tls_last_errorstr; extern int32_t mqtt_tls_last_error; extern BearSSL::WiFiClientSecure_light*mqtt_tls; +extern int32_t mqtt_tls_last_cipher_suite; // extern BearSSL::X509List mqtt_X509List; @@ -31,6 +58,7 @@ extern String mqtt_fingerprint; # endif // if FEATURE_MQTT_TLS extern PubSubClient MQTTclient; +extern LongTermOnOffTimer MQTTclient_connected_stats; extern bool MQTTclient_should_reconnect; extern bool MQTTclient_must_send_LWT_connected; extern bool MQTTclient_connected; diff --git a/src/src/Globals/Plugins.cpp b/src/src/Globals/Plugins.cpp index d71dfaeee..0c8a1d835 100644 --- a/src/src/Globals/Plugins.cpp +++ b/src/src/Globals/Plugins.cpp @@ -18,7 +18,7 @@ #include "../Globals/ExtraTaskSettings.h" #include "../Globals/EventQueue.h" #include "../Globals/GlobalMapPortStatus.h" -#include "../Globals/NetworkState.h" +#include "../../ESPEasy/net/Globals/NetworkState.h" #include "../Globals/Settings.h" #include "../Globals/Statistics.h" @@ -39,7 +39,7 @@ bool validDeviceIndex(deviceIndex_t index) { - return validDeviceIndex_init(index); + return do_check_validDeviceIndex(index); } /* @@ -127,7 +127,7 @@ String getPluginNameFromDeviceIndex(deviceIndex_t deviceIndex) { String deviceName; if (validDeviceIndex(deviceIndex)) { - PluginCall(deviceIndex, PLUGIN_GET_DEVICENAME, nullptr, deviceName); + do_PluginCall(deviceIndex, PLUGIN_GET_DEVICENAME, nullptr, deviceName); } return deviceName; } @@ -149,7 +149,7 @@ bool checkPluginI2CAddressFromDeviceIndex(deviceIndex_t deviceIndex, uint8_t i2c String dummy; struct EventStruct TempEvent; TempEvent.Par1 = i2cAddress; - hasI2CAddress = PluginCall(deviceIndex, PLUGIN_I2C_HAS_ADDRESS, &TempEvent, dummy); + hasI2CAddress = do_PluginCall(deviceIndex, PLUGIN_I2C_HAS_ADDRESS, &TempEvent, dummy); } return hasI2CAddress; } @@ -168,7 +168,7 @@ bool getPluginDisplayParametersFromTaskIndex(taskIndex_t taskIndex, uint16_t& x, struct EventStruct TempEvent; TempEvent.setTaskIndex(taskIndex); - if (PluginCall(deviceIndex, PLUGIN_GET_DISPLAY_PARAMETERS, &TempEvent, dummy)) { + if (do_PluginCall(deviceIndex, PLUGIN_GET_DISPLAY_PARAMETERS, &TempEvent, dummy)) { x = TempEvent.Par1; y = TempEvent.Par2; r = TempEvent.Par3; @@ -191,7 +191,7 @@ uint8_t getTaskI2CAddress(taskIndex_t taskIndex) { TempEvent.setTaskIndex(taskIndex); TempEvent.Par1 = 0; - if (PluginCall(deviceIndex, PLUGIN_I2C_GET_ADDRESS, &TempEvent, dummy)) { + if (do_PluginCall(deviceIndex, PLUGIN_I2C_GET_ADDRESS, &TempEvent, dummy)) { getI2CAddress = TempEvent.Par1; } } @@ -218,11 +218,11 @@ bool prepare_I2C_by_taskIndex(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) if (!Settings.isI2CEnabled(Settings.getI2CInterface(taskIndex))) { return false; // Plugin-selected I2C bus is not configured, fail } - +#if FEATURE_CLEAR_I2C_STUCK if (I2C_state != I2C_bus_state::OK) { return false; // Bus state is not OK, so do not consider task runnable } - +#endif #if FEATURE_I2C_MULTIPLE const uint8_t i2cBus = Settings.getI2CInterface(taskIndex); #else // if FEATURE_I2C_MULTIPLE @@ -281,7 +281,7 @@ void queueTaskEvent(const String& eventName, taskIndex_t taskIndex, const String event += ','; event += wrapWithQuotesIfContainsParameterSeparatorChar(value_str); } - eventQueue.addMove(std::move(event)); + eventQueue.addMove(std::move(event), true); } } @@ -430,7 +430,7 @@ bool PluginCallForTask(taskIndex_t taskIndex, uint8_t Function, EventStruct *Tem } START_TIMER; - retval = (PluginCall(DeviceIndex, Function, TempEvent, command)); + retval = (do_PluginCall(DeviceIndex, Function, TempEvent, command)); STOP_TIMER_TASK(DeviceIndex, Function); @@ -546,7 +546,7 @@ bool PluginCall(uint8_t Function, struct EventStruct *event, String& str) I2CSelectHighClockSpeed(i2cBus); // Switch to requested bus, no need to switch back, // next I2C plugin call will switch to desired bus #endif // if FEATURE_I2C_MULTIPLE - PluginCall(DeviceIndex, Function, &TempEvent, str); + do_PluginCall(DeviceIndex, Function, &TempEvent, str); STOP_TIMER_TASK(DeviceIndex, Function); } } @@ -653,7 +653,7 @@ bool PluginCall(uint8_t Function, struct EventStruct *event, String& str) if (Function == PLUGIN_REQUEST) { // @FIXME TD-er: work-around as long as gpio command is still performed in P001_switch. for (deviceIndex_t deviceIndex = 0; validDeviceIndex(deviceIndex); deviceIndex++) { - if (PluginCall(deviceIndex, Function, event, str)) { + if (do_PluginCall(deviceIndex, Function, event, str)) { delay(0); // SMY: call delay(0) unconditionally CPluginCall(CPlugin::Function::CPLUGIN_ACKNOWLEDGE, event, str); return true; @@ -762,7 +762,7 @@ bool PluginCall(uint8_t Function, struct EventStruct *event, String& str) case PLUGIN_PRIORITY_INIT_ALL: { if (Function == PLUGIN_PRIORITY_INIT_ALL) { - addLogMove(LOG_LEVEL_INFO, F("INIT : Check for Priority tasks")); + addLog(LOG_LEVEL_INFO, F("INIT : Check for Priority tasks")); PluginInit(true); // Priority only, load plugins but don't initialize them yet Function = PLUGIN_PRIORITY_INIT; } @@ -871,7 +871,7 @@ bool PluginCall(uint8_t Function, struct EventStruct *event, String& str) // It may be better if ranges are set in the call for default values and error values set via PLUGIN_INIT. // Also these may be plugin specific so perhaps create a helper function to load/save these values and call these helpers from the // plugin code. - PluginCall(DeviceIndex, PLUGIN_INIT_VALUE_RANGES, event, str); // Initialize value range(s) + do_PluginCall(DeviceIndex, PLUGIN_INIT_VALUE_RANGES, event, str); // Initialize value range(s) } if ((Function == PLUGIN_INIT) @@ -895,7 +895,7 @@ bool PluginCall(uint8_t Function, struct EventStruct *event, String& str) } if (performPluginCall) { - retval = PluginCall(DeviceIndex, Function, event, str); + retval = do_PluginCall(DeviceIndex, Function, event, str); } else { retval = event->Source == EventValueSource::Enum::VALUE_SOURCE_UDP; } @@ -904,7 +904,7 @@ bool PluginCall(uint8_t Function, struct EventStruct *event, String& str) if (!retval) { String errorStr; - if (PluginCall(DeviceIndex, PLUGIN_READ_ERROR_OCCURED, event, errorStr)) + if (do_PluginCall(DeviceIndex, PLUGIN_READ_ERROR_OCCURED, event, errorStr)) { // Apparently the last read call resulted in an error // Send event indicating the error. @@ -1006,13 +1006,17 @@ bool PluginCall(uint8_t Function, struct EventStruct *event, String& str) #ifdef USES_ESPEASY_NOW case PLUGIN_FILTEROUT_CONTROLLER_DATA: #endif // ifdef USES_ESPEASY_NOW - #if FEATURE_MQTT_DISCOVER + #if FEATURE_MQTT_DISCOVER || FEATURE_CUSTOM_TASKVAR_VTYPE case PLUGIN_GET_DISCOVERY_VTYPES: - #endif // if FEATURE_MQTT_DISCOVER + #endif // if FEATURE_MQTT_DISCOVER || FEATURE_CUSTOM_TASKVAR_VTYPE + #if FEATURE_TASKVALUE_UNIT_OF_MEASURE + case PLUGIN_GET_UOM_GROUPS: + #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE - // PLUGIN_MQTT_xxx functions are directly called from the scheduler. - // case PLUGIN_MQTT_CONNECTION_STATE: - // case PLUGIN_MQTT_IMPORT: + #if FEATURE_MQTT + case PLUGIN_MQTT_CONNECTION_STATE: + case PLUGIN_MQTT_IMPORT: + #endif // if FEATURE_MQTT { START_TIMER; const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(event->TaskIndex); @@ -1026,9 +1030,12 @@ bool PluginCall(uint8_t Function, struct EventStruct *event, String& str) (Function == PLUGIN_INIT_VALUE_RANGES) || (Function == PLUGIN_WEBFORM_SHOW_SERIAL_PARAMS) || (Function == PLUGIN_WEBFORM_PRE_SERIAL_PARAMS) - #if FEATURE_MQTT_DISCOVER + #if FEATURE_MQTT_DISCOVER || FEATURE_CUSTOM_TASKVAR_VTYPE || (Function == PLUGIN_GET_DISCOVERY_VTYPES) - #endif // if FEATURE_MQTT_DISCOVER + #endif // if FEATURE_MQTT_DISCOVER || FEATURE_CUSTOM_TASKVAR_VTYPE + #if FEATURE_TASKVALUE_UNIT_OF_MEASURE + || (Function == PLUGIN_GET_UOM_GROUPS) + #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE ) { LoadTaskSettings(event->TaskIndex); } @@ -1044,7 +1051,7 @@ bool PluginCall(uint8_t Function, struct EventStruct *event, String& str) } } - bool retval = PluginCall(DeviceIndex, Function, event, str); + bool retval = do_PluginCall(DeviceIndex, Function, event, str); // Calls may have updated ExtraTaskSettings, so validate them. ExtraTaskSettings.validate(); @@ -1106,7 +1113,7 @@ bool PluginCall(uint8_t Function, struct EventStruct *event, String& str) if (Function == PLUGIN_GET_DEVICEVTYPE) { event->sensorType = Device[DeviceIndex].VType; } - bool retval = PluginCall(DeviceIndex, Function, event, str); + bool retval = do_PluginCall(DeviceIndex, Function, event, str); if (Function == PLUGIN_GET_DEVICEVALUECOUNT) { // Check if we have a valid value count. diff --git a/src/src/Globals/SecuritySettings.cpp b/src/src/Globals/SecuritySettings.cpp index 3fd353f65..10b9d430e 100644 --- a/src/src/Globals/SecuritySettings.cpp +++ b/src/src/Globals/SecuritySettings.cpp @@ -2,4 +2,8 @@ SecurityStruct SecuritySettings; -ExtendedControllerCredentialsStruct ExtendedControllerCredentials; \ No newline at end of file +ExtendedControllerCredentialsStruct ExtendedControllerCredentials; + +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE +SecurityStruct_deviceSpecific SecuritySettings_deviceSpecific; +#endif diff --git a/src/src/Globals/SecuritySettings.h b/src/src/Globals/SecuritySettings.h index 7ef8b18c4..e0255659f 100644 --- a/src/src/Globals/SecuritySettings.h +++ b/src/src/Globals/SecuritySettings.h @@ -3,9 +3,14 @@ #include "../DataStructs/SecurityStruct.h" #include "../DataStructs/ExtendedControllerCredentialsStruct.h" +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE +#include "../DataStructs/SecurityStruct_deviceSpecific.h" +#endif extern SecurityStruct SecuritySettings; extern ExtendedControllerCredentialsStruct ExtendedControllerCredentials; - +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE +extern SecurityStruct_deviceSpecific SecuritySettings_deviceSpecific; +#endif #endif // GLOBALS_SECURITY_SETTINGS_H \ No newline at end of file diff --git a/src/src/Globals/Statistics.cpp b/src/src/Globals/Statistics.cpp index 1dee64440..d2b73b1c5 100644 --- a/src/src/Globals/Statistics.cpp +++ b/src/src/Globals/Statistics.cpp @@ -11,24 +11,25 @@ String lowestFreeStackfunction; uint8_t lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; SchedulerTimerID lastMixedSchedulerId_beforereboot(0); -unsigned long idle_msec_per_sec = 0; -unsigned long elapsed10ps = 0; -unsigned long elapsed10psU = 0; -unsigned long elapsed50ps = 0; -unsigned long loopCounter = 0; -unsigned long loopCounterLast = 0; -unsigned long loopCounterMax = 1; -uint32_t lastLoopStart = 0; -unsigned long shortestLoop = 10000000; -unsigned long longestLoop = 0; -unsigned long loopCounter_full = 1; +uint32_t idle_msec_per_sec = 0; +uint32_t elapsed10ps = 0; +uint32_t elapsed10psU = 0; +uint32_t elapsed50ps = 0; +uint32_t loopCounter = 0; +uint32_t loopCounterLast = 0; +uint32_t loopCounterMax = 1; +uint32_t lastLoopStart = 0; +uint32_t shortestLoop = 10000000; +uint32_t longestLoop = 0; +uint32_t loopCounter_full = 1; float loop_usec_duration_total = 0.0f; -unsigned long dailyResetCounter = 0; - -ESPEASY_VOLATILE(unsigned long) sw_watchdog_callback_count{}; +uint32_t dailyResetCounter = 0; +ESPEASY_VOLATILE(uint32_t) sw_watchdog_callback_count{}; +#if FEATURE_CLEAR_I2C_STUCK I2C_bus_state I2C_state = I2C_bus_state::OK; -unsigned long I2C_bus_cleared_count = 0; +uint32_t I2C_bus_cleared_count = 0; +#endif \ No newline at end of file diff --git a/src/src/Globals/Statistics.h b/src/src/Globals/Statistics.h index 1cb45dabb..f7b846f86 100644 --- a/src/src/Globals/Statistics.h +++ b/src/src/Globals/Statistics.h @@ -32,21 +32,21 @@ extern String lowestFreeStackfunction; extern uint8_t lastBootCause; extern SchedulerTimerID lastMixedSchedulerId_beforereboot; -extern unsigned long loopCounter; -extern unsigned long loopCounterLast; -extern unsigned long loopCounterMax; +extern uint32_t loopCounter; +extern uint32_t loopCounterLast; +extern uint32_t loopCounterMax; extern uint32_t lastLoopStart; -extern unsigned long shortestLoop; -extern unsigned long longestLoop; -extern unsigned long loopCounter_full; +extern uint32_t shortestLoop; +extern uint32_t longestLoop; +extern uint32_t loopCounter_full; extern float loop_usec_duration_total; -extern unsigned long dailyResetCounter; -extern ESPEASY_VOLATILE(unsigned long) sw_watchdog_callback_count; - +extern uint32_t dailyResetCounter; +extern ESPEASY_VOLATILE(uint32_t) sw_watchdog_callback_count; +#if FEATURE_CLEAR_I2C_STUCK extern I2C_bus_state I2C_state; -extern unsigned long I2C_bus_cleared_count; - +extern uint32_t I2C_bus_cleared_count; +#endif #endif // GLOBALS_STATISTICS_H diff --git a/src/src/Globals/WiFi_AP_Candidates.cpp b/src/src/Globals/WiFi_AP_Candidates.cpp deleted file mode 100644 index 0c188328a..000000000 --- a/src/src/Globals/WiFi_AP_Candidates.cpp +++ /dev/null @@ -1,3 +0,0 @@ -#include "../Globals/WiFi_AP_Candidates.h" - -WiFi_AP_CandidatesList WiFi_AP_Candidates; diff --git a/src/src/Globals/WiFi_AP_Candidates.h b/src/src/Globals/WiFi_AP_Candidates.h deleted file mode 100644 index 8e8019c82..000000000 --- a/src/src/Globals/WiFi_AP_Candidates.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef GLOBALS_WIFI_AP_CANDIDATES_H -#define GLOBALS_WIFI_AP_CANDIDATES_H - -#include "../Helpers/WiFi_AP_CandidatesList.h" - - -extern WiFi_AP_CandidatesList WiFi_AP_Candidates; - - -#endif // ifndef GLOBALS_WIFI_AP_CANDIDATES_H diff --git a/src/src/Helpers/AdafruitGFX_helper.cpp b/src/src/Helpers/AdafruitGFX_helper.cpp index 90fa6f4de..ed616452e 100644 --- a/src/src/Helpers/AdafruitGFX_helper.cpp +++ b/src/src/Helpers/AdafruitGFX_helper.cpp @@ -1566,7 +1566,7 @@ bool AdafruitGFX_helper::processCommand(const String& string) { break; case adagfx_commands_e::tpm: // tpm: Text Print Mode - if ((argCount == 1) && ((nParams[0] < 0) || (nParams[0] >= static_cast(AdaGFXTextPrintMode::MAX)))) { + if ((argCount == 1) && !((nParams[0] < 0) || (nParams[0] >= static_cast(AdaGFXTextPrintMode::MAX)))) { _textPrintMode = static_cast(nParams[0]); _display->setTextWrap(_textPrintMode == AdaGFXTextPrintMode::ContinueToNextLine); success = true; @@ -2636,6 +2636,11 @@ void AdafruitGFX_helper::printText(const char *string, _display->getTextBounds(newString, _x, _y, &xText, &yText, &wText, &hText); // Calculate length + if ((hText < hChar1) && _columnRowMode) { // If the text-height is lower than the default height + yText -= (hChar1 - hText); // Subtract height-difference between A and current height + hText = hChar1; // Use default height + } + if ((maxWidth > 0) && ((_x - xOffset) + maxWidth <= res_x)) { res_x = (_x - xOffset) + maxWidth; _w = maxWidth; diff --git a/src/src/Helpers/BusCmd_Handler_I2C.cpp b/src/src/Helpers/BusCmd_Handler_I2C.cpp index 6493694d8..a6ae45bdc 100644 --- a/src/src/Helpers/BusCmd_Handler_I2C.cpp +++ b/src/src/Helpers/BusCmd_Handler_I2C.cpp @@ -1,5 +1,6 @@ #include "../Helpers/BusCmd_Handler_I2C.h" +#if FEATURE_BUS_COMMAND /** * Constructor BusCmd_Handler_I2C */ @@ -487,4 +488,5 @@ uint32_t BusCmd_Handler_I2C::writeStringReg(uint16_t reg, return _wire->endTransmission() == 0; } -#endif // if FEATURE_BUSCMD_STRING +#endif +#endif \ No newline at end of file diff --git a/src/src/Helpers/BusCmd_Handler_I2C.h b/src/src/Helpers/BusCmd_Handler_I2C.h index 9b957f156..d0867dd0e 100644 --- a/src/src/Helpers/BusCmd_Handler_I2C.h +++ b/src/src/Helpers/BusCmd_Handler_I2C.h @@ -7,6 +7,8 @@ */ #include "../../_Plugin_Helper.h" +#if FEATURE_BUS_COMMAND + #include "../Helpers/IBusCmd_Handler.h" #include @@ -89,4 +91,5 @@ private: TwoWire *_wire; bool _ok; }; +#endif #endif // ifndef _HELPERS_BUSCMD_HANDLER_I2C_H diff --git a/src/src/Helpers/BusCmd_Helper.cpp b/src/src/Helpers/BusCmd_Helper.cpp index 14827914b..01d8fe0e3 100644 --- a/src/src/Helpers/BusCmd_Helper.cpp +++ b/src/src/Helpers/BusCmd_Helper.cpp @@ -1,5 +1,5 @@ #include "../Helpers/BusCmd_Helper.h" - +#if FEATURE_BUS_COMMAND #include #include "../Globals/RulesCalculate.h" @@ -219,7 +219,8 @@ const __FlashStringHelper * BusCmd_Helper_struct::cacheSuffix(BusCmd_CommandSour switch (source) { case BusCmd_CommandSource_e::PluginIdle: case BusCmd_CommandSource_e::PluginGetConfigVar: - case BusCmd_CommandSource_e::PluginRead: return F(""); + case BusCmd_CommandSource_e::PluginRead: + case BusCmd_CommandSource_e::PluginWrite: return F(""); case BusCmd_CommandSource_e::PluginOncePerSecond: return F("_1ps"); case BusCmd_CommandSource_e::PluginTenPerSecond: return F("_10ps"); case BusCmd_CommandSource_e::PluginFiftyPerSecond: return F("_50ps"); @@ -256,18 +257,21 @@ std::vectorBusCmd_Helper_struct::parseBusCmdCommands(cons const String key = parseString(name, 1); String keyPostfix; + const bool parseAndLogOK = ((BusCmd_CommandSource_e::PluginRead == _commandSource) || + (BusCmd_CommandSource_e::PluginGetConfigVar == _commandSource) || + (BusCmd_CommandSource_e::PluginWrite == _commandSource) + ); if (!key.isEmpty() && (_commandCache.count(key) == 1) && !update) { commands = _commandCache.find(key)->second; - - if (loglevelActiveFor(LOG_LEVEL_INFO) && _showLog && ((BusCmd_CommandSource_e::PluginRead == _commandSource) || - (BusCmd_CommandSource_e::PluginGetConfigVar == _commandSource))) { +#ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_INFO) && _showLog && parseAndLogOK) { addLog(LOG_LEVEL_INFO, strformat(F("BUSCMD: Retrieve '%s' from cache with %d commands."), name.c_str(), commands.size())); } +#endif } - if (!line.isEmpty() && ((commands.empty()) || update) && ((BusCmd_CommandSource_e::PluginRead == _commandSource) || - (BusCmd_CommandSource_e::PluginGetConfigVar == _commandSource))) { + if (!line.isEmpty() && ((commands.empty()) || update) && parseAndLogOK) { int evt = 1; while (evt > 0) { @@ -322,8 +326,7 @@ std::vectorBusCmd_Helper_struct::parseBusCmdCommands(cons #ifndef LIMIT_BUILD_SIZE - if (loglevelActiveFor(LOG_LEVEL_INFO) && _showLog && ((BusCmd_CommandSource_e::PluginRead == _commandSource) || - (BusCmd_CommandSource_e::PluginGetConfigVar == _commandSource))) { + if (loglevelActiveFor(LOG_LEVEL_INFO) && _showLog && parseAndLogOK) { addLog(LOG_LEVEL_INFO, strformat(F("BUSCMD: Arguments parsed: %d (%s)"), args.size(), cmdAll.c_str())); } #endif // ifndef LIMIT_BUILD_SIZE @@ -506,11 +509,12 @@ std::vectorBusCmd_Helper_struct::parseBusCmdCommands(cons if (!key.isEmpty()) { _commandCache[concat(key, keyPostfix)] = commands; - +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO) && _showLog) { addLog(LOG_LEVEL_INFO, strformat(F("BUSCMD: Insert '%s%s' into cache with %d commands."), name.c_str(), keyPostfix.c_str(), commands.size())); } +#endif } } } @@ -528,6 +532,10 @@ bool BusCmd_Helper_struct::executeBusCmdCommands() { if ((nullptr == _iBusCmd_Handler) || !_iBusCmd_Handler->init()) { return result; } + const bool parseAndLogOK = ((BusCmd_CommandSource_e::PluginRead == _commandSource) || + (BusCmd_CommandSource_e::PluginGetConfigVar == _commandSource) || + (BusCmd_CommandSource_e::PluginWrite == _commandSource) + ); if (BusCmd_CommandState_e::Processing == _commandState) { _it = _commands.begin(); @@ -928,8 +936,7 @@ bool BusCmd_Helper_struct::executeBusCmdCommands() { if (!newVar.isEmpty()) { setCustomStringVar(newVar, newCalc); // Assign string value to a string variable - if (loglevelActiveFor(LOG_LEVEL_INFO) && _showLog && ((BusCmd_CommandSource_e::PluginRead == _commandSource) || - (BusCmd_CommandSource_e::PluginGetConfigVar == _commandSource))) { + if (loglevelActiveFor(LOG_LEVEL_INFO) && _showLog && parseAndLogOK) { addLog(LOG_LEVEL_INFO, strformat(F("BUSCMD: Calculation: %s -> LetStr,%s,%s"), toCalc.c_str(), newVar.c_str(), newCalc.c_str())); } @@ -938,11 +945,11 @@ bool BusCmd_Helper_struct::executeBusCmdCommands() { #endif // if FEATURE_BUSCMD_STRING && FEATURE_STRING_VARIABLES if (Calculate(newCalc, tmp) == CalculateReturnCode::OK) { - if (loglevelActiveFor(LOG_LEVEL_INFO) && _showLog && ((BusCmd_CommandSource_e::PluginRead == _commandSource) || - (BusCmd_CommandSource_e::PluginGetConfigVar == _commandSource))) { +#ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_INFO) && _showLog && parseAndLogOK) { addLog(LOG_LEVEL_INFO, strformat(F("BUSCMD: Calculation: %s, result: %s"), toCalc.c_str(), doubleToString(tmp).c_str())); } - +#endif if (BusCmd_Command_e::If == _it->command) { if (essentiallyZero(tmp)) { // 0 = false => cancel execution if (0 == _it->len) { @@ -1058,18 +1065,18 @@ bool BusCmd_Helper_struct::executeBusCmdCommands() { } } #ifndef LIMIT_BUILD_SIZE - if (loglevelActiveFor(LOG_LEVEL_INFO) && _showLog && ((BusCmd_CommandSource_e::PluginRead == _commandSource) || - (BusCmd_CommandSource_e::PluginGetConfigVar == _commandSource))) { - #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + + if (loglevelActiveFor(LOG_LEVEL_INFO) && _showLog && parseAndLogOK) { + # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE const String valStr = doubleToString(_value, 2, true); - #else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE const String valStr = toString(_value, 2, true); - #endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE addLog(LOG_LEVEL_INFO, strformat(F("BUSCMD: Executing command: %s, value[%d]:(%c): %s"), _it->toString().c_str(), _varIndex, _valueIsSet ? 't' : 'f', valStr.c_str())); } -#endif +#endif // ifndef LIMIT_BUILD_SIZE ++_it; // Next command while (toSkip > 0 && _it != _commands.end()) { @@ -1131,9 +1138,11 @@ bool BusCmd_Helper_struct::parseAndExecute(BusCmd_CommandSource_e source, _commands = parseBusCmdCommands(EMPTY_STRING, line); if (!_commands.empty()) { +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO) && _showLog) { addLog(LOG_LEVEL_INFO, strformat(logFormat, _commands.size())); } +#endif _commandState = BusCmd_CommandState_e::Processing; result = executeBusCmdCommands(); _commands.clear(); @@ -1206,7 +1215,9 @@ bool BusCmd_Helper_struct::processCommands(struct EventStruct *event) { _commands = parseBusCmdCommands(cacheName, // PluginOnce/Ten/Fifty-PerSecond must come from cache (BusCmd_CommandSource_e::PluginRead == _commandSource) || - (BusCmd_CommandSource_e::PluginGetConfigVar == _commandSource) ? buf.commandSet : EMPTY_STRING); + (BusCmd_CommandSource_e::PluginGetConfigVar == _commandSource) || + (BusCmd_CommandSource_e::PluginWrite == _commandSource) + ? buf.commandSet : EMPTY_STRING); } _varIndex = _loop; } @@ -1226,7 +1237,9 @@ bool BusCmd_Helper_struct::processCommands(struct EventStruct *event) { _commands = parseBusCmdCommands(cacheName, // PluginOnce/Ten/Fifty-PerSecond must come from cache (BusCmd_CommandSource_e::PluginRead == _commandSource) || - (BusCmd_CommandSource_e::PluginGetConfigVar == _commandSource) ? buf.commandSet : EMPTY_STRING); + (BusCmd_CommandSource_e::PluginGetConfigVar == _commandSource) || + (BusCmd_CommandSource_e::PluginWrite == _commandSource) + ? buf.commandSet : EMPTY_STRING); _varIndex = _loop; } } @@ -1307,3 +1320,4 @@ bool BusCmd_Helper_struct::plugin_get_config(struct EventStruct *event, } #endif // ifndef LIMIT_BUILD_SIZE +#endif \ No newline at end of file diff --git a/src/src/Helpers/BusCmd_Helper.h b/src/src/Helpers/BusCmd_Helper.h index b21249a65..1c9b19b77 100644 --- a/src/src/Helpers/BusCmd_Helper.h +++ b/src/src/Helpers/BusCmd_Helper.h @@ -10,7 +10,7 @@ */ #include "../../_Plugin_Helper.h" - +#if FEATURE_BUS_COMMAND #include "../Helpers/IBusCmd_Handler.h" #define BUSCMD_EVENT_SEPARATOR '|' @@ -72,6 +72,7 @@ enum class BusCmd_CommandState_e :uint8_t { enum class BusCmd_CommandSource_e : uint8_t { PluginIdle = 0u, PluginRead, + PluginWrite, PluginOncePerSecond, PluginTenPerSecond, PluginFiftyPerSecond, @@ -189,6 +190,14 @@ struct BusCmd_Helper_struct { const String& name, const String& line); + inline void setCommandSource(BusCmd_CommandSource_e commandSource) { + _commandSource = commandSource; + } + + inline BusCmd_CommandSource_e getCommandSource() const { + return _commandSource; + } + // Getters BusCmd_CommandState_e getCommandState() const { return _commandState; @@ -230,4 +239,5 @@ private: std::vector::iterator _it; std::vector _buffer; }; +#endif #endif // ifndef _HELPERS_BUSCMD_HELPER_H diff --git a/src/src/Helpers/Convert.cpp b/src/src/Helpers/Convert.cpp index 1127858b1..ee2f9f73b 100644 --- a/src/src/Helpers/Convert.cpp +++ b/src/src/Helpers/Convert.cpp @@ -103,7 +103,7 @@ String minutesToHourColonMinute(int minutes) { return strformat(F("%02d:%02d"), hours, mins); } -String secondsToDayHourMinuteSecond(int seconds) { +String secondsToDayHourMinuteSecond(int seconds, bool useHMS) { const int sec = seconds % 60; const int minutes = seconds / 60; const int days = minutes / 1440; @@ -111,44 +111,57 @@ String secondsToDayHourMinuteSecond(int seconds) { const int hours = min_day / 60; const int mins = min_day % 60; if (days == 0) { - return strformat(F("%02d:%02d:%02d"), hours, mins, sec); + if (hours == 0 && useHMS) { + return strformat(F("%dm%02ds"), mins, sec); + } + return strformat( + useHMS ? F("%dh%02dm%02ds") : F("%02d:%02d:%02d"), + hours, mins, sec); } - return strformat(F("%dT%02d:%02d:%02d"), days, hours, mins, sec); + return strformat( + useHMS ? F("%dT%02dh%02dm%02ds") : F("%dT%02d:%02d:%02d"), + days, hours, mins, sec); } -String secondsToDayHourMinuteSecond_ms(int64_t systemMicros) +String secondsToDayHourMinuteSecond_ms(int64_t systemMicros, bool useHMS) { if (systemMicros < 0ll) { - return concat('-', secondsToDayHourMinuteSecond_ms(-1ll*systemMicros)); + return concat('-', secondsToDayHourMinuteSecond_ms(-1ll*systemMicros, useHMS)); } uint32_t usec{}; const uint32_t seconds = micros_to_sec_usec(systemMicros, usec); return strformat( F("%s.%03u"), - secondsToDayHourMinuteSecond(seconds).c_str(), + secondsToDayHourMinuteSecond(seconds, useHMS).c_str(), usec / 1000ul); } -String format_msec_duration(int64_t duration) { +String format_msec_duration(int64_t duration, bool useHMS) { if (duration < 0ll) { - return concat('-', format_msec_duration(-1ll*duration)); + return concat('-', format_msec_duration(-1ll*duration, useHMS)); } const uint32_t duration_s = duration / 1000ll; const int32_t duration_ms = duration % 1000ll; if (duration_s < 60) { return strformat( - F("%02d.%03d"), + useHMS ? F("%02d.%03d sec") : F("%02d.%03d"), duration_s, duration_ms); } + if (useHMS && duration_s > 60) { + // No need to show msec when time is over 1 minute + return secondsToDayHourMinuteSecond(duration_s, useHMS); + } return strformat( F("%s.%03d"), - secondsToDayHourMinuteSecond(duration_s).c_str(), + secondsToDayHourMinuteSecond(duration_s, useHMS).c_str(), duration_ms); } +String format_msec_duration_HMS(int64_t duration) { return format_msec_duration(duration, true); } + // Compute the dew point temperature, given temperature and humidity (temp in Celsius) // Formula: http://www.ajdesigner.com/phphumidity/dewpoint_equation_dewpoint_temperature.php diff --git a/src/src/Helpers/Convert.h b/src/src/Helpers/Convert.h index 276c36ad5..6fe350a73 100644 --- a/src/src/Helpers/Convert.h +++ b/src/src/Helpers/Convert.h @@ -24,10 +24,11 @@ String minutesToDayHourMinute(int minutes); String minutesToHourColonMinute(int minutes); -String secondsToDayHourMinuteSecond(int seconds); -String secondsToDayHourMinuteSecond_ms(int64_t systemMicros); +String secondsToDayHourMinuteSecond(int seconds, bool useHMS = false); +String secondsToDayHourMinuteSecond_ms(int64_t systemMicros, bool useHMS = false); -String format_msec_duration(int64_t duration); +String format_msec_duration(int64_t duration, bool useHMS = false); +String format_msec_duration_HMS(int64_t duration); // Compute the dew point temperature, given temperature and humidity (temp in Celsius) // Formula: http://www.ajdesigner.com/phphumidity/dewpoint_equation_dewpoint_temperature.php diff --git a/src/src/Helpers/DeepSleep.cpp b/src/src/Helpers/DeepSleep.cpp index f9248937e..a5ac77b44 100644 --- a/src/src/Helpers/DeepSleep.cpp +++ b/src/src/Helpers/DeepSleep.cpp @@ -4,9 +4,9 @@ #include "../../ESPEasy-Globals.h" #include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/ESPEasyEth.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyWifi.h" +#include "../../ESPEasy/net/eth/ESPEasyEth.h" +#include "../../ESPEasy/net/ESPEasyNetwork.h" +#include "../../ESPEasy/net/wifi/ESPEasyWifi.h" #include "../ESPEasyCore/ESPEasyRules.h" #include "../Globals/EventQueue.h" @@ -77,7 +77,7 @@ bool readyForSleep() return false; } - if (!NetworkConnected()) { + if (!ESPEasy::net::NetworkConnected()) { // Allow 12 seconds to establish connections return timeOutReached(timerAwakeFromDeepSleep + 12000); } diff --git a/src/src/Helpers/ESPEasyStatistics.cpp b/src/src/Helpers/ESPEasyStatistics.cpp index 81e8addd6..5c53f1f2b 100644 --- a/src/src/Helpers/ESPEasyStatistics.cpp +++ b/src/src/Helpers/ESPEasyStatistics.cpp @@ -2,7 +2,7 @@ #if FEATURE_TIMING_STATS - +#ifdef WEBSERVER_NEW_UI #include "../DataStructs/TimingStats.h" #include "../WebServer/ESPEasy_WebServer.h" #include "../Helpers/Convert.h" @@ -139,5 +139,5 @@ void jsonStatistics(bool clearStats) { } } - +#endif #endif // if FEATURE_TIMING_STATS \ No newline at end of file diff --git a/src/src/Helpers/ESPEasyStatistics.h b/src/src/Helpers/ESPEasyStatistics.h index f35f28ddb..dbc48bf79 100644 --- a/src/src/Helpers/ESPEasyStatistics.h +++ b/src/src/Helpers/ESPEasyStatistics.h @@ -6,7 +6,7 @@ #if FEATURE_TIMING_STATS - +#ifdef WEBSERVER_NEW_UI #include "../DataStructs/TimingStats.h" //void logStatistics(uint8_t loglevel, bool clearStats); @@ -14,7 +14,7 @@ void stream_json_timing_stats(const TimingStats& stats, long timeSinceLastReset); void jsonStatistics(bool clearStats); - +#endif #endif // if FEATURE_TIMING_STATS diff --git a/src/src/Helpers/ESPEasy_FactoryDefault.cpp b/src/src/Helpers/ESPEasy_FactoryDefault.cpp index 77a98564d..aac872006 100644 --- a/src/src/Helpers/ESPEasy_FactoryDefault.cpp +++ b/src/src/Helpers/ESPEasy_FactoryDefault.cpp @@ -11,10 +11,10 @@ #include "../DataStructs/GpioFactorySettingsStruct.h" #include "../ESPEasyCore/ESPEasy_backgroundtasks.h" -#include "../ESPEasyCore/ESPEasyWifi.h" +#include "../../ESPEasy/net/wifi/ESPEasyWifi.h" #include "../ESPEasyCore/Serial.h" -#include "../Globals/ESPEasyWiFiEvent.h" +#include "../../ESPEasy/net/Globals/ESPEasyWiFiEvent.h" #include "../Globals/RTC.h" #include "../Globals/ResetFactoryDefaultPref.h" #include "../Globals/SecuritySettings.h" @@ -236,7 +236,9 @@ void ResetFactory(bool formatFS) setLogLevelFor(LOG_TO_SYSLOG, DEFAULT_SYSLOG_LEVEL); setLogLevelFor(LOG_TO_SERIAL, DEFAULT_SERIAL_LOG_LEVEL); setLogLevelFor(LOG_TO_WEBLOG, DEFAULT_WEB_LOG_LEVEL); +#if FEATURE_SD setLogLevelFor(LOG_TO_SDCARD, DEFAULT_SD_LOG_LEVEL); +#endif Settings.SyslogFacility = DEFAULT_SYSLOG_FACILITY; Settings.SyslogPort = DEFAULT_SYSLOG_PORT; Settings.UseValueLogger = DEFAULT_USE_SD_LOG; @@ -318,10 +320,10 @@ void ResetFactory(bool formatFS) Settings.NetworkMedium = gpio_settings.network_medium; /* - Settings.GlobalSync = DEFAULT_USE_GLOBAL_SYNC; + Settings.GlobalSync_unused = DEFAULT_USE_GLOBAL_SYNC; Settings.IP_Octet = DEFAULT_IP_OCTET; - Settings.WDI2CAddress = DEFAULT_WD_IC2_ADDRESS; + Settings.WDI2CAddress = DEFAULT_WD_IC2_ADDRESS; Settings.UseSSDP = DEFAULT_USE_SSDP; Settings.ConnectionFailuresThreshold = DEFAULT_CON_FAIL_THRES; Settings.WireClockStretchLimit = DEFAULT_I2C_CLOCK_LIMIT; @@ -419,8 +421,8 @@ void ResetFactory(bool formatFS) // NOTE: this is a known ESP8266 bug, not our fault. :) delay(1000); WiFi.persistent(true); // use SDK storage of SSID/WPA parameters - WiFiEventData.intent_to_reboot = true; - WifiDisconnect(); // this will store empty ssid/wpa into sdk storage +// WiFiEventData.intent_to_reboot = true; + ESPEasy::net::wifi::WifiDisconnect(); // this will store empty ssid/wpa into sdk storage WiFi.persistent(false); // Do not use SDK storage of SSID/WPA parameters reboot(IntendedRebootReason_e::ResetFactory); } diff --git a/src/src/Helpers/ESPEasy_Storage.cpp b/src/src/Helpers/ESPEasy_Storage.cpp index 9274f2c74..c75b15727 100644 --- a/src/src/Helpers/ESPEasy_Storage.cpp +++ b/src/src/Helpers/ESPEasy_Storage.cpp @@ -15,26 +15,27 @@ # include "../ESPEasyCore/Controller.h" #endif // if FEATURE_MQTT #include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyWifi.h" +#include "../../ESPEasy/net/ESPEasyNetwork.h" +#include "../../ESPEasy/net/wifi/ESPEasyWifi.h" + #include "../ESPEasyCore/Serial.h" #include "../Globals/CRCValues.h" #include "../Globals/Cache.h" #include "../Globals/Device.h" -#include "../Globals/ESPEasyWiFiEvent.h" +#include "../../ESPEasy/net/Globals/ESPEasyWiFiEvent.h" #include "../Globals/ESPEasy_Scheduler.h" #include "../Globals/ESPEasy_time.h" #include "../Globals/EventQueue.h" #include "../Globals/ExtraTaskSettings.h" -#include "../Globals/NetworkState.h" +#include "../../ESPEasy/net/Globals/NetworkState.h" #include "../Globals/Plugins.h" #include "../Globals/RTC.h" #include "../Globals/ResetFactoryDefaultPref.h" #include "../Globals/RuntimeData.h" #include "../Globals/SecuritySettings.h" #include "../Globals/Settings.h" -#include "../Globals/WiFi_AP_Candidates.h" +#include "../../ESPEasy/net/Globals/WiFi_AP_Candidates.h" #include "../Helpers/ESPEasyRTC.h" #include "../Helpers/ESPEasy_checks.h" @@ -61,7 +62,10 @@ #ifdef ESP32 # include -# include +#ifndef ESP32P4 +# include +#endif + String patch_fname(const String& fname) { if (fname.startsWith(F("/"))) { @@ -265,8 +269,9 @@ bool tryDeleteFile(const String& fname, FileDestination_e destination) { if (fname.length() > 0) { #if FEATURE_RTC_CACHE_STORAGE + const bool cacheFile = isCacheFile(fname); - if (isCacheFile(fname)) { + if (cacheFile) { ControllerCache.closeOpenFiles(); } #endif // if FEATURE_RTC_CACHE_STORAGE @@ -287,6 +292,11 @@ bool tryDeleteFile(const String& fname, FileDestination_e destination) { res = SD.remove(patch_fname(fname)); } #endif // if FEATURE_SD +#ifndef BUILD_NO_DEBUG + if (!res) { + addLog(LOG_LEVEL_ERROR, concat(F("Del : Could not delete "), patch_fname(fname))); + } +#endif // A call to GarbageCollection() will at most erase a single block. (e.g. 8k block size) // A deleted file may have covered more than a single block, so try to clear multiple blocks. @@ -295,6 +305,14 @@ bool tryDeleteFile(const String& fname, FileDestination_e destination) { while (retries > 0 && GarbageCollection()) { --retries; } +#if FEATURE_RTC_CACHE_STORAGE +/* + if (cacheFile) { + ControllerCache.updateRTC_filenameCounters(); + // FIXME TD-er: Tell Cache Reader a file has been deleted + } +*/ +#endif return res; } return false; @@ -426,11 +444,11 @@ bool BuildFixes() if (Settings.Build < 20112) { Settings.WiFi_TX_power = 70; // 70 = 17.5dBm. unit: 0.25 dBm - Settings.WiFi_sensitivity_margin = 3; // Margin in dBm on top of sensitivity. + Settings.WiFi_sensitivity_margin = 5; // Margin in dBm on top of sensitivity. } if (Settings.Build < 20113) { - Settings.NumberExtraWiFiScans = 0; + Settings.ConnectFailRetryCount = 0; } if (Settings.Build < 20114) { @@ -678,18 +696,21 @@ bool check_and_update_WiFi_Calibration() { bool Erase_WiFi_Calibration() { #ifdef ESP8266 - WifiDisconnect(); - setWifiMode(WIFI_OFF); + ESPEasy::net::wifi::WifiDisconnect(); + ESPEasy::net::wifi::setWifiMode(WIFI_OFF); if (!ESP.eraseConfig()) return false; - #ifndef BUILD_MINIMAL_OTA + #ifndef LIMIT_BUILD_SIZE addLog(LOG_LEVEL_INFO, F("WiFi : Erased WiFi calibration data")); #endif #endif #ifdef ESP32 - WifiDisconnect(); - setWifiMode(WIFI_OFF); + # ifndef SOC_WIFI_SUPPORTED + return false; + #else + ESPEasy::net::wifi::WifiDisconnect(); + ESPEasy::net::wifi::setWifiMode(WIFI_OFF); // Make sure power is stable, so wait a bit longer delay(1000); esp_phy_erase_cal_data_in_nvs(); @@ -700,6 +721,7 @@ bool Erase_WiFi_Calibration() { delay(200); setWiFi_CalibrationVersion(); #endif + #endif return true; } @@ -821,12 +843,12 @@ String SaveSettings(bool forFactoryReset) return err; } -#ifndef BUILD_MINIMAL_OTA +#ifndef LIMIT_BUILD_SIZE // Must check this after saving, or else it is not possible to fix multiple // issues which can only corrected on different pages. if (!SettingsCheck(err)) { return err; } -#endif // ifndef BUILD_MINIMAL_OTA +#endif // } @@ -853,12 +875,16 @@ String SaveSecuritySettings(bool forFactoryReset) { sizeof(SecuritySettings)); // Security settings are saved, may be update of WiFi settings or hostname. - if (!forFactoryReset && !NetworkConnected()) { - if (SecuritySettings.hasWiFiCredentials() && (active_network_medium == NetworkMedium_t::WIFI)) { - WiFiEventData.wifiConnectAttemptNeeded = true; - WiFi_AP_Candidates.force_reload(); // Force reload of the credentials and found APs from the last scan - resetWiFi(); - AttemptWiFiConnect(); + if (!forFactoryReset) { + if (SecuritySettings.hasWiFiCredentials() && (active_network_medium == ESPEasy::net::NetworkMedium_t::WIFI)) { + ESPEasy::net::wifi::WiFi_AP_Candidates.force_reload(); // Force reload of the credentials and found APs from the last scan + if (!ESPEasy::net::NetworkConnected()) { +// WiFiEventData.wifiConnectAttemptNeeded = true; + ESPEasy::net::wifi::resetWiFi(); + String dummy; + ESPEasy::net::NWPluginCall( + NWPlugin::Function::NWPLUGIN_CREDENTIALS_CHANGED, 0, dummy); + } } } } @@ -871,9 +897,15 @@ String SaveSecuritySettings(bool forFactoryReset) { // FIXME TD-er: How to check if these have changed? if (forFactoryReset) { ExtendedControllerCredentials.clear(); +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + SecuritySettings_deviceSpecific.clear(); +#endif } ExtendedControllerCredentials.save(); +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + SecuritySettings_deviceSpecific.save(); +#endif if (!forFactoryReset) { afterloadSettings(); @@ -933,14 +965,16 @@ void afterloadSettings() { applyFactoryDefaultPref(); Scheduler.setEcoMode(Settings.EcoPowerMode()); #ifdef ESP32 + #if !defined(CORE32SOLO1) && !defined(ESP32P4) setCpuFrequencyMhz(Settings.EcoPowerMode() ? getCPU_MinFreqMHz() : getCPU_MaxFreqMHz()); + #endif #endif // ifdef ESP32 if (!Settings.UseRules) { eventQueue.clear(); } node_time.applyTimeZone(); - CheckRunningServices(); // To update changes in hostname. + ESPEasy::net::CheckRunningServices(); // To update changes in hostname. } /********************************************************************************************\ @@ -1009,6 +1043,10 @@ String LoadSettings() #endif // ifndef BUILD_NO_DEBUG ExtendedControllerCredentials.load(); +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + SecuritySettings_deviceSpecific.load(); +#endif + // setupStaticIPconfig(); // FIXME TD-er: Must check if static/dynamic IP was changed and trigger a reconnect? Or is a reboot better when changing those settings? @@ -1105,6 +1143,36 @@ uint8_t disableAllNotifications(uint8_t bootFailedCount) { #endif // if FEATURE_NOTIFIER +/********************************************************************************************\ + Disable Network Interfaces, based on bootFailedCount + \*********************************************************************************************/ +uint8_t disableNetwork(uint8_t bootFailedCount) +{ + for (ESPEasy::net::networkIndex_t i = 0; i < NETWORK_MAX && bootFailedCount > 0; ++i) { + if (Settings.getNetworkEnabled(i)) { + --bootFailedCount; + + if (bootFailedCount == 0) { + Settings.setNetworkEnabled(i, false); + } + } + } + return bootFailedCount; +} + +uint8_t disableAllNetworkss(uint8_t bootFailedCount) +{ + if (bootFailedCount > 0) { + --bootFailedCount; + + for (ESPEasy::net::networkIndex_t i = 0; i < NETWORK_MAX; ++i) { + Settings.setNetworkEnabled(i, false); + } + } + return bootFailedCount; +} + + /********************************************************************************************\ Disable Rules, based on bootFailedCount \*********************************************************************************************/ @@ -1361,8 +1429,7 @@ String SaveTaskSettings(taskIndex_t TaskIndex) reinterpret_cast(&ExtraTaskSettings), sizeof(struct ExtraTaskSettingsStruct)); -#if !defined(PLUGIN_BUILD_MINIMAL_OTA) && !defined(ESP8266_1M) - +#ifndef LIMIT_BUILD_SIZE if (err.isEmpty()) { err = checkTaskSettings(TaskIndex); } @@ -2605,11 +2672,6 @@ String getPartitionTable(uint8_t pType, const String& itemSep, const String& lin #if FEATURE_DOWNLOAD String downloadFileType(const String& url, const String& user, const String& pass, FileType::Enum filetype, unsigned int filenr) { - if (!getDownloadFiletypeChecked(filetype, filenr)) { - // Not selected, so not downloaded - return F("Not Allowed"); - } - String filename = getFileName(filetype, filenr); String fullUrl = joinUrlFilename(url, filename); String error; @@ -2663,6 +2725,48 @@ String downloadFileType(const String& url, const String& user, const String& pas return error; } +# if defined(ESP8266) +void deleteBakFiles() +{ + fs::Dir dir = ESPEASY_FS.openDir(""); + + while (dir.next()) + { + const String fname = dir.fileName(); + if (fname.endsWith(F("_bak"))) { + if (tryDeleteFile(fname)) { + delay(1); + } + } + } +} +#endif +#ifdef ESP32 +void deleteBakFiles() +{ + fs::File root = ESPEASY_FS.open("/"); + fs::File file = root.openNextFile(); + + while (file) + { + if (!file.isDirectory()) { + const String fname = file.name(); + // Need to open next file or else we cannot delete the file + file = root.openNextFile(); + + if (fname.endsWith(F("_bak"))) { + addLog(LOG_LEVEL_INFO, concat(F("Del : Delete _bak file: "), fname)); + if (tryDeleteFile(fname)) { + delay(1); + } + } + } else { + file = root.openNextFile(); + } + } +} +#endif + #endif // if FEATURE_DOWNLOAD bool validateUploadConfigDat(const uint8_t *buf) { diff --git a/src/src/Helpers/ESPEasy_Storage.h b/src/src/Helpers/ESPEasy_Storage.h index 93b648405..9d0f07645 100644 --- a/src/src/Helpers/ESPEasy_Storage.h +++ b/src/src/Helpers/ESPEasy_Storage.h @@ -153,6 +153,12 @@ uint8_t disableAllNotifications(uint8_t bootFailedCount); \*********************************************************************************************/ uint8_t disableRules(uint8_t bootFailedCount); +/********************************************************************************************\ + Disable Network Interfaces, based on bootFailedCount + \*********************************************************************************************/ +uint8_t disableNetwork(uint8_t bootFailedCount); +uint8_t disableAllNetworkss(uint8_t bootFailedCount); + bool getAndLogSettingsParameters(bool read, SettingsType::Enum settingsType, int index, int& offset, int& max_size); @@ -393,6 +399,8 @@ bool validateUploadConfigDat(const uint8_t *buf); #if FEATURE_DOWNLOAD String downloadFileType(const String& url, const String& user, const String& pass, FileType::Enum filetype, unsigned int filenr = 0); +void deleteBakFiles(); + #endif // if FEATURE_DOWNLOAD #if FEATURE_CUSTOM_PROVISIONING // Download file type based on settings stored in provisioning.dat file. diff --git a/src/src/Helpers/ESPEasy_UnitOfMeasure.cpp b/src/src/Helpers/ESPEasy_UnitOfMeasure.cpp new file mode 100644 index 000000000..a2571a562 --- /dev/null +++ b/src/src/Helpers/ESPEasy_UnitOfMeasure.cpp @@ -0,0 +1,198 @@ +#include "../Helpers/ESPEasy_UnitOfMeasure.h" + +#if FEATURE_TASKVALUE_UNIT_OF_MEASURE +# include "../DataTypes/SensorVType.h" + +// *** DO NOT CHANGE ORDER, SAVED IN TASK SETTINGS! *** +const char unit_of_measure_list[] PROGMEM = + "|" // 0 = Empty/none + "°C|°F|K|" // 1..3 + "%|" // 4 + "Pa|hPa|bar|mbar|inHg|psi|" // 5..10 + "W|kW|" // 11..12 + "V|" // 13 + "Wh|kWh|" // 14..15 + "A|VA|" // 16..17 + "mm|cm|m|km|" // 18..21 + "L|mL|m³|ft³|" // 22..25 + "m³/h|ft³/h|" // 26..27 + "lx|" // 28 + "UV index|" // 29 + "µg/m³|mg/m³|p/m³|ppm|ppb|" // 30..34 + "°|" // 35 + "€|$|¢|" // 36..38 + "μs|ms|s|min|h|d|w|m|y|" // 39..47 + "in|ft|yd|mi|" // 48..51 + "Hz|GHz|" // 52..53 + "gal|fl. oz|" // 54..55 + "m²|" // 56 + "g|kg|mg|µg|" // 57..60 + "oz|lb|" // 61..62 + "µS/cm|" // 63 + "W/m²|" // 64 + "mm/h|" // 65 + "mm/s|in/s|m/s|in/h|km/h|mph|" // 66..71 + "dB|dBm|" // 72..73 + "bit|kbit|Mbit|Gbit|B|kB|MB|GB|TB|PB|EB|ZB|YB|KiB|MiB|GiB|TiB|PiB|EiB|ZiB|YiB|" // 74..94 + "bit/s|kbit/s|Mbit/s|Gbit/s|B/s|kB/s|MB/s|GB/s|KiB/s|MiB/s|GiB/s|" // 95..105 + "ft/s|kn|" // 106..107 + "mW|MW|GW|TW|" // 108..111 + "BTU/(h⋅ft²)|" // 112 + "pH|" // 113 + "cbar|mmHg|kPa|" // 114..116 + "mA|µA|mV|µV|kV|" // 117..121 + "cm²|km²|mm²|in²|ft²|yd²|mi²|ac|ha|" // 122..130 + "kHz|MHz|" // 131..132 + "mWh|MWh|GWh|TWh|cal|kcal|Mcal|Gcal|J|kJ|MJ|GJ|" // 133..144 + "var|kvar|varh|kvarh|" // 145..148 + "st|" // 149 + "mg/dL|mmol/L|" // 150..151 + "μSv|μSv/h|" // 152..153 + "m³/s|ft³/min|L/h|L/min|L/s|gal/min|mL/s|" // 154..160 + "g/m³|kWh/100km|Wh/km|mi/kWh|km/kWh|" // 161..165 + "in/d|mm/d|" // 166 .. 167 + "Ah|" // 168 +; // *** DO NOT CHANGE ORDER, SAVED IN TASK SETTINGS! *** + + +// Not stored, when UoM index >= 1024 it's a label-index with 1024 subtracted +const char unit_of_measure_labels[] PROGMEM = + "Apparent power|Air quality/CO/CO2|Area|(Atmospheric) Pressure|" // A 1024..1027 + "Blood glucose concentr.|" // B 1028 + "Data rate|Data size|Distance|Duration|" // D 1029..1032 + "Energy distance|Energy(-storage)|" // E 1033..1034 + "Frequency|" // F 1035 + "Gas|" // G 1036 + "Percent Hum./Batt./Moist.|" // H 1037 + "Illuminance|Irradiance|" // I 1038..1039 + "Monetary|" // M 1040 + "Nitrogen (di-/mon-)oxide|" // N 1041 + "Voc/Ozone|" // O 1042 + "Ph|PM/CO/CO2/NO(x)/Voc/Ozone|Power|" // P 1043..1045 + "Radiation|Reactive energy/power|" // R 1046..1047 + "Signal strength|Sound pressure|Speed|" // S 1048..1050 + "Temperature|" // T 1051 + "Voltage/Current|Volume/Water cons.|Volume flow rate|" // V 1052..1054 + "Weight|Wind direction/angle|" // W 1055..1056 + "Various units|" // Additional 1057 +; + +const uint16_t unit_of_measure_map[] PROGMEM = { + 1051, 1, 2, 3, // Temperature + 1037, 4, // Percent Battery, + // Humidity, + // Moisture + 1027, 8, 6, 116, 7, 115, 10, 5, 114, 9, // (Atmospheric) + // Pressure + 1052, 13, 119, 120, 121, 16, 117, 118, // Voltage/Current + 1045, 11, 12, 108, 109, 110, 111, // Power + 1024, 17, // Apparent power + 1047, 145, 146, 147, 148, 168, // Reactive + // power/energy + 1044, 30, 31, 32, 33, 34, // Particle matter + 1031, 18, 19, 20, 21, 48, 49, 50, 51, // Distance + 1055, 57, 58, 59, 60, 61, 62, 149, // Weight + 1053, 22, 23, 24, 25, 54, 55, // Volume/Water + 1054, 26, 27, 153, 154, 155, 156, 157, 158, 159, 160, // Volume flow rate + 1032, 39, 40, 41, 42, 43, 44, 45, 46, 47, // Duration + 1034, 14, 15, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, // Energy(-storage) + 1033, 162, 163, 164, 165, // Energy distance + 1050, 66, 67, 68, 69, 70, 71, 65, 106, 107, // Speed + 1056, 35, // (Wind) + // direction/angle + 1038, 28, // Illuminance + 1039, 64, 112, // Irradiance + 1046, 152, 153, // Radiation + 1057, 29, 63, 161, 166, 167, // Various units + 1035, 52, 53, 131, 132, // Frequency + 1043, 113, // Potential + // hydrogen + 1026, 56, 122, 123, 124, 125, 126, 127, 128, 129, 130, // Area + 1029, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, // Data rate + 1030, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87,88, 89, 90, 91, 92, 93, 94, // Data size + 1049, 72, 73, // Sound pressure + 1028, 150, 151, // Blood glucose + 1040, 36, 37, 38, // Monetary +}; +const uint16_t unit_of_measure_map_size = NR_ELEMENTS(unit_of_measure_map); + +String toUnitOfMeasureName(const uint32_t unitOfMeasureIndex, + const String & defUoM) { + char tmp[26]{}; // "PM/CO/CO2/NO(x)/Voc/Ozone" + \0 + String result; + + if (unitOfMeasureIndex < 1024) { + result = GetTextIndexed(tmp, sizeof(tmp), unitOfMeasureIndex, unit_of_measure_list); + } else { + result = GetTextIndexed(tmp, sizeof(tmp), unitOfMeasureIndex - 1024, unit_of_measure_labels); + } + + return result.isEmpty() ? defUoM : result; +} + +int getUnitOfMeasureIndex(const String& uomName) { + return GetCommandCode(uomName.c_str(), unit_of_measure_list); +} + +/** + * getUoMGroupForUoM: Get the selector group for the requested Unit of Measurement name + */ +uint16_t getUoMGroupForUoM(const String& uom) { + return getUoMGroupForUoM(getUnitOfMeasureIndex(uom)); +} + +/** + * getUoMGroupForUoM: Get the selector group for the requested Unit of Measurement index + */ +uint16_t getUoMGroupForUoM(const int findUom) { + if ((findUom < 0)) { return 0; } + const uint16_t find = findUom; + uint16_t result = 0; + uint16_t last = 0; + + for (uint16_t idx = 0; idx < unit_of_measure_map_size; ++idx) { + const uint16_t uomIdx = pgm_read_word_near(&unit_of_measure_map[idx]); + + if (uomIdx < 1024) { + if (find == uomIdx) { + result = last; + break; + } + } else { + last = uomIdx; + } + } + return result; +} + +/** + * getDefaultUoMforSensorVType: get the default UoM parameters into Par64[] array for Sensor_VType::SENSOR_TYPE_TEMP_HUM, + * Sensor_VType::SENSOR_TYPE_TEMP_BARO, Sensor_VType::SENSOR_TYPE_TEMP_EMPTY_BARO and Sensor_VType::SENSOR_TYPE_TEMP_HUM_BARO + */ +bool getDefaultUoMforSensorVType(EventStruct *event) { + switch (event->sensorType) { // These are the only default UoM values to be handled + case Sensor_VType::SENSOR_TYPE_TEMP_HUM: + event->Par64_1 = UOM_GROUP_TEMPERATURE; + event->Par64_2 = UOM_GROUP_PERC_HUM_BAT_MOIST; + return true; + case Sensor_VType::SENSOR_TYPE_TEMP_BARO: + event->Par64_1 = UOM_GROUP_TEMPERATURE; + event->Par64_2 = UOM_GROUP_PRESSURE; + return true; + case Sensor_VType::SENSOR_TYPE_TEMP_EMPTY_BARO: + event->Par64_1 = UOM_GROUP_TEMPERATURE; + event->Par64_2 = UOM_GROUP_NONE; + event->Par64_3 = UOM_GROUP_PRESSURE; + return true; + case Sensor_VType::SENSOR_TYPE_TEMP_HUM_BARO: + event->Par64_1 = UOM_GROUP_TEMPERATURE; + event->Par64_2 = UOM_GROUP_PERC_HUM_BAT_MOIST; + event->Par64_3 = UOM_GROUP_PRESSURE; + return true; + default: + break; + } + return false; +} + +#endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE diff --git a/src/src/Helpers/ESPEasy_UnitOfMeasure.h b/src/src/Helpers/ESPEasy_UnitOfMeasure.h new file mode 100644 index 000000000..6f39edadc --- /dev/null +++ b/src/src/Helpers/ESPEasy_UnitOfMeasure.h @@ -0,0 +1,73 @@ +#pragma once + +#if FEATURE_TASKVALUE_UNIT_OF_MEASURE + +# include "../DataStructs/ESPEasy_EventStruct.h" +# include "../Helpers/StringConverter.h" + +const uint64_t UOM_GROUP_APPARENT_POWER = 0x0000000000000001; // 1024 +const uint64_t UOM_GROUP_AIR_QUALITY = 0x0000000000000002; +const uint64_t UOM_GROUP_AREA = 0x0000000000000004; +const uint64_t UOM_GROUP_PRESSURE = 0x0000000000000008; +const uint64_t UOM_GROUP_BLOOD_GLUCOSE = 0x0000000000000010; +const uint64_t UOM_GROUP_DATA_RATE = 0x0000000000000020; +const uint64_t UOM_GROUP_DATA_SIZE = 0x0000000000000040; // 1030 +const uint64_t UOM_GROUP_DISTANCE = 0x0000000000000080; +const uint64_t UOM_GROUP_DURATION = 0x0000000000000100; +const uint64_t UOM_GROUP_ENERGY_DISTANCE = 0x0000000000000200; +const uint64_t UOM_GROUP_ENERGY_STORAGE = 0x0000000000000400; +const uint64_t UOM_GROUP_FREQUENCY = 0x0000000000000800; // 1035 +const uint64_t UOM_GROUP_GAS = 0x0000000000001000; +const uint64_t UOM_GROUP_PERC_HUM_BAT_MOIST = 0x0000000000002000; +const uint64_t UOM_GROUP_ILLUMINANCE = 0x0000000000004000; +const uint64_t UOM_GROUP_IRRADIANCE = 0x0000000000008000; +const uint64_t UOM_GROUP_MONETARY = 0x0000000000010000; // 1040 +const uint64_t UOM_GROUP_NITROGEN_OXIDE = 0x0000000000020000; +const uint64_t UOM_GROUP_VOC_OZONE = 0x0000000000040000; +const uint64_t UOM_GROUP_PH = 0x0000000000080000; +const uint64_t UOM_GROUP_PM_CO_CO2_NOX = 0x0000000000100000; +const uint64_t UOM_GROUP_POWER = 0x0000000000200000; // 1045 +const uint64_t UOM_GROUP_RADIATION = 0x0000000000400000; +const uint64_t UOM_GROUP_REACTIVE_POWER = 0x0000000000800000; +const uint64_t UOM_GROUP_SIGNAL_STRENGTH = 0x0000000001000000; +const uint64_t UOM_GROUP_SOUND_PRESSURE = 0x0000000002000000; +const uint64_t UOM_GROUP_SPEED = 0x0000000004000000; // 1050 +const uint64_t UOM_GROUP_TEMPERATURE = 0x0000000008000000; +const uint64_t UOM_GROUP_VOLTAGE_CURRENT = 0x0000000010000000; +const uint64_t UOM_GROUP_VOLUME_WATER_CONS = 0x0000000020000000; +const uint64_t UOM_GROUP_VOLUME_FLOW_RATE = 0x0000000040000000; +const uint64_t UOM_GROUP_WEIGHT = 0x0000000080000000; // 1055 +const uint64_t UOM_GROUP_WIND_DIRECTION = 0x0000000100000000; +const uint64_t UOM_GROUP_VARIOUS_UNITS = 0x0000000200000000; +const uint64_t UOM_GROUP_ALL = std::numeric_limits::max(); +const uint64_t UOM_GROUP_NONE = 0x0; + +# define UOM_dB 72 +# define UOM_dBm 73 +# define UOM_milliVolt 119 +# define UOM_Mbps 97 // Mbit/s +# define UOM_usec 39 +# define UOM_msec 40 +# define UOM_sec 41 +# define UOM_min 42 +# define UOM_hour 43 +# define UOM_day 44 +# define UOM_GHz 53 +# define UOM_MHz 132 +# define UOM_ppm 33 +# define UOM_degC 1 +# define UOM_kB 79 +# define UOM_Byte 78 +# define UOM_percent 4 + +extern const uint16_t unit_of_measure_map[] PROGMEM; +extern const uint16_t unit_of_measure_map_size; + +String toUnitOfMeasureName(const uint32_t unitOfMeasureIndex, + const String & defUoM = EMPTY_STRING); +int getUnitOfMeasureIndex(const String& uomName); + +bool getDefaultUoMforSensorVType(EventStruct *event); +uint16_t getUoMGroupForUoM(const String& uom); +uint16_t getUoMGroupForUoM(const int findUom); +#endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE diff --git a/src/src/Helpers/ESPEasy_checks.cpp b/src/src/Helpers/ESPEasy_checks.cpp index f2ab65935..f7265d487 100644 --- a/src/src/Helpers/ESPEasy_checks.cpp +++ b/src/src/Helpers/ESPEasy_checks.cpp @@ -2,7 +2,7 @@ #include "../../ESPEasy_common.h" -#ifndef BUILD_MINIMAL_OTA +#ifndef LIMIT_BUILD_SIZE #include "../DataStructs/CRCStruct.h" #include "../DataStructs/ControllerSettingsStruct.h" @@ -11,7 +11,7 @@ #include "../DataStructs/ExtraTaskSettingsStruct.h" #include "../DataStructs/FactoryDefaultPref.h" #include "../DataStructs/GpioFactorySettingsStruct.h" -#include "../DataStructs/LogStruct.h" +#include "../DataStructs/LogBuffer.h" #if FEATURE_ESPEASY_P2P #include "../DataStructs/NodeStruct.h" #endif @@ -79,15 +79,14 @@ void check_max_size() { // ******************************************************************************** void run_compiletime_checks() { - #ifndef LIMIT_BUILD_SIZE check_size(); check_size(); check_max_size(); #ifdef ESP32 - constexpr unsigned int SettingsStructSize = (340 + 84 * TASKS_MAX); + constexpr unsigned int SettingsStructSize = (376 + 84 * TASKS_MAX); #endif #ifdef ESP8266 - constexpr unsigned int SettingsStructSize = (316 + 84 * TASKS_MAX); + constexpr unsigned int SettingsStructSize = (344 + 84 * TASKS_MAX); #endif #if FEATURE_CUSTOM_PROVISIONING check_size(); @@ -102,23 +101,13 @@ void run_compiletime_checks() { #endif // if FEATURE_NOTIFIER check_size(); check_max_size(); - #if ESP_IDF_VERSION_MAJOR > 3 + #ifdef ESP32 // ESP_IDF_VERSION_MAJOR > 3 // String class has increased with 4 bytes - check_size(); // Is not stored + check_size(); // Is not stored #else - check_size(); // Is not stored + check_size(); // Is not stored #endif - - // LogStruct is mainly dependent on the number of lines. - // Has to be round up to multiple of 4. - #if ESP_IDF_VERSION_MAJOR > 3 - // String class has increased with 4 bytes - const unsigned int LogStructSize = ((13u + 24 * LOG_STRUCT_MESSAGE_LINES) + 3) & ~3; - #else - const unsigned int LogStructSize = ((13u + 20 * LOG_STRUCT_MESSAGE_LINES) + 3) & ~3; - #endif - check_size(); // Is not stored check_size(); // Is not stored check_size(); #if FEATURE_NOTIFIER @@ -130,7 +119,7 @@ void run_compiletime_checks() { #if FEATURE_CUSTOM_PROVISIONING check_size(); #endif - check_size(); + check_size(); check_size(); check_size(); check_size(); @@ -179,6 +168,10 @@ void run_compiletime_checks() { #endif } + constexpr size_t offset_WireClockStretchLimit = offsetof(SettingsStruct, WireClockStretchLimit); + constexpr size_t offset_ConnectionFailuresThreshold = offsetof(SettingsStruct, ConnectionFailuresThreshold); + static_assert(184 == offset_ConnectionFailuresThreshold, ""); + static_assert(192u == offsetof(SettingsStruct, Protocol), ""); static_assert(195u == offsetof(SettingsStruct, Notification), "CONTROLLER_MAX has changed?"); @@ -197,11 +190,7 @@ void run_compiletime_checks() { // to determine nr of bits in a struct. static_assert(GPIO_DIRECTION_NR_BITS== NR_BITS(static_cast(gpio_direction::gpio_direction_MAX)), "Correct GPIO_DIRECTION_NR_BITS"); - - #endif } - -#ifndef LIMIT_BUILD_SIZE String ReportOffsetErrorInStruct(const String& structname, size_t offset) { String error; if (error.reserve(48 + structname.length())) { @@ -211,7 +200,6 @@ String ReportOffsetErrorInStruct(const String& structname, size_t offset) { } return error; } -#endif /*********************************************************************************************\ * Analyze SettingsStruct and report inconsistencies diff --git a/src/src/Helpers/ESPEasy_checks.h b/src/src/Helpers/ESPEasy_checks.h index 070e36e73..a6a55c51e 100644 --- a/src/src/Helpers/ESPEasy_checks.h +++ b/src/src/Helpers/ESPEasy_checks.h @@ -3,7 +3,7 @@ #include "../../ESPEasy_common.h" -#ifndef BUILD_MINIMAL_OTA +#ifndef LIMIT_BUILD_SIZE #include "../Globals/Plugins.h" void run_compiletime_checks(); diff --git a/src/src/Helpers/ESPEasy_key_value_store_import_export.cpp b/src/src/Helpers/ESPEasy_key_value_store_import_export.cpp new file mode 100644 index 000000000..6fdff263f --- /dev/null +++ b/src/src/Helpers/ESPEasy_key_value_store_import_export.cpp @@ -0,0 +1,191 @@ +#include "../Helpers/ESPEasy_key_value_store_import_export.h" + +#if FEATURE_ESPEASY_KEY_VALUE_STORE +# include "../Helpers/_ESPEasy_key_value_store.h" +# include "../Helpers/KeyValueWriter.h" +# include "../Helpers/StringConverter.h" + + +ESPEasy_key_value_store_import_export::ESPEasy_key_value_store_import_export( + ESPEasy_key_value_store*store) + : _store(store) {} + + +bool ESPEasy_key_value_store_import_export::do_export( + uint32_t key, + KeyValueWriter*writer, + LabelStringFunction fnc) const +{ + if ((_store == nullptr) || (fnc == nullptr) || (writer == nullptr)) { + return false; + } + KVS_StorageType::Enum storageType = KVS_StorageType::Enum::not_set; + + auto label = fnc(key, false, storageType); + + if (!_store->hasKey(storageType, key)) { + return false; + } + + switch (storageType) + { + case KVS_StorageType::Enum::not_set: + case KVS_StorageType::Enum::MAX_Type: + break; + case KVS_StorageType::Enum::binary: + // TODO TD-er: Implement + break; + case KVS_StorageType::Enum::bool_type: + case KVS_StorageType::Enum::bool_true: + case KVS_StorageType::Enum::bool_false: + { + bool value{}; + + if (_store->getValue(key, value)) { + writer->write({ label, value }); + return true; + } + break; + } + case KVS_StorageType::Enum::float_type: + { + float value{}; + + if (_store->getValue(key, value)) { + writer->write({ label, value }); + return true; + } + break; + } + case KVS_StorageType::Enum::double_type: + { + double value{}; + + if (_store->getValue(key, value)) { + writer->write({ label, value }); + return true; + } + break; + } + default: + { + String value; + + if (_store->getValueAsString(key, value)) { + writer->write({ label, value }); + return true; + } + break; + } + } + return false; +} + + +bool getNextKeyValue(String& json, String& key, String& value) +{ + String keyValueStr; + int pos = json.indexOf(','); + if (pos == -1) keyValueStr = json; + else keyValueStr = json.substring(0, pos); + + if (keyValueStr.isEmpty()) { + return false; + } + + // KeyValueStr now contains something like this: + // "key": value + // "key": "value" + + key = parseStringKeepCase(keyValueStr, 1, ':'); + value = parseStringKeepCase(keyValueStr, 2, ':'); + + addLog(LOG_LEVEL_INFO, strformat(F("KVS : kvs: '%s' key:'%s' value:'%s'"), keyValueStr.c_str(), key.c_str(), value.c_str())); + + // Strip found item off + json = json.substring(keyValueStr.length()); + json.trim(); + + if (json.startsWith(",")) { + json = json.substring(1); + } + return true; +} + + +ESPEasy_key_value_store_import_export::ESPEasy_key_value_store_import_export( + ESPEasy_key_value_store*store, const String& json) + : _store(store) +{ + if ((_store != nullptr) && _store->isEmpty()) { + String tmp_json(json); + + tmp_json.trim(); + + if (tmp_json.startsWith("{") && tmp_json.endsWith("}")) { + tmp_json = tmp_json.substring(1, tmp_json.length() - 1); + } + tmp_json.trim(); + + String key_str; + String value; + + while (getNextKeyValue(tmp_json, key_str, value)) { + key_str.toLowerCase(); + _parsedJSON[key_str] = value; + } + } +} + + + + +String ESPEasy_key_value_store_import_export::do_import( + LabelStringFunction fnc, + NextKeyFunction nextKey) +{ + if (_store == nullptr) { + return F("No Store Set"); + } + + if (!_store->isEmpty()) { + return F("Store not empty"); + } + fnc = fnc; + + if ((fnc == nullptr) || (nextKey == nullptr)) { + return F("No decoder functions set"); + } + + int32_t key = nextKey(-1); + + while (key >= 0) { + KVS_StorageType::Enum storageType = KVS_StorageType::Enum::not_set; + + auto label = fnc(key, false, storageType); + + String value; + + if (getParsedJSON(label, value)) { + _store->setValue(storageType, key, value); + } + key = nextKey(key); + } + + _store->dump(); + return EMPTY_STRING; +} + +bool ESPEasy_key_value_store_import_export::getParsedJSON(const String& key, String& value) const +{ + String key_lc = key; + + key_lc.toLowerCase(); + auto it = _parsedJSON.find(key_lc); + + if (it == _parsedJSON.end()) { return false; } + value = it->second; + return true; +} + +#endif // if FEATURE_ESPEASY_KEY_VALUE_STORE diff --git a/src/src/Helpers/ESPEasy_key_value_store_import_export.h b/src/src/Helpers/ESPEasy_key_value_store_import_export.h new file mode 100644 index 000000000..b4187d063 --- /dev/null +++ b/src/src/Helpers/ESPEasy_key_value_store_import_export.h @@ -0,0 +1,50 @@ +#pragma once +#include "../../ESPEasy_common.h" + +#if FEATURE_ESPEASY_KEY_VALUE_STORE +# include "../DataTypes/ESPEasy_key_value_store_StorageType.h" + +# include + +class ESPEasy_key_value_store; +class KeyValueWriter; + +class ESPEasy_key_value_store_import_export +{ +public: + + using LabelStringFunction = const __FlashStringHelper * (*)(uint32_t, bool, KVS_StorageType::Enum&); + + + // When queried with a key of -1, it will return the first key index + // Return next key, or -2 when no next key exists. + using NextKeyFunction = int32_t (*)(int32_t); + + ESPEasy_key_value_store_import_export( + ESPEasy_key_value_store*store); + + bool do_export(uint32_t key, + KeyValueWriter *writer, + LabelStringFunction fnc) const; + + + ESPEasy_key_value_store_import_export( + ESPEasy_key_value_store*store, + const String & json); + + String do_import( + LabelStringFunction fnc, + NextKeyFunction nextKey); + + bool getParsedJSON(const String& key, + String & value) const; + +private: + + std::map_parsedJSON; + + ESPEasy_key_value_store*_store{}; + +}; // class ESPEasy_key_value_store_import_export + +#endif // if FEATURE_ESPEASY_KEY_VALUE_STORE diff --git a/src/src/Helpers/ESPEasy_time.cpp b/src/src/Helpers/ESPEasy_time.cpp index ff8fe940b..9b06b7f54 100644 --- a/src/src/Helpers/ESPEasy_time.cpp +++ b/src/src/Helpers/ESPEasy_time.cpp @@ -12,10 +12,13 @@ #include "../DataTypes/TimeSource.h" #include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../../ESPEasy/net/ESPEasyNetwork.h" #include "../Globals/EventQueue.h" -#include "../Globals/NetworkState.h" +#include "../../ESPEasy/net/Globals/NetworkState.h" +#include "../../ESPEasy/net/DataTypes/NetworkIndex.h" +#include "../../ESPEasy/net/_NWPlugin_Helper.h" + #include "../Globals/Nodes.h" #include "../Globals/RTC.h" #include "../Globals/Settings.h" @@ -203,6 +206,21 @@ uint32_t ESPEasy_time::systemMicros_to_Localtime(const int64_t& systemMicros, ui return time_zone.toLocal(systemMicros_to_Unixtime(systemMicros, unix_time_frac)); } +int64_t ESPEasy_time::internalTimestamp_to_systemMicros(const uint32_t& internalTimestamp, uint32_t internal_to_micros_ratio) const +{ + const uint64_t cur_micros = getMicros64(); + const uint64_t overflow_step = 4294967296ull * internal_to_micros_ratio; + + uint64_t sysMicros = static_cast(internalTimestamp) * internal_to_micros_ratio; + + // Try to get in the range of the current system micros + while ((sysMicros + overflow_step) < cur_micros) { + sysMicros += overflow_step; + } + return sysMicros; + +} + void ESPEasy_time::initTime() { nextSyncTime = 0; @@ -337,6 +355,17 @@ unsigned long ESPEasy_time::now_() { taskData->processTimeSet(externalUnixTime_offset_usec / 1000000.0); } } +#if FEATURE_NETWORK_STATS + // Process for network interfaces too + for (ESPEasy::net::networkIndex_t networkIndex = 0; networkIndex < NETWORK_MAX; networkIndex++) + { + ESPEasy::net::NWPluginData_base *NWdata = ESPEasy::net::getNWPluginData(networkIndex); + + if (NWdata != nullptr) { + NWdata->processTimeSet(externalUnixTime_offset_usec / 1000000.0); + } + } +#endif } } @@ -375,7 +404,7 @@ unsigned long ESPEasy_time::now_() { syncInterval = 3600; } } - +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { String log = F("Time set to "); #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE @@ -394,7 +423,7 @@ unsigned long ESPEasy_time::now_() { } addLogMove(LOG_LEVEL_INFO, log); } - +#endif time_zone.applyTimeZone(unixTime_d); lastSyncTime_ms = millis(); nextSyncTime = getUptime_in_sec() + syncInterval; @@ -415,13 +444,13 @@ unsigned long ESPEasy_time::now_() { calcSunRiseAndSet(timeSynced); if (timeSynced) { - #ifndef BUILD_MINIMAL_OTA +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, strformat( F("Local time: %s"), getDateTimeString('-', ':', ' ').c_str())); } - #endif +#endif { // Notify plugins the time has been set. String dummy; @@ -480,7 +509,7 @@ bool ESPEasy_time::systemTimePresent() const { bool ESPEasy_time::getNtpTime(double& unixTime_d) { - if (!Settings.UseNTP() || !NetworkConnected(10)) { + if (!Settings.UseNTP() || !ESPEasy::net::NetworkConnected()) { return false; } @@ -497,7 +526,7 @@ bool ESPEasy_time::getNtpTime(double& unixTime_d) bool useNTPpool = false; if (Settings.NTPHost[0] != 0) { - resolveHostByName(Settings.NTPHost, timeServerIP); + if (!resolveHostByName(Settings.NTPHost, timeServerIP)) return false; log += Settings.NTPHost; // When single set host fails, retry again in 20 seconds @@ -506,7 +535,7 @@ bool ESPEasy_time::getNtpTime(double& unixTime_d) // Have to do a lookup each time, since the NTP pool always returns another IP const String ntpServerName = strformat( F("%d.pool.ntp.org"), HwRandom(0, 3)); - resolveHostByName(ntpServerName.c_str(), timeServerIP); + if (!resolveHostByName(ntpServerName.c_str(), timeServerIP)) return false; log += ntpServerName; // When pool host fails, retry can be much sooner @@ -668,7 +697,7 @@ bool ESPEasy_time::getNtpTime(double& unixTime_d) )); #endif // ifndef BUILD_NO_DEBUG } - CheckRunningServices(); // FIXME TD-er: Sometimes services can only be started after NTP is successful + ESPEasy::net::CheckRunningServices(); // FIXME TD-er: Sometimes services can only be started after NTP is successful STOP_TIMER(NTP_SUCCESS); return true; } diff --git a/src/src/Helpers/ESPEasy_time.h b/src/src/Helpers/ESPEasy_time.h index d10f219ec..7f5569d07 100644 --- a/src/src/Helpers/ESPEasy_time.h +++ b/src/src/Helpers/ESPEasy_time.h @@ -72,6 +72,10 @@ public: uint32_t systemMicros_to_Localtime(const int64_t& systemMicros, uint32_t & unix_time_frac) const; + // Try to get in the range of the current system micros using given ratio + // Ratio of 1000 reflects millis. Ratio of 20000 reflects 1/50 sec (20 msec) + int64_t internalTimestamp_to_systemMicros(const uint32_t& internalTimestamp, uint32_t internal_to_micros_ratio = 1000u) const; + void initTime(); unsigned long getLocalUnixTime() const; diff --git a/src/src/Helpers/ESPEasy_time_zone.cpp b/src/src/Helpers/ESPEasy_time_zone.cpp index 1963d20cf..b687c74d7 100644 --- a/src/src/Helpers/ESPEasy_time_zone.cpp +++ b/src/src/Helpers/ESPEasy_time_zone.cpp @@ -53,6 +53,7 @@ void ESPEasy_time_zone::setTimeZone(const TimeChangeRule& dstStart, const TimeCh } void ESPEasy_time_zone::logTimeZoneInfo() { +#ifndef BUILD_NO_DEBUG if (!loglevelActiveFor(LOG_LEVEL_INFO)) return; String log = F("Current Time Zone: "); @@ -82,6 +83,7 @@ void ESPEasy_time_zone::logTimeZoneInfo() { log += m_std.offset; log += F(" min"); addLogMove(LOG_LEVEL_INFO, log); +#endif } diff --git a/src/src/Helpers/Hardware.cpp b/src/src/Helpers/Hardware.cpp index b8f41a32e..6763dbb4d 100644 --- a/src/src/Helpers/Hardware.cpp +++ b/src/src/Helpers/Hardware.cpp @@ -7,7 +7,7 @@ #include "../ESPEasyCore/ESPEasy_Log.h" #include "../Globals/Device.h" -#include "../Globals/ESPEasyWiFiEvent.h" +#include "../../ESPEasy/net/Globals/ESPEasyWiFiEvent.h" #include "../Globals/ExtraTaskSettings.h" #include "../Globals/Settings.h" #include "../Globals/Statistics.h" @@ -15,6 +15,7 @@ #include "../Helpers/ESPEasy_FactoryDefault.h" #include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/ESPEasy_UnitOfMeasure.h" #include "../Helpers/FS_Helper.h" #include "../Helpers/Hardware_device_info.h" #include "../Helpers/Hardware_GPIO.h" @@ -214,9 +215,10 @@ void hardwareInit() bool tryInitSPI = true; #if FEATURE_ETHERNET - if ((Settings.NetworkMedium == NetworkMedium_t::Ethernet) && +// FIXME TD-er: Is this still needed? + if ((Settings.NetworkMedium == ESPEasy::net::NetworkMedium_t::Ethernet) && isValid(Settings.ETH_Phy_Type) && - isSPI_EthernetType(Settings.ETH_Phy_Type)) + ESPEasy::net::isSPI_EthernetType(Settings.ETH_Phy_Type)) { #if !ETH_SPI_SUPPORTS_CUSTOM tryInitSPI = false; @@ -253,24 +255,32 @@ void hardwareInit() if (SPI_initialized) { +#ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("INIT : SPI Init (without CS)")); +#endif #if FEATURE_SD if (Settings.Pin_sd_cs >= 0) { if (SD.begin(Settings.Pin_sd_cs)) { +#ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("SD : Init OK")); +#endif } else { SD.end(); +#ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_ERROR, F("SD : Init failed")); +#endif } } #endif // if FEATURE_SD +#ifndef BUILD_NO_DEBUG } else { addLog(LOG_LEVEL_INFO, F("INIT : SPI not enabled")); +#endif } } @@ -304,13 +314,13 @@ void checkResetFactoryPin() { int lastADCvalue = 0; int espeasy_analogRead(int pin) { - if (!WiFiEventData.wifiConnectInProgress) { +// if (!WiFiEventData.wifiConnectInProgress) { #if FEATURE_ADC_VCC lastADCvalue = ESP.getVcc(); #else lastADCvalue = analogRead(A0); #endif // if FEATURE_ADC_VCC - } +// } return lastADCvalue; } @@ -446,11 +456,19 @@ int getCPU_MaxFreqMHz() return 160; #elif CONFIG_IDF_TARGET_ESP32C6 return 160; +#elif CONFIG_IDF_TARGET_ESP32C61 + return 160; +#elif CONFIG_IDF_TARGET_ESP32C5 + return 240; #elif CONFIG_IDF_TARGET_ESP32H2 //IDF-6570 return 96; #elif CONFIG_IDF_TARGET_ESP32P4 + #ifdef CONFIG_ESP32P4_SELECTS_REV_LESS_V3 + return 360; + #else return 400; + #endif #elif CONFIG_IDF_TARGET_ESP32S2 return 240; #elif CONFIG_IDF_TARGET_ESP32S3 @@ -675,6 +693,30 @@ void readBootCause() { case POWER_GLITCH_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<23, power glitch reset digital core and rtc module*/ } +#elif defined(ESP32C5) + switch (rtc_get_reset_reason(0)) { + case NO_MEAN : break; + case POWERON_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<1, Vbat power on reset*/ + case RTC_SW_SYS_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<3, Software reset digital core*/ + case DEEPSLEEP_RESET : lastBootCause = BOOT_CAUSE_DEEP_SLEEP; break; /**<5, Deep Sleep reset digital core*/ +// case SDIO_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<6, Reset by SLC module, reset digital core (hp system)*/ + case TG0WDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<7, Timer Group0 Watch dog reset digital core*/ + case TG1WDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<8, Timer Group1 Watch dog reset digital core*/ + case RTCWDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<9, RTC Watch dog Reset digital core*/ + case TG0WDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<11, Time Group0 reset CPU*/ + case RTC_SW_CPU_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<12, Software reset CPU*/ + case RTCWDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<13, RTC Watch dog Reset CPU*/ + case RTCWDT_BROWN_OUT_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<15, Reset when the vdd voltage is not stable*/ + case RTCWDT_RTC_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<16, RTC Watch dog reset digital core and rtc module*/ + case TG1WDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<17, Time Group1 reset CPU*/ + case SUPER_WDT_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<18, super watchdog reset digital core and rtc module*/ + case EFUSE_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<20, efuse reset digital core*/ + case USB_UART_CHIP_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<21, usb uart reset digital core */ + case USB_JTAG_CHIP_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<22, usb jtag reset digital core */ + case JTAG_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<24, jtag reset CPU*/ + } + + #elif defined(ESP32C6) switch (rtc_get_reset_reason(0)) { case NO_MEAN : break; @@ -698,6 +740,55 @@ void readBootCause() { case JTAG_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<24, jtag reset CPU*/ } +#elif defined(ESP32C61) + switch (rtc_get_reset_reason(0)) { + case NO_MEAN : break; + case POWERON_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<1, Vbat power on reset*/ + case RTC_SW_SYS_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<3, Software reset digital core*/ + case DEEPSLEEP_RESET : lastBootCause = BOOT_CAUSE_DEEP_SLEEP; break; /**<5, Deep Sleep reset digital core*/ + + case RTC_PWR_GLITCH_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; + case CPU_LOCKUP_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; + + case TG0WDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<7, Timer Group0 Watch dog reset digital core*/ + case TG1WDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<8, Timer Group1 Watch dog reset digital core*/ + case RTCWDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<9, RTC Watch dog Reset digital core*/ + case TG0WDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<11, Time Group0 reset CPU*/ + case RTC_SW_CPU_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<12, Software reset CPU*/ + case RTCWDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<13, RTC Watch dog Reset CPU*/ + case RTCWDT_BROWN_OUT_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<15, Reset when the vdd voltage is not stable*/ + case RTCWDT_RTC_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<16, RTC Watch dog reset digital core and rtc module*/ + case TG1WDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<17, Time Group1 reset CPU*/ + case SUPER_WDT_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<18, super watchdog reset digital core and rtc module*/ + case EFUSE_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<20, efuse reset digital core*/ + case USB_UART_CHIP_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<21, usb uart reset digital core */ + case USB_JTAG_CHIP_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<22, usb jtag reset digital core */ + case JTAG_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<24, jtag reset CPU*/ + } + +#elif defined(ESP32P4) + switch (rtc_get_reset_reason(0)) { + case NO_MEAN : break; + case POWERON_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<1, Vbat power on reset*/ + case SW_SYS_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<3, Software reset digital core*/ + case PMU_SYS_PWR_DOWN_RESET : lastBootCause = BOOT_CAUSE_DEEP_SLEEP; break; /**<5, PMU HP system power down reset*/ + case HP_SYS_HP_WDT_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<7, HP system reset from HP watchdog*/ + case HP_SYS_LP_WDT_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<9, HP system reset from LP watchdog*/ + case HP_CORE_HP_WDT_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<11, HP core reset from HP watchdog*/ + case SW_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<12, software reset cpu*/ + case HP_CORE_LP_WDT_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<13, HP core reset from LP watchdog*/ + case BROWN_OUT_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<15, Reset when the vdd voltage is not stable*/ + case CHIP_LP_WDT_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<16, LP watchdog chip reset*/ + case SUPER_WDT_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<18, super watchdog reset*/ + case GLITCH_RTC_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<19, glitch reset*/ + case EFUSE_CRC_ERR_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<20, efuse ecc error reset*/ + case CHIP_USB_JTAG_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<22, HP usb jtag chip reset*/ + case CHIP_USB_UART_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<23, HP usb uart chip reset*/ + case JTAG_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<24, jtag reset*/ + case CPU_LOCKUP_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<26, cpu lockup reset*/ + } + + # elif defined(ESP32_CLASSIC) switch (rtc_get_reset_reason(0)) { case NO_MEAN : break; @@ -744,7 +835,7 @@ const __FlashStringHelper* getDeviceModelBrandString(DeviceModel model) { case DeviceModel::DeviceModel_Sonoff_POWr2: return F("Sonoff"); case DeviceModel::DeviceModel_Shelly1: case DeviceModel::DeviceModel_ShellyPLUG_S: return F("Shelly"); -# if CONFIG_ETH_USE_ESP32_EMAC +# if CONFIG_ETH_USE_ESP32_EMAC && FEATURE_ETHERNET case DeviceModel::DeviceModel_Olimex_ESP32_PoE: case DeviceModel::DeviceModel_Olimex_ESP32_EVB: case DeviceModel::DeviceModel_Olimex_ESP32_GATEWAY: @@ -797,13 +888,13 @@ const __FlashStringHelper* getDeviceModelTypeString(DeviceModel model) case DeviceModel::DeviceModel_ShellyPLUG_S: return F("default"); #endif // if defined(ESP8266) && !defined(LIMIT_BUILD_SIZE) -#if CONFIG_ETH_USE_ESP32_EMAC +#if CONFIG_ETH_USE_ESP32_EMAC && FEATURE_ETHERNET case DeviceModel::DeviceModel_Olimex_ESP32_PoE: return F(" ESP32-PoE"); case DeviceModel::DeviceModel_Olimex_ESP32_EVB: return F(" ESP32-EVB"); case DeviceModel::DeviceModel_Olimex_ESP32_GATEWAY: return F(" ESP32-GATEWAY"); case DeviceModel::DeviceModel_wESP32: break; case DeviceModel::DeviceModel_WT32_ETH01: return F(" add-on"); -#endif // if CONFIG_ETH_USE_ESP32_EMAC +#endif // if CONFIG_ETH_USE_ESP32_EMAC && FEATURE_ETHERNET case DeviceModel::DeviceModel_default: case DeviceModel::DeviceModel_MAX: return F("default"); @@ -856,7 +947,7 @@ bool modelMatchingFlashSize(DeviceModel model) { #endif // ifdef ESP8266 // These Olimex boards all have Ethernet -#if CONFIG_ETH_USE_ESP32_EMAC +#if CONFIG_ETH_USE_ESP32_EMAC && FEATURE_ETHERNET case DeviceModel::DeviceModel_Olimex_ESP32_PoE: case DeviceModel::DeviceModel_Olimex_ESP32_EVB: case DeviceModel::DeviceModel_Olimex_ESP32_GATEWAY: @@ -867,7 +958,7 @@ bool modelMatchingFlashSize(DeviceModel model) { # else // if defined(ESP32_CLASSIC) && FEATURE_ETHERNET return false; # endif // if defined(ESP32_CLASSIC) && FEATURE_ETHERNET -#endif // if CONFIG_ETH_USE_ESP32_EMAC +#endif // if CONFIG_ETH_USE_ESP32_EMAC && FEATURE_ETHERNET case DeviceModel::DeviceModel_default: case DeviceModel::DeviceModel_MAX: return true; diff --git a/src/src/Helpers/Hardware_ADC.cpp b/src/src/Helpers/Hardware_ADC.cpp index e96a174e0..d4c905fc9 100644 --- a/src/src/Helpers/Hardware_ADC.cpp +++ b/src/src/Helpers/Hardware_ADC.cpp @@ -6,7 +6,7 @@ #endif // ifdef ESP32 #ifdef ESP8266 -#include "../Globals/ESPEasyWiFiEvent.h" +#include "../../ESPEasy/net/Globals/ESPEasyWiFiEvent.h" bool Hardware_ADC_t::init() { @@ -15,13 +15,13 @@ bool Hardware_ADC_t::init() { int Hardware_ADC_t::read() { - if (!WiFiEventData.wifiConnectInProgress) { +// if (!WiFiEventData.wifiConnectInProgress) { # if FEATURE_ADC_VCC _lastADCvalue = ESP.getVcc(); # else // if FEATURE_ADC_VCC _lastADCvalue = analogRead(A0); # endif // if FEATURE_ADC_VCC - } +// } return _lastADCvalue; } diff --git a/src/src/Helpers/Hardware_ADC_cali.h b/src/src/Helpers/Hardware_ADC_cali.h index 824ad553d..387405fd6 100644 --- a/src/src/Helpers/Hardware_ADC_cali.h +++ b/src/src/Helpers/Hardware_ADC_cali.h @@ -18,7 +18,7 @@ # include # endif // if ESP_IDF_VERSION_MAJOR >= 5 -# if CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 +# if CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32C61 /** * On ESP32C3, ADC2 is no longer supported, due to its HW limitation. * Search for errata on espressif website for more details. diff --git a/src/src/Helpers/Hardware_GPIO.cpp b/src/src/Helpers/Hardware_GPIO.cpp index d782884af..fb83a1bfe 100644 --- a/src/src/Helpers/Hardware_GPIO.cpp +++ b/src/src/Helpers/Hardware_GPIO.cpp @@ -4,6 +4,21 @@ #include "../Globals/Settings.h" #include "../Helpers/Hardware_defines.h" #include "../Helpers/Hardware_device_info.h" +#include "../Helpers/StringConverter.h" + +#ifdef ESP32 +# include +# include +# include + +// # include + # include + +// #include + +# include +#endif // ifdef ESP32 + // ******************************************************************************** // Get info of a specific GPIO pin @@ -24,3 +39,45 @@ bool isSerialConsolePin(int gpio) { return false; #endif } + + +#ifdef ESP32 +bool getPeriman_gpio_info(int gpio_pin, + peripheral_bus_type_t& bus_type, + String& gpio_str, + String& typeName,int& bus_number, int& bus_channel) +{ + if (!perimanPinIsValid(gpio_pin)) { + return false; // invalid pin + } + bus_type = perimanGetPinBusType(gpio_pin); + + if (bus_type == ESP32_BUS_TYPE_INIT) { + return false; // unused pin + } + +# if defined(BOARD_HAS_PIN_REMAP) + int dpin = gpioNumberToDigitalPin(gpio_pin); + + if (dpin < 0) { + return false; // pin is not exported + } else { + gpio_str = strformat(F("D%-3d|%4u"), dpin, gpio_pin); + } +# else // if defined(BOARD_HAS_PIN_REMAP) + gpio_str = strformat(F("%4u"), gpio_pin); +# endif // if defined(BOARD_HAS_PIN_REMAP) + const char *extra_type = perimanGetPinBusExtraType(gpio_pin); + + if (extra_type) { + typeName = strformat(F("%s [%s]"), perimanGetTypeName(bus_type), extra_type); + } else { + typeName = perimanGetTypeName(bus_type); + } + bus_number = perimanGetPinBusNum(gpio_pin); + bus_channel = perimanGetPinBusChannel(gpio_pin); + return true; +} + + +#endif \ No newline at end of file diff --git a/src/src/Helpers/Hardware_GPIO.h b/src/src/Helpers/Hardware_GPIO.h index dd97c5636..64417d583 100644 --- a/src/src/Helpers/Hardware_GPIO.h +++ b/src/src/Helpers/Hardware_GPIO.h @@ -5,7 +5,8 @@ #include "../../ESPEasy_common.h" #ifdef ESP32 -#include "soc/soc_caps.h" +# include "soc/soc_caps.h" +# include #endif // ******************************************************************************** @@ -18,6 +19,11 @@ bool getGpioInfo(int gpio, bool& output, bool& warning); + +// For ESP32, see: https://github.com/espressif/arduino-esp32/blob/9d84c78cf2d44911639530e54a9dbb9ee9164f6c/cores/esp32/esp32-hal.h#L64-L75 +// BOOT_PIN is defined for all ESP32 chips. +bool isBootModePin(int gpio); + bool isBootStrapPin(int gpio); bool getGpioPullResistor(int gpio, @@ -49,6 +55,11 @@ bool getDAC_gpio_info(int gpio_pin, int& dac); #endif +bool getPeriman_gpio_info(int gpio_pin, + peripheral_bus_type_t& bus_type, + String& gpio_str, + String& typeName,int& bus_number, int& bus_channel); + #endif // ifdef ESP32 diff --git a/src/src/Helpers/Hardware_GPIO_ESP32.cpp b/src/src/Helpers/Hardware_GPIO_ESP32.cpp index c7a42abec..809d10e36 100644 --- a/src/src/Helpers/Hardware_GPIO_ESP32.cpp +++ b/src/src/Helpers/Hardware_GPIO_ESP32.cpp @@ -48,7 +48,7 @@ bool getGpioInfo(int gpio, int& pinnr, bool& input, bool& output, bool& warning) # if FEATURE_ETHERNET // Check pins used for RMII Ethernet PHY - if (NetworkMedium_t::Ethernet == Settings.NetworkMedium) { + if (ESPEasy::net::NetworkMedium_t::Ethernet == Settings.NetworkMedium) { switch (gpio) { case 0: case 21: @@ -82,6 +82,12 @@ bool getGpioInfo(int gpio, int& pinnr, bool& input, bool& output, bool& warning) return (input || output); } +bool isBootModePin(int gpio) +{ + return gpio == 0; +} + + bool isBootStrapPin(int gpio) { // GPIO 0 & 2 can't be used as an input. State during boot is dependent on boot mode. diff --git a/src/src/Helpers/Hardware_GPIO_ESP32C2.cpp b/src/src/Helpers/Hardware_GPIO_ESP32C2.cpp index 609839052..a5d525d5f 100644 --- a/src/src/Helpers/Hardware_GPIO_ESP32C2.cpp +++ b/src/src/Helpers/Hardware_GPIO_ESP32C2.cpp @@ -36,6 +36,11 @@ bool getGpioInfo(int gpio, int& pinnr, bool& input, bool& output, bool& warning) return (input || output); } +bool isBootModePin(int gpio) +{ + return gpio == 9; +} + bool isBootStrapPin(int gpio) { if (gpio == 8) { diff --git a/src/src/Helpers/Hardware_GPIO_ESP32C3.cpp b/src/src/Helpers/Hardware_GPIO_ESP32C3.cpp index fe3538705..b7e90b3f4 100644 --- a/src/src/Helpers/Hardware_GPIO_ESP32C3.cpp +++ b/src/src/Helpers/Hardware_GPIO_ESP32C3.cpp @@ -73,6 +73,11 @@ bool getGpioInfo(int gpio, int& pinnr, bool& input, bool& output, bool& warning) return (input || output); } +bool isBootModePin(int gpio) +{ + return gpio == 9; +} + bool isBootStrapPin(int gpio) { if (gpio == 2) { diff --git a/src/src/Helpers/Hardware_GPIO_ESP32C5.cpp b/src/src/Helpers/Hardware_GPIO_ESP32C5.cpp new file mode 100644 index 000000000..6f3e7218a --- /dev/null +++ b/src/src/Helpers/Hardware_GPIO_ESP32C5.cpp @@ -0,0 +1,157 @@ +#include "../Helpers/Hardware_GPIO.h" + +#ifdef ESP32C5 +# include +# include + +# include "../Helpers/Hardware_device_info.h" + + + +// ******************************************************************************** +// Get info of a specific GPIO pin +// ******************************************************************************** + +bool getGpioInfo(int gpio, int& pinnr, bool& input, bool& output, bool& warning) { + pinnr = -1; // ESP32 does not label the pins, they just use the GPIO number. + + input = GPIO_IS_VALID_GPIO(gpio); + output = GPIO_IS_VALID_OUTPUT_GPIO(gpio); + warning = isBootStrapPin(gpio); + + if (!(GPIO_IS_VALID_GPIO(gpio))) { return false; } + + + // FIXME TD-er: Implement for ESP32-C5 + // See: + // - https://docs.espressif.com/projects/esp-idf/en/v5.0/esp32c5/hw-reference/esp32c3/user-guide-devkitm-1.html + // - https://docs.espressif.com/projects/esp-idf/en/latest/esp32c5/api-reference/peripherals/gpio.html + // Datasheet: https://www.espressif.com/sites/default/files/documentation/esp32-c3_datasheet_en.pdf + + if (isFlashInterfacePin_ESPEasy(gpio) || isPSRAMInterfacePin(gpio)) { + if (getChipFeatures().embeddedFlash) { + // Connected to the integrated SPI flash. + input = false; + output = false; + } else { + // See: https://www.letscontrolit.com/forum/viewtopic.php?p=71880#p71874 + if ((gpio == MSPI_IOMUX_PIN_NUM_HD) || (gpio == MSPI_IOMUX_PIN_NUM_WP)) { + if ((ESP.getFlashChipMode() != FM_DOUT) && + (ESP.getFlashChipMode() != FM_DIO)) { + input = false; + output = false; + } + } + } + warning = true; + } + + if ((gpio == PIN_USB_D_MIN) || (gpio == PIN_USB_D_PLUS)) { + // USB OTG and USB Serial/JTAG function. USB signal is a differential + // signal transmitted over a pair of D+ and D- wires. + warning = true; + } + + return (input || output); +} + +bool isBootModePin(int gpio) +{ + return gpio == 28; +} + +bool isBootStrapPin(int gpio) +{ + // Boot mode | GPIO26 | GPIO27 | GPIO28 + // + // SPI Boot | Any | Any | 1 + // Joint Download Boot 0 | Any | 1 | 0 + // Joint Download Boot 1 | 0 | 0 | 0 +/* + Joint Download Boot 0 mode supports the following download methods: + - USB-Serial-JTAG Download Boot + - UART Download Boot + - SPI Slave Download Boot (chip revision v0.1 only) + + Joint Download Boot 1 mode supports the following download methods: + - UART Download Boot + - SDIO Download Boot +*/ + if (gpio == 26 || gpio == 27 || gpio == 28) { + // Strapping pin setting boot mode + return true; + } + + + if (gpio == 7) { + // Strapping pin JTAG signal source + return true; + } + + // Ignoring strapping pins GPIO-2, -3, 25 + // as they should never cause issues for users. + return false; +} + +bool getGpioPullResistor(int gpio, bool& hasPullUp, bool& hasPullDown) { + hasPullDown = false; + hasPullUp = false; + + if (validGpio(gpio)) { + hasPullUp = true; + hasPullDown = true; + } + return true; +} + +bool validGpio(int gpio) { + if (!GPIO_IS_VALID_GPIO(gpio)) { return false; } + + int pinnr; + bool input; + bool output; + bool warning; + + return getGpioInfo(gpio, pinnr, input, output, warning); +} + +// Get ADC related info for a given GPIO pin +// @param gpio_pin GPIO pin number +// @param adc Number of ADC unit (0 == Hall effect) +// @param ch Channel number on ADC unit +// @param t index of touch pad ID +bool getADC_gpio_info(int gpio_pin, int& adc, int& ch, int& t) +{ + adc = -1; + ch = -1; + t = -1; + + // GPIO 1 ... 6 -> Channel 0 ... 5 + if ((gpio_pin > 0) && (gpio_pin <= 6)) { + adc = 1; + ch = gpio_pin - 1; + return true; + } + return false; +} + +#if SOC_TOUCH_SENSOR_SUPPORTED +int touchPinToGpio(int touch_pin) +{ + // No touch pin support + return -1; +} +#endif + +#if SOC_DAC_SUPPORTED +// Get DAC related info for a given GPIO pin +// @param gpio_pin GPIO pin number +// @param dac Number of DAC unit +bool getDAC_gpio_info(int gpio_pin, int& dac) +{ + // ESP32-C5, ESP32-S3, ESP32-C2, ESP32-C6 and ESP32-H2 don't have a DAC onboard + return false; +} +#endif + +#endif // ifdef ESP32C5 diff --git a/src/src/Helpers/Hardware_GPIO_ESP32C6.cpp b/src/src/Helpers/Hardware_GPIO_ESP32C6.cpp index b559912c4..bf5a53456 100644 --- a/src/src/Helpers/Hardware_GPIO_ESP32C6.cpp +++ b/src/src/Helpers/Hardware_GPIO_ESP32C6.cpp @@ -42,6 +42,11 @@ bool getGpioInfo(int gpio, int& pinnr, bool& input, bool& output, bool& warning) return (input || output); } +bool isBootModePin(int gpio) +{ + return gpio == 9; +} + bool isBootStrapPin(int gpio) { // See: https://www.espressif.com/sites/default/files/documentation/esp32-c6_datasheet_en.pdf @@ -58,12 +63,15 @@ bool isBootStrapPin(int gpio) return true; } + // Ignoring SDIO stapping pins as this will not be used in typical use cases where ESPEasy is used. + /* if (gpio == 4 || gpio == 5) { // SDIO Sampling and Driving Clock Edge // MTMS = GPIO-4 // MTDI = Gpio-5 return true; } + */ if (gpio == 15) { // JTAG signal source diff --git a/src/src/Helpers/Hardware_GPIO_ESP32C61.cpp b/src/src/Helpers/Hardware_GPIO_ESP32C61.cpp new file mode 100644 index 000000000..aa2be5b89 --- /dev/null +++ b/src/src/Helpers/Hardware_GPIO_ESP32C61.cpp @@ -0,0 +1,145 @@ +#include "../Helpers/Hardware_GPIO.h" + +#ifdef ESP32C61 +# include + +# include "../Helpers/Hardware_device_info.h" + +// ******************************************************************************** +// Get info of a specific GPIO pin +// ******************************************************************************** + +bool getGpioInfo(int gpio, int& pinnr, bool& input, bool& output, bool& warning) { + pinnr = -1; // ESP32 does not label the pins, they just use the GPIO number. + + input = GPIO_IS_VALID_GPIO(gpio); + output = GPIO_IS_VALID_OUTPUT_GPIO(gpio); + warning = isBootStrapPin(gpio); + + if (!(GPIO_IS_VALID_GPIO(gpio))) { return false; } + + if (gpio == 27) { + // By default VDD_SPI is the power supply pin for embedded flash or external flash. It can only be used as GPIO + // only when the chip is connected to an external flash, and this flash is powered by an external power supply + input = false; + output = false; + warning = true; + } + + if (isFlashInterfacePin_ESPEasy(gpio)) { + // Connected to the integrated SPI flash. + input = false; + output = false; + warning = true; + } + + if ((gpio == PIN_USB_D_MIN) || (gpio == PIN_USB_D_PLUS)) { + // USB OTG and USB Serial/JTAG function. USB signal is a differential + // signal transmitted over a pair of D+ and D- wires. + warning = true; + } + + return (input || output); +} + +bool isBootModePin(int gpio) +{ + return gpio == 9; +} + +bool isBootStrapPin(int gpio) +{ + // See: https://www.espressif.com/sites/default/files/documentation/esp32-c6_datasheet_en.pdf + // Chapter 3 - Boot Configurations + if (gpio == 8) { + // Strapping pin which must be high during flashing + // ROM message printing + return true; + } + + if (gpio == 9) { + // Strapping pin to force download mode (like GPIO-0 on ESP8266/ESP32-classic) + // internal Weak pull-up, must be pulled down to enter download boot mode. + return true; + } + + // Ignoring SDIO stapping pins as this will not be used in typical use cases where ESPEasy is used. + /* + if (gpio == 4 || gpio == 5) { + // SDIO Sampling and Driving Clock Edge + // MTMS = GPIO-4 + // MTDI = Gpio-5 + return true; + } + */ + + if (gpio == 15) { + // JTAG signal source + return true; + } + + return false; +} + +bool getGpioPullResistor(int gpio, bool& hasPullUp, bool& hasPullDown) { + hasPullDown = false; + hasPullUp = false; + + if (validGpio(gpio)) { + hasPullUp = true; + hasPullDown = true; + } + + return true; +} + +bool validGpio(int gpio) { + if (!GPIO_IS_VALID_GPIO(gpio)) { return false; } + + int pinnr; + bool input; + bool output; + bool warning; + + return getGpioInfo(gpio, pinnr, input, output, warning); +} + +// Get ADC related info for a given GPIO pin +// @param gpio_pin GPIO pin number +// @param adc Number of ADC unit (0 == Hall effect) +// @param ch Channel number on ADC unit +// @param t index of touch pad ID +bool getADC_gpio_info(int gpio_pin, int& adc, int& ch, int& t) +{ + adc = -1; + ch = -1; + t = -1; + + if ((gpio_pin >= 0) && (gpio_pin <= 6)) { + adc = 1; + ch = gpio_pin; + return true; + } + return false; +} + +#if SOC_TOUCH_SENSOR_SUPPORTED +int touchPinToGpio(int touch_pin) +{ + // No touch pin support + return -1; +} +#endif + +#if SOC_DAC_SUPPORTED +// Get DAC related info for a given GPIO pin +// @param gpio_pin GPIO pin number +// @param dac Number of DAC unit +bool getDAC_gpio_info(int gpio_pin, int& dac) +{ + // ESP32-C3, ESP32-S3, ESP32-C2, ESP32-C6 and ESP32-H2 don't have a DAC onboard + return false; +} +#endif + +#endif // ifdef ESP32C61 diff --git a/src/src/Helpers/Hardware_GPIO_ESP32P4.cpp b/src/src/Helpers/Hardware_GPIO_ESP32P4.cpp index ebfe55994..4fa954dae 100644 --- a/src/src/Helpers/Hardware_GPIO_ESP32P4.cpp +++ b/src/src/Helpers/Hardware_GPIO_ESP32P4.cpp @@ -6,6 +6,8 @@ # include "../Globals/Settings.h" # include "../Helpers/Hardware_device_info.h" +#include + // ******************************************************************************** // Get info of a specific GPIO pin // ******************************************************************************** @@ -24,33 +26,46 @@ bool getGpioInfo(int gpio, int& pinnr, bool& input, bool& output, bool& warning) # if FEATURE_ETHERNET // Check pins used for RMII Ethernet PHY - if (NetworkMedium_t::Ethernet == Settings.NetworkMedium) { - switch (gpio) { - case 28: // RMII_RXDV - case 29: // RMII_RXD0 - case 30: // RMII_RXD1 - case 34: // RMII_TXD0 - case 35: // RMII_TXD1 - case 49: // RMII_TXEN - case 50: // RMII_CLK - case 51: // PHY_RSTN - case 52: // MDIO - warning = true; - break; - } - - - // FIXME TD-er: Must we also check for pins used for MDC/MDIO and Eth PHY power? + if (Settings.isEthernetPin(gpio) || Settings.isEthernetPinOptional(gpio)) + { + warning = true; } # endif // if FEATURE_ETHERNET +#ifdef BOARD_HAS_SDIO_ESP_HOSTED + // TODO TD-er: Make this configurable as we can set this via WiFi.setPins + switch (gpio) { + case BOARD_SDIO_ESP_HOSTED_CLK : + case BOARD_SDIO_ESP_HOSTED_CMD : + case BOARD_SDIO_ESP_HOSTED_D0 : + case BOARD_SDIO_ESP_HOSTED_D1 : + case BOARD_SDIO_ESP_HOSTED_D2 : + case BOARD_SDIO_ESP_HOSTED_D3 : + case BOARD_SDIO_ESP_HOSTED_RESET : + { + warning = true; +// if (gpio != BOARD_SDIO_ESP_HOSTED_RESET) { return false; } + break; + } + } +#endif + return (input || output); } +bool isBootModePin(int gpio) +{ + return gpio == 35; +} + + bool isBootStrapPin(int gpio) { - - return false; + // Boot mode | GPIO35 | GPIO36 | GPIO37 | GPIO38 + // + // SPI Boot | 1 | Any | Any | Any + // Joint Download Boot | 0 | 1 | Any | Any + return gpio >= 35 && gpio <= 38; } bool getGpioPullResistor(int gpio, bool& hasPullUp, bool& hasPullDown) { @@ -97,20 +112,20 @@ bool getADC_gpio_info(int gpio_pin, int& adc, int& ch, int& t) t = -1; switch (gpio_pin) { - case 16: adc = 1; ch = 0; break; - case 17: adc = 1; ch = 1; break; - case 18: adc = 1; ch = 2; break; - case 19: adc = 1; ch = 3; break; - case 20: adc = 1; ch = 4; break; - case 21: adc = 1; ch = 5; break; - case 22: adc = 1; ch = 6; break; - case 23: adc = 1; ch = 7; break; - case 49: adc = 2; ch = 0; break; - case 50: adc = 2; ch = 1; break; - case 51: adc = 2; ch = 2; break; - case 52: adc = 2; ch = 3; break; - case 53: adc = 2; ch = 4; break; - case 54: adc = 2; ch = 5; break; + case A0: adc = 1; ch = 0; break; + case A1: adc = 1; ch = 1; break; + case A2: adc = 1; ch = 2; break; + case A3: adc = 1; ch = 3; break; + case A4: adc = 1; ch = 4; break; + case A5: adc = 1; ch = 5; break; + case A6: adc = 1; ch = 6; break; + case A7: adc = 1; ch = 7; break; + case A8: adc = 2; ch = 0; break; + case A9: adc = 2; ch = 1; break; + case A10: adc = 2; ch = 2; break; + case A11: adc = 2; ch = 3; break; + case A12: adc = 2; ch = 4; break; + case A13: adc = 2; ch = 5; break; default: return false; } diff --git a/src/src/Helpers/Hardware_GPIO_ESP32S2.cpp b/src/src/Helpers/Hardware_GPIO_ESP32S2.cpp index 4ffe6ab2c..c7337b49b 100644 --- a/src/src/Helpers/Hardware_GPIO_ESP32S2.cpp +++ b/src/src/Helpers/Hardware_GPIO_ESP32S2.cpp @@ -45,6 +45,12 @@ bool getGpioInfo(int gpio, int& pinnr, bool& input, bool& output, bool& warning) return (input || output); } +bool isBootModePin(int gpio) +{ + return gpio == 0; +} + + bool isBootStrapPin(int gpio) { if (gpio == 45) { diff --git a/src/src/Helpers/Hardware_GPIO_ESP32S3.cpp b/src/src/Helpers/Hardware_GPIO_ESP32S3.cpp index be747f1f9..1f57ff3ce 100644 --- a/src/src/Helpers/Hardware_GPIO_ESP32S3.cpp +++ b/src/src/Helpers/Hardware_GPIO_ESP32S3.cpp @@ -46,6 +46,11 @@ bool getGpioInfo(int gpio, int& pinnr, bool& input, bool& output, bool& warning) return (input || output); } +bool isBootModePin(int gpio) +{ + return gpio == 0; +} + bool isBootStrapPin(int gpio) { if (gpio == 45) { diff --git a/src/src/Helpers/Hardware_GPIO_ESP8266.cpp b/src/src/Helpers/Hardware_GPIO_ESP8266.cpp index 917602466..17a566789 100644 --- a/src/src/Helpers/Hardware_GPIO_ESP8266.cpp +++ b/src/src/Helpers/Hardware_GPIO_ESP8266.cpp @@ -66,6 +66,11 @@ bool getGpioInfo(int gpio, int& pinnr, bool& input, bool& output, bool& warning) return input || output; } +bool isBootModePin(int gpio) +{ + return gpio == 0; +} + bool isBootStrapPin(int gpio) { return (gpio == 0 || gpio == 2 || gpio == 15); diff --git a/src/src/Helpers/Hardware_I2C.cpp b/src/src/Helpers/Hardware_I2C.cpp index 733ecb444..b4c210c6b 100644 --- a/src/src/Helpers/Hardware_I2C.cpp +++ b/src/src/Helpers/Hardware_I2C.cpp @@ -32,7 +32,7 @@ void initI2C() { #endif // if !FEATURE_I2C_MULTIPLE { if (Settings.isI2CEnabled(i2cBus)) { - #ifndef BUILD_MINIMAL_OTA + #ifndef LIMIT_BUILD_SIZE #if !FEATURE_I2C_MULTIPLE addLog(LOG_LEVEL_INFO, F("INIT : I2C Bus")); #else // if !FEATURE_I2C_MULTIPLE diff --git a/src/src/Helpers/Hardware_defines.h b/src/src/Helpers/Hardware_defines.h index ff80204fc..128db88b2 100644 --- a/src/src/Helpers/Hardware_defines.h +++ b/src/src/Helpers/Hardware_defines.h @@ -59,7 +59,8 @@ # define WIFI_SENSITIVITY_11b -88 # define WIFI_SENSITIVITY_54g -76 # define WIFI_SENSITIVITY_n -72 -#elif defined(ESP32C2) || defined(ESP32C3) || defined(ESP32C6) +#elif defined(ESP32C2) || defined(ESP32C3) || defined(ESP32C5) || defined(ESP32C6) || defined(ESP32C61) || defined(ESP32P4) +// TODO TD-er: P4 does not have WiFi, but C6 used on those boards has. What to do here? # define MAX_TX_PWR_DBM_11b 21.0f # define MAX_TX_PWR_DBM_54g 19.0f # define MAX_TX_PWR_DBM_n 18.5f @@ -85,6 +86,10 @@ static_assert(false, "Implement processor architecture"); # define SOC_RX0 44 # elif CONFIG_IDF_TARGET_ESP32C6 # define SOC_RX0 17 +# elif CONFIG_IDF_TARGET_ESP32C61 +# define SOC_RX0 17 +# elif CONFIG_IDF_TARGET_ESP32C5 +# define SOC_RX0 12 # elif CONFIG_IDF_TARGET_ESP32C3 # define SOC_RX0 20 # elif CONFIG_IDF_TARGET_ESP32C2 @@ -101,6 +106,10 @@ static_assert(false, "Implement processor architecture"); # define SOC_TX0 43 # elif CONFIG_IDF_TARGET_ESP32C6 # define SOC_TX0 16 +# elif CONFIG_IDF_TARGET_ESP32C61 +# define SOC_TX0 16 +# elif CONFIG_IDF_TARGET_ESP32C5 +# define SOC_TX0 11 # elif CONFIG_IDF_TARGET_ESP32C3 # define SOC_TX0 21 # elif CONFIG_IDF_TARGET_ESP32C2 @@ -115,9 +124,18 @@ static_assert(false, "Implement processor architecture"); #if defined(ESP32S2) || defined(ESP32S3) #define PIN_USB_D_MIN 19 #endif +#ifdef ESP32P4 +#define PIN_USB_D_MIN 25 +#endif #ifdef ESP32C6 #define PIN_USB_D_MIN 12 #endif +#ifdef ESP32C61 +#define PIN_USB_D_MIN 12 // TODO TD-er: Check +#endif +#ifdef ESP32C5 +#define PIN_USB_D_MIN 13 +#endif #ifdef ESP32C3 #define PIN_USB_D_MIN 18 #endif @@ -128,9 +146,18 @@ static_assert(false, "Implement processor architecture"); #if defined(ESP32S2) || defined(ESP32S3) #define PIN_USB_D_PLUS 20 #endif +#ifdef ESP32P4 +#define PIN_USB_D_PLUS 24 +#endif #ifdef ESP32C6 #define PIN_USB_D_PLUS 13 #endif +#ifdef ESP32C61 +#define PIN_USB_D_PLUS 13 // TODO TD-er: Check +#endif +#ifdef ESP32C5 +#define PIN_USB_D_PLUS 14 +#endif #ifdef ESP32C3 #define PIN_USB_D_PLUS 19 #endif diff --git a/src/src/Helpers/Hardware_device_info.cpp b/src/src/Helpers/Hardware_device_info.cpp index ce69b6eb1..527c8ec2e 100644 --- a/src/src/Helpers/Hardware_device_info.cpp +++ b/src/src/Helpers/Hardware_device_info.cpp @@ -113,6 +113,9 @@ uint32_t getFlashChipId() { if (flashChipId == 0) { #ifdef ESP32 + #ifdef ESP32P4 + // TODO TD-er: Implement + #else uint32_t tmp = g_rom_flashchip.device_id; for (int i = 0; i < 3; ++i) { @@ -122,6 +125,7 @@ uint32_t getFlashChipId() { } // esp_flash_read_id(nullptr, &flashChipId); + #endif #elif defined(ESP8266) flashChipId = ESP.getFlashChipId(); #endif // ifdef ESP32 @@ -135,7 +139,12 @@ uint32_t getFlashRealSizeInBytes() { if (res == 0) { #if defined(ESP32) + #ifdef ESP32P4 + // TODO TD-er: Implement + res = 1 << 24; + #else res = (1 << ((getFlashChipId() >> 16) & 0xFF)); + #endif #else // if defined(ESP32) res = ESP.getFlashChipRealSize(); // ESP.getFlashChipSize(); #endif // if defined(ESP32) @@ -214,7 +223,7 @@ String getChipFeaturesString() { } bool getFlashChipOPI_wired() { - # if defined(ESP32_CLASSIC) || defined(ESP32C2) + # if defined(ESP32_CLASSIC) || defined(ESP32C2) || defined(ESP32C61) return false; # else // ifdef ESP32_CLASSIC @@ -238,6 +247,11 @@ uint32_t getFlashChipSpeed() { // for which patches have been submitted and somehow they managed to merge it completely wrong. return ESP.getFlashChipSpeed(); # else // if ESP_IDF_STILL_NEEDS_SPI_REGISTERS_FIXED +#if defined(ESP32P4) || defined(ESP32C61) + // TODO TD-er: Implement +// return 80000000; + return ESP.getFlashChipSpeed(); +#else // All ESP32-variants have the SPI flash wired to SPI peripheral 1 const uint32_t spi_clock = REG_READ(SPI_CLOCK_REG(1)); @@ -253,13 +267,17 @@ uint32_t getFlashChipSpeed() { return getApbFrequency(); } return spiClockDivToFrequency(spi_clock); +#endif # endif // if ESP_IDF_STILL_NEEDS_SPI_REGISTERS_FIXED #endif // ifdef ESP8266 } const __FlashStringHelper* getFlashChipMode() { - #ifdef ESP32 + #ifdef ESP32P4 + // TODO TD-er: Implement + #else + #ifdef ESP32 if (getFlashChipOPI_wired()) { switch (ESP.getFlashChipMode()) { case FM_QIO: return F("QIO (OPI Wired)"); @@ -273,7 +291,6 @@ const __FlashStringHelper* getFlashChipMode() { case FM_UNKNOWN: break; } } - #endif // ifdef ESP32 switch (ESP.getFlashChipMode()) { @@ -287,6 +304,7 @@ const __FlashStringHelper* getFlashChipMode() { #endif // ifdef ESP32 case FM_UNKNOWN: break; } +#endif return F("Unknown"); } @@ -365,8 +383,8 @@ bool isESP8285() { #endif // ifdef ESP32 - -String getChipRevision() { +uint16_t getChipRevision_val() +{ static uint16_t rev = 0; #ifdef ESP32 @@ -382,6 +400,12 @@ String getChipRevision() { # endif // if ESP_IDF_VERSION_MAJOR < 5 } #endif // ifdef ESP32 + return rev; +} + + +String getChipRevision() { + const uint16_t rev = getChipRevision_val(); return strformat(F("%d.%02d"), rev / 100, rev % 100); } diff --git a/src/src/Helpers/Hardware_device_info.h b/src/src/Helpers/Hardware_device_info.h index 8b8610017..48548fc1b 100644 --- a/src/src/Helpers/Hardware_device_info.h +++ b/src/src/Helpers/Hardware_device_info.h @@ -88,6 +88,7 @@ const __FlashStringHelper* getChipModel( bool isESP8285(uint32_t& pkg_version, bool& high_temp_version); bool isESP8285(); +uint16_t getChipRevision_val(); String getChipRevision(); uint32_t getSketchSize(); diff --git a/src/src/Helpers/Hardware_device_info_ESP32C5.cpp b/src/src/Helpers/Hardware_device_info_ESP32C5.cpp index e9b9148bf..80440d4d9 100644 --- a/src/src/Helpers/Hardware_device_info_ESP32C5.cpp +++ b/src/src/Helpers/Hardware_device_info_ESP32C5.cpp @@ -2,17 +2,84 @@ #ifdef ESP32C5 +# include +# include +# include +# include +# include +# include +# include + + +bool isFlashInterfacePin_ESPEasy(int gpio) { + // FIXME TD-er: Must know whether we have internal or external flash + + // For chip variants with an in-package flash, this pin can not be used. + + + switch (gpio) { + case MSPI_IOMUX_PIN_NUM_HD: + case MSPI_IOMUX_PIN_NUM_WP: + case MSPI_IOMUX_PIN_NUM_CS0: + case MSPI_IOMUX_PIN_NUM_CLK: + case MSPI_IOMUX_PIN_NUM_MOSI: + case MSPI_IOMUX_PIN_NUM_MISO: + return true; + + case 19: + // special pin to power flash + return true; + } + return false; + + // return (gpio) >= 24 && (gpio) <= 30 && gpio != 27; +} + +bool flashVddPinCanBeUsedAsGPIO() +{ + return false; +} + +int32_t getEmbeddedFlashSize() +{ + /* + // See: framework-arduinoespressif32\tools\esp32-arduino-libs\esp32c5\include\soc\esp32c5\include\soc\efuse_reg.h + const uint32_t flash_cap = REG_GET_FIELD(EFUSE_RD_MAC_SPI_SYS_4_REG, EFUSE_FLASH_CAP); + + // FIXME TD-er: No idea about meaning of values + switch (flash_cap) { + case 0: return 0; + case 1: return 4; + case 2: return 2; + case 3: return 1; + case 4: return 8; + } + + // Unknown value, thus mark as negative value + return -1 * static_cast(flash_cap); + */ + return 8; +} + +int32_t getEmbeddedPSRAMSize() +{ + // Doesn't have PSRAM + return 0; +} + + +# ifndef isPSRAMInterfacePin +bool isPSRAMInterfacePin(int gpio) { + return FoundPSRAM() ? ((gpio) >= MSPI_IOMUX_PIN_NUM_CS1 && (gpio) <= MSPI_IOMUX_PIN_NUM_MOSI) : false; +} + +# endif // ifndef isPSRAMInterfacePin + + const __FlashStringHelper* getChipModel(uint32_t chip_model, uint32_t chip_revision, uint32_t pkg_version, bool single_core) { - if (17 == chip_model) { // ESP32-C5 beta3 (MPW) - return F("ESP32-C5 beta3"); - } - else if (23 == chip_model) { // ESP32-C5 MP - return F("ESP32-C5"); - } - - return F("Unknown"); + return F("ESP32-C5"); } #endif // ifdef ESP32C5 diff --git a/src/src/Helpers/Hardware_device_info_ESP32C6.cpp b/src/src/Helpers/Hardware_device_info_ESP32C6.cpp index 22b8ddf3e..3ae98765a 100644 --- a/src/src/Helpers/Hardware_device_info_ESP32C6.cpp +++ b/src/src/Helpers/Hardware_device_info_ESP32C6.cpp @@ -112,9 +112,14 @@ const __FlashStringHelper* getChipModel(uint32_t chip_model, uint32_t chip_revis // AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("HDW: ESP32 Model %d, Revision %d, Core %d, Package %d"), chip_info.model, chip_revision, // chip_info.cores, chip_ver); + const int32_t flash_cap = getEmbeddedFlashSize(); + switch (pkg_version) { - case 0: return F("ESP32-C6"); - case 1: return F("ESP32-C6FH4 (QFN32)"); + case 0: return F("ESP32-C6 (QFN40)"); + case 1: + if (flash_cap == 4) return F("ESP32-C6FH4 (QFN32)"); + if (flash_cap == 8) return F("ESP32-C6FH8 (QFN32)"); + break; } return F("ESP32-C6"); diff --git a/src/src/Helpers/Hardware_device_info_ESP32C61.cpp b/src/src/Helpers/Hardware_device_info_ESP32C61.cpp index 75d4740ab..955d4ffd0 100644 --- a/src/src/Helpers/Hardware_device_info_ESP32C61.cpp +++ b/src/src/Helpers/Hardware_device_info_ESP32C61.cpp @@ -3,6 +3,84 @@ #ifdef ESP32C61 +# include +# include +# include +# include +# include +# include +# include + + +bool isFlashInterfacePin_ESPEasy(int gpio) { + // FIXME TD-er: Must know whether we have internal or external flash + + // For chip variants with an in-package flash, this pin can not be used. + + // FIXME TD-er: No idea where these pins come from. Disabled for now. + // See: https://github.com/letscontrolit/ESPEasy/issues/5220 + + /* + if ((gpio == 10) || (gpio == 11)) { + return true; + } + */ + + // For chip variants without an in-package flash, this pin can not be used. + // if (gpio == 14) + // return true; + + // GPIO-27: Flash voltage selector + // GPIO-24 ... 30: Connected to internal flash (might be available when using external flash???) + + switch (gpio) { +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 4, 0) + case SPI_IOMUX_PIN_NUM_CS: + case SPI_IOMUX_PIN_NUM_CLK: + case SPI_IOMUX_PIN_NUM_MOSI: + case SPI_IOMUX_PIN_NUM_MISO: + case SPI_IOMUX_PIN_NUM_WP: + case SPI_IOMUX_PIN_NUM_HD: +#else + case MSPI_IOMUX_PIN_NUM_HD: + case MSPI_IOMUX_PIN_NUM_WP: + case MSPI_IOMUX_PIN_NUM_CS0: + case MSPI_IOMUX_PIN_NUM_CLK: + case MSPI_IOMUX_PIN_NUM_MOSI: + case MSPI_IOMUX_PIN_NUM_MISO: +#endif + return true; + } + return false; + + // return (gpio) >= 24 && (gpio) <= 30 && gpio != 27; +} + +bool flashVddPinCanBeUsedAsGPIO() +{ + return false; +} + +int32_t getEmbeddedFlashSize() +{ + return 8; // TODO TD-er: Must implement + // See: framework-arduinoespressif32\tools\esp32-arduino-libs\esp32c61\include\soc\esp32c61\include\soc\efuse_reg.h +} + +int32_t getEmbeddedPSRAMSize() +{ + // Doesn't have PSRAM + return 0; +} + +# ifndef isPSRAMInterfacePin +bool isPSRAMInterfacePin(int gpio) { + return false; +} + +# endif // ifndef isPSRAMInterfacePin + + const __FlashStringHelper* getChipModel(uint32_t chip_model, uint32_t chip_revision, uint32_t pkg_version, bool single_core) { if (20 == chip_model) { // ESP32-C61 diff --git a/src/src/Helpers/Hardware_device_info_ESP32P4.cpp b/src/src/Helpers/Hardware_device_info_ESP32P4.cpp index 99c80aada..1ae9fc977 100644 --- a/src/src/Helpers/Hardware_device_info_ESP32P4.cpp +++ b/src/src/Helpers/Hardware_device_info_ESP32P4.cpp @@ -2,14 +2,54 @@ #ifdef ESP32P4 +#include const __FlashStringHelper* getChipModel(uint32_t chip_model, uint32_t chip_revision, uint32_t pkg_version, bool single_core) { if (18 == chip_model) { // ESP32-P4 - return F("ESP32-P4"); + const uint16_t rev = getChipRevision_val(); + return rev < 300 ? F("ESP32-P4") : F("ESP32-P4r3"); } return F("Unknown"); } +int32_t getEmbeddedFlashSize() +{ + // Returned size in MB + return ESP.getFlashChipSize() >> 20; +} + +int32_t getEmbeddedPSRAMSize() +{ + const uint32_t psram_cap = REG_GET_FIELD(EFUSE_RD_MAC_SYS_2_REG, EFUSE_PSRAM_CAP); + + // TODO TD-er: Must check whether this is true + switch (psram_cap) { + case 0: return 0; + case 1: return 16; + case 2: return 32; + } + + // Unknown value, thus mark as negative value + return -1 * static_cast(psram_cap); +} + +# ifndef isPSRAMInterfacePin +bool isPSRAMInterfacePin(int gpio) { + // PSRAM and flash do not use GPIO pins + // The ESP32-P4 chip has dedicated pins for external flash and in-package PSRAM. + // Such pins can not be used for other purpose. + return false; +} + +# endif // ifndef isPSRAMInterfacePin + +bool isFlashInterfacePin_ESPEasy(int gpio) { + // PSRAM and flash do not use GPIO pins + // The ESP32-P4 chip has dedicated pins for external flash and in-package PSRAM. + // Such pins can not be used for other purpose. + return false; +} + #endif // ifdef ESP32P4 diff --git a/src/src/Helpers/Hardware_temperature_sensor.cpp b/src/src/Helpers/Hardware_temperature_sensor.cpp index 6420ca5d6..cc89d6074 100644 --- a/src/src/Helpers/Hardware_temperature_sensor.cpp +++ b/src/src/Helpers/Hardware_temperature_sensor.cpp @@ -17,7 +17,7 @@ extern "C" { uint8_t temprature_sens_read(); } -# elif defined(ESP32C2) || defined(ESP32C3) || defined(ESP32C6) || defined(ESP32S2) || defined(ESP32S3) +# elif defined(ESP32C2) || defined(ESP32C3) || defined(ESP32C5) || defined(ESP32C6) || defined(ESP32C61) || defined(ESP32S2) || defined(ESP32S3) || defined(ESP32P4) # if ESP_IDF_VERSION_MAJOR < 5 # include @@ -62,7 +62,7 @@ esp_err_t do_read_internal_temperature(float& celsius) { return result; } -# elif defined(ESP32C2) || defined(ESP32C3) || defined(ESP32C6) || defined(ESP32S2) || defined(ESP32S3) +# elif defined(ESP32C2) || defined(ESP32C3) || defined(ESP32C5) || defined(ESP32C6) || defined(ESP32C61) || defined(ESP32S2) || defined(ESP32S3) || defined(ESP32P4) esp_err_t do_read_internal_temperature(float& celsius) { esp_err_t result = ESP_FAIL; diff --git a/src/src/Helpers/IBusCmd_Handler.h b/src/src/Helpers/IBusCmd_Handler.h index 7ec7cee77..edff68843 100644 --- a/src/src/Helpers/IBusCmd_Handler.h +++ b/src/src/Helpers/IBusCmd_Handler.h @@ -17,7 +17,7 @@ * 2025-05-13 tonhuisman: Add String data-format support, guarded with FEATURE_BUSCMD_STRING * 2025-05-10 tonhuisman: Extract interface and I2C implementation from Plugin P180 I2C Generic implementation */ - +#if FEATURE_BUS_COMMAND #include class IBusCmd_Handler { // Interface/Abstract Class @@ -86,4 +86,5 @@ public: bool wideReg = false) = 0; #endif // if FEATURE_BUSCMD_STRING }; +#endif #endif // ifndef _HELPERS_IBUSCMD_HANDLER_H diff --git a/src/src/Helpers/Improv_Helper.cpp b/src/src/Helpers/Improv_Helper.cpp index c646fa087..477f8e34c 100644 --- a/src/src/Helpers/Improv_Helper.cpp +++ b/src/src/Helpers/Improv_Helper.cpp @@ -2,7 +2,7 @@ #if FEATURE_IMPROV # include "../CustomBuild/CompiletimeDefines.h" -# include "../ESPEasyCore/ESPEasyNetwork.h" +# include "../../ESPEasy/net/ESPEasyNetwork.h" # include "../Globals/SecuritySettings.h" # include "../Helpers/ESPEasy_Storage.h" # include "../Helpers/Hardware_device_info.h" @@ -94,7 +94,7 @@ void Improv_Helper_t::init() _improv.setDeviceInfo( firmwareName.c_str(), buildString.c_str(), - NetworkGetHostNameFromSettings().c_str()); + ESPEasy::net::NetworkGetHostNameFromSettings().c_str()); String chipModel = getChipModel(); diff --git a/src/src/Helpers/KeyValueWriter.cpp b/src/src/Helpers/KeyValueWriter.cpp new file mode 100644 index 000000000..a0896e046 --- /dev/null +++ b/src/src/Helpers/KeyValueWriter.cpp @@ -0,0 +1,73 @@ +#include "../Helpers/KeyValueWriter.h" + + + + +// ******************************************************************************** +// KeyValueWriter +// ******************************************************************************** + +void KeyValueWriter::clear() { + _isEmpty = true; + + if (_toString) { + _toString->clear(); + } +} + +void KeyValueWriter::writeLabels(const LabelType::Enum labels[], bool extendedValues) +{ + size_t i = 0; + LabelType::Enum cur = static_cast(pgm_read_byte(labels + i)); + + while (cur != LabelType::MAX_LABEL) { + auto kv = getKeyValue(cur, extendedValues); + if (!kv._key.isEmpty()) + write(kv); + cur = static_cast(pgm_read_byte(labels + i + 1)); + ++i; + } +} + +void KeyValueWriter::writeNote(const String& note) +{ + if (!dataOnlyOutput()) { + write({ + EMPTY_STRING, + note, + KeyValueStruct::Format::Note }); + } +} + +void KeyValueWriter::writeNote(const __FlashStringHelper *note) +{ + if (!dataOnlyOutput()) { + write({ + EMPTY_STRING, + note, + KeyValueStruct::Format::Note }); + } +} + +int KeyValueWriter::getLevel() const +{ + if (_parent == nullptr) { return 0; } + return _parent->getLevel() + 1; +} + +bool KeyValueWriter::plainText() const { + if (_toString != nullptr) { return true; } + + if (_parent && _parent->plainText()) { return true; } + return _plainText; +} + +bool KeyValueWriter::allowHTML() const { + return false; +} + +bool KeyValueWriter::summaryValueOnly() const { + if (_parent && _parent->summaryValueOnly()) { return true; } + + return _summaryValueOnly; +} diff --git a/src/src/Helpers/KeyValueWriter.h b/src/src/Helpers/KeyValueWriter.h new file mode 100644 index 000000000..7735bc4be --- /dev/null +++ b/src/src/Helpers/KeyValueWriter.h @@ -0,0 +1,160 @@ +#pragma once + +#include "../../ESPEasy_common.h" + +#include "../DataStructs/KeyValueStruct.h" +#include "../Helpers/PrintToString.h" +#include "../WebServer/HTML_Print.h" + +#include + + +class KeyValueWriter; +DEF_UP(KeyValueWriter); + +// ******************************************************************************** +// KeyValueWriter +// ******************************************************************************** +class KeyValueWriter +{ +public: + + KeyValueWriter(bool emptyHeader = false, PrintToString *toStr = nullptr) : _toString(toStr), _hasHeader(emptyHeader) {} + +protected: + + KeyValueWriter(KeyValueWriter*parent, PrintToString *toStr = nullptr) : _toString(toStr), _parent(parent) + {} + + KeyValueWriter(bool emptyHeader, KeyValueWriter*parent, PrintToString *toStr = nullptr) : _toString(toStr), _parent(parent), + _hasHeader(emptyHeader) {} + + KeyValueWriter(const String& header, KeyValueWriter*parent, PrintToString *toStr = nullptr) : _toString(toStr), _header(header), + _parent(parent) + {} + +public: + + virtual ~KeyValueWriter() {} + + virtual void setHeader(const String& header) { + _header = header; + _hasHeader = true; + } + + virtual void setFooter(const String& footer) { _footer = footer; } + + virtual void setIsArray() { _isArray = true; } + + virtual void clear(); + + // Mark a write, typically called from a child calling its parent it is about to write + virtual void write() = 0; + + virtual void write(const KeyValueStruct& kv) = 0; + + void writeLabels(const LabelType::Enum labels[], + bool extendedValues = false); + + virtual void writeNote(const String& note); + virtual void writeNote(const __FlashStringHelper *note); + + // virtual void setParent(KeyValueWriter*parent) { _parent = parent; } + + virtual int getLevel() const; + + // When set to 'plainText', the writer will not try to insert writer specific + // markings, like
 or 
for example for HTML output + // When set to true, any child writer will also have this set to true + virtual void setPlainText() { _plainText = true; } + + virtual bool plainText() const; + + virtual bool allowHTML() const; + + // 'summaryValueOnly' means the key will not be output and this is also a hint to generate a summary of data. + // Typically this is intended for human readable texts. + // When set to true, any child writer will also have this set to true + virtual void setSummaryValueOnly() { _summaryValueOnly = true; } + + virtual bool summaryValueOnly() const; + + // Force to even write the header when list/array is empty. + virtual void setWriteWhenEmpty() { _writeWhenEmpty = true; } + + // This should be override by any writer outputting data which is not intended to be human readable. + virtual bool dataOnlyOutput() const { return false; } + + // TODO TD-er: Change this to std::unique_ptr + virtual void setOutputToString(PrintToString*printToStr) { _toString = printToStr; } + + // Create writer of the same derived type, with this set as parent + virtual UP_KeyValueWriter createChild() = 0; + virtual UP_KeyValueWriter createChild(const String& header) = 0; + virtual UP_KeyValueWriter createChildArray(const String& header) = 0; + + // Create new writer of the same derived type, without parent + virtual UP_KeyValueWriter createNew() = 0; + virtual UP_KeyValueWriter createNew(const String& header) = 0; + + const String& get() const { + if (_toString == nullptr) { return EMPTY_STRING; } + return _toString->get(); + } + + String&& getMove() + { + if (_toString == nullptr) { + static String tmp; + return std::move(tmp); + } + return std::move(_toString->getMove()); + } + + bool reserve(unsigned int size) { return _toString && _toString->reserve(size); } + + virtual void indent() {} + + virtual void allowFormatOverrides(bool allow) { _allowFormatOverrides = allow; } + +protected: + + Print& getPrint() { + if (_toString == nullptr) { return _toWeb; } + return *_toString; + } + + // TODO TD-er: Change this to std::unique_ptr + PrintToString *_toString = nullptr; + +private: + + PrintToWebServer _toWeb; + +protected: + + String _header; + + // To be written from destructor + String _footer; + + KeyValueWriter *_parent = nullptr; + + bool _hasHeader = true; + + bool _isEmpty = true; + + bool _writeWhenEmpty = false; + + bool _isArray{}; + + // private: + + bool _plainText = false; + + bool _summaryValueOnly = false; + + bool _allowFormatOverrides = true; + + +}; // class KeyValueWriter diff --git a/src/src/Helpers/KeyValueWriter_JSON.cpp b/src/src/Helpers/KeyValueWriter_JSON.cpp new file mode 100644 index 000000000..0d45907c7 --- /dev/null +++ b/src/src/Helpers/KeyValueWriter_JSON.cpp @@ -0,0 +1,269 @@ +#include "../Helpers/KeyValueWriter_JSON.h" + +#include "../Globals/Settings.h" +#include "../Helpers/StringConverter.h" + +#include "../WebServer/HTML_wrappers.h" + + +KeyValueWriter_JSON::KeyValueWriter_JSON(bool emptyHeader, PrintToString *toStr) + : KeyValueWriter(emptyHeader, toStr) +{} + +KeyValueWriter_JSON::KeyValueWriter_JSON(KeyValueWriter_JSON*parent, PrintToString *toStr) + : KeyValueWriter(parent, toStr) +{} + +KeyValueWriter_JSON::KeyValueWriter_JSON(bool emptyHeader, KeyValueWriter_JSON*parent, PrintToString *toStr) + : KeyValueWriter(emptyHeader, parent, toStr) +{} + +KeyValueWriter_JSON::KeyValueWriter_JSON(const String& header, PrintToString *toStr) + : KeyValueWriter(header, nullptr, toStr) +{} + +KeyValueWriter_JSON::KeyValueWriter_JSON(const __FlashStringHelper *header, PrintToString *toStr) + : KeyValueWriter(String(header), nullptr, toStr) +{} + + +KeyValueWriter_JSON::KeyValueWriter_JSON(const String& header, KeyValueWriter_JSON*parent, PrintToString *toStr) + : KeyValueWriter(header, parent, toStr) +{} + +KeyValueWriter_JSON::KeyValueWriter_JSON(const __FlashStringHelper *header, KeyValueWriter_JSON*parent, PrintToString *toStr) + : KeyValueWriter(String(header), parent, toStr) +{} + +KeyValueWriter_JSON::~KeyValueWriter_JSON() +{ + if (_writeWhenEmpty && _isEmpty) { + // Make sure the header is written and then write the closing braces. + write(); + } + + + if (!_isEmpty) { +#ifdef USE_KWH_JSON_PRETTY_PRINT + getPrint().write('\n'); +#endif + + if (_hasHeader) { +#ifdef USE_KWH_JSON_PRETTY_PRINT + indent(); +#endif + + getPrint().write(_isArray ? ']' : '}'); +#ifndef USE_KWH_JSON_PRETTY_PRINT + if (_isArray) getPrint().write('\n'); +#endif + + } + } + + if (!_footer.isEmpty()) + { + getPrint().print(_footer); + } +} + +void KeyValueWriter_JSON::write() +{ + if (_isEmpty) { + if (_parent != nullptr) { _parent->write(); } + + if (_hasHeader) { +#ifdef USE_KWH_JSON_PRETTY_PRINT + indent(); +#endif + + if (_header.isEmpty()) { + getPrint().write('{'); +#ifdef USE_KWH_JSON_PRETTY_PRINT + getPrint().write('\n'); +#endif + } else { + getPrint().print(strformat( +#ifdef USE_KWH_JSON_PRETTY_PRINT + F("\"%s\":%c\n"), +#else + F("\"%s\":%c"), +#endif + _header.c_str(), + _isArray ? '[' : '{')); + } + } + _isEmpty = false; + } else { + getPrint().write(','); +#ifdef USE_KWH_JSON_PRETTY_PRINT + getPrint().write('\n'); +#endif + } +} + +void KeyValueWriter_JSON::write(const KeyValueStruct& kv) +{ + if (kv._format == KeyValueStruct::Format::Note) { return; } + write(); +#ifdef USE_KWH_JSON_PRETTY_PRINT + indent(); + getPrint().write('\t'); +#endif // ifdef USE_KWH_JSON_PRETTY_PRINT + + if (!kv._key.isEmpty()) { + auto& pr = getPrint(); + pr.write('"'); + kv._key.print(pr); + pr.write('"'); + pr.write(':'); + } + + const size_t nrValues = kv._values.size(); + + if (!kv._isArray) { + // Either 1 value or empty value + if (nrValues == 0) { + auto& pr = getPrint(); + pr.write('"'); + pr.write('"'); + } + else { + writeValue(kv._values[0]); + } + } else { + // Multiple values, so we must wrap it in [] + auto& pr = getPrint(); + pr.write('['); +#ifdef USE_KWH_JSON_PRETTY_PRINT + pr.write('\n'); +#endif + for (size_t i = 0; i < nrValues; ++i) { + if (i != 0) { + auto& pr = getPrint(); + pr.write(','); +#ifdef USE_KWH_JSON_PRETTY_PRINT + pr.write('\n'); +#endif + } +#ifdef USE_KWH_JSON_PRETTY_PRINT + indent(); + auto& pr = getPrint(); + pr.write('\t'); + pr.write('\t'); +#endif // ifdef USE_KWH_JSON_PRETTY_PRINT + + writeValue(kv._values[i]); + } + getPrint().write(']'); +#ifndef USE_KWH_JSON_PRETTY_PRINT + getPrint().write('\n'); +#endif + } +} + +void KeyValueWriter_JSON::writeValue(const ValueStruct& val) +{ + if (!val.isSet()) { return; } + auto& pr = getPrint(); + + ValueStruct::ValueType valueType = val.getValueType(); + ValueStruct::ValueType valueType_afterPrint; + String str = val.toString(valueType_afterPrint); + + switch (valueType) + { + case ValueStruct::ValueType::Float: + case ValueStruct::ValueType::Double: + + if (!_allowFormatOverrides && valueType_afterPrint != valueType) { + pr.print(F("null")); + return; + } + + case ValueStruct::ValueType::Int: + case ValueStruct::ValueType::UInt: + pr.print(str); + return; + case ValueStruct::ValueType::Bool: + + if (_allowFormatOverrides && !Settings.JSONBoolWithoutQuotes()) { pr.write('"'); } + pr.print(str.equals("0") ? F("false") : F("true")); + + if (_allowFormatOverrides && !Settings.JSONBoolWithoutQuotes()) { pr.write('"'); } + return; + + case ValueStruct::ValueType::Unset: + case ValueStruct::ValueType::String: + case ValueStruct::ValueType::FlashString: + break; + } + pr.print(to_json_value(str)); +} + +UP_KeyValueWriter KeyValueWriter_JSON::createChild() +{ + UP_KeyValueWriter_JSON child(new (std::nothrow) KeyValueWriter_JSON(this, _toString)); + + child->_allowFormatOverrides = _allowFormatOverrides; + + return std::move(child); + + // return std::make_unique(this, _toString); +} + +UP_KeyValueWriter KeyValueWriter_JSON::createChild(const String& header) +{ + UP_KeyValueWriter_JSON child(new (std::nothrow) KeyValueWriter_JSON(header, this, _toString)); + + child->_allowFormatOverrides = _allowFormatOverrides; + + return std::move(child); + + // return std::make_unique(header, this, _toString); +} + +UP_KeyValueWriter KeyValueWriter_JSON::createChildArray(const String& header) +{ + auto child = createChild(header); + + if (child) { + child->setIsArray(); + } + //return std::move(child); + return child; +} + +UP_KeyValueWriter KeyValueWriter_JSON::createNew() +{ + UP_KeyValueWriter_JSON child(new (std::nothrow) KeyValueWriter_JSON(false, _toString)); + + child->_allowFormatOverrides = _allowFormatOverrides; + + return std::move(child); + + // return std::make_unique(false, _toString); +} + +UP_KeyValueWriter KeyValueWriter_JSON::createNew(const String& header) +{ + UP_KeyValueWriter_JSON child(new (std::nothrow) KeyValueWriter_JSON(header, _toString)); + + child->_allowFormatOverrides = _allowFormatOverrides; + + return std::move(child); + + // return std::make_unique(header, _toString); +} + +#ifdef USE_KWH_JSON_PRETTY_PRINT + +void KeyValueWriter_JSON::indent() +{ + if (_parent != nullptr) { + getPrint().write('\t'); + _parent->indent(); + } +} + +#endif // ifdef USE_KWH_JSON_PRETTY_PRINT diff --git a/src/src/Helpers/KeyValueWriter_JSON.h b/src/src/Helpers/KeyValueWriter_JSON.h new file mode 100644 index 000000000..e32483d44 --- /dev/null +++ b/src/src/Helpers/KeyValueWriter_JSON.h @@ -0,0 +1,88 @@ +#pragma once + +#include "../Helpers/KeyValueWriter.h" + +#ifndef BUILD_NO_DEBUG + +// # define USE_KWH_JSON_PRETTY_PRINT +#endif // ifndef BUILD_NO_DEBUG + +class KeyValueWriter_JSON : public KeyValueWriter +{ +public: + + /* + TODO TD-er: Replace + - stream_to_json_object_value used in ChartJS code + + */ + + using KeyValueWriter::writeLabels; + + KeyValueWriter_JSON(bool emptyHeader = false, + PrintToString *toStr = nullptr); + + KeyValueWriter_JSON(KeyValueWriter_JSON*parent, + PrintToString *toStr = nullptr); + + KeyValueWriter_JSON(bool emptyHeader, + KeyValueWriter_JSON*parent, + PrintToString *toStr = nullptr); + + KeyValueWriter_JSON(const String & header, + PrintToString *toStr = nullptr); + KeyValueWriter_JSON(const __FlashStringHelper *header, + PrintToString *toStr = nullptr); + + KeyValueWriter_JSON(const String & header, + KeyValueWriter_JSON*parent, + PrintToString *toStr = nullptr); + KeyValueWriter_JSON(const __FlashStringHelper *header, + KeyValueWriter_JSON *parent, + PrintToString *toStr = nullptr); + + virtual ~KeyValueWriter_JSON(); + + virtual void write(); + + virtual void write(const KeyValueStruct& kv); + + // Create writer of the same derived type, with this set as parent + virtual UP_KeyValueWriter createChild(); + virtual UP_KeyValueWriter createChild(const String& header); + virtual UP_KeyValueWriter createChildArray(const String& header); + + // Create new writer of the same derived type, without parent + virtual UP_KeyValueWriter createNew(); + virtual UP_KeyValueWriter createNew(const String& header); + + virtual bool dataOnlyOutput() const override { + // JSON is not intended to be human readable + return true; + } + + virtual bool plainText() const override { + // No 'plain text' in JSON + return false; + } + + virtual bool summaryValueOnly() const override { + // No 'summary mode' in JSON + return false; + } + +private: + + void writeValue(const ValueStruct& value); + +#ifdef USE_KWH_JSON_PRETTY_PRINT + +protected: + + virtual void indent() override; +#endif // ifdef USE_KWH_JSON_PRETTY_PRINT + + +}; // class KeyValueWriter_JSON + +DEF_UP(KeyValueWriter_JSON); diff --git a/src/src/Helpers/LogStreamWriter.cpp b/src/src/Helpers/LogStreamWriter.cpp new file mode 100644 index 000000000..b58e56ce2 --- /dev/null +++ b/src/src/Helpers/LogStreamWriter.cpp @@ -0,0 +1,118 @@ +#include "../Helpers/LogStreamWriter.h" + +#include "../Globals/Logging.h" + +#include "../Helpers/ESPEasy_time_calc.h" +#include "../Helpers/Memory.h" +#include "../Helpers/StringConverter.h" + +void LogStreamWriter::clear() +{ + _prefix.clear(); + free_string(_message); + _timestamp = 0; + _readpos = 0; +} + +bool LogStreamWriter::process(Stream*stream, size_t availableForWrite) +{ + if (stream == nullptr) { return false; } + return write(*stream, availableForWrite) != 0; +} + +bool LogStreamWriter::process() +{ + return false; +} + +uint32_t LogStreamWriter::getNrMessages() const +{ + return Logging.getNrMessages(_log_destination); +} + +size_t LogStreamWriter::write(Stream& stream, size_t nrBytesToWrite) +{ + size_t bytesWritten = 0; + + while (bytesWritten < nrBytesToWrite) { + const size_t written = write_item(stream, (nrBytesToWrite - bytesWritten)); + + if (written == 0) { + return bytesWritten; + } + bytesWritten += written; + } + return bytesWritten; +} + +size_t LogStreamWriter::write_single_item(Stream& stream, + size_t nrBytesToWrite) +{ + const size_t res = write_item(stream, nrBytesToWrite); + + clear(); + return res; +} + +size_t LogStreamWriter::write_item(Stream& stream, + size_t nrBytesToWrite) +{ + size_t bytesWritten = 0; + + if ((_timestamp != 0) && (timePassedSince(_timestamp) > LOG_BUFFER_EXPIRE)) { + clear(); + bytesWritten += write_skipping(stream); + } + + if (_timestamp == 0) { + _readpos = 0; + + // Need to fetch a line + if (!Logging.getNext(_log_destination, _timestamp, _message, _loglevel) || + !loglevelActiveFor(_log_destination, _loglevel)) { + free_string(_message); + _timestamp = 0; + return bytesWritten; + } + + prepare_prefix(); + } + + const size_t maxToWrite = _prefix.length() + _message.length(); + + if (nrBytesToWrite > maxToWrite) { + nrBytesToWrite = maxToWrite; + } + + bool done = false; + + while (!done && bytesWritten < nrBytesToWrite) { + if (_readpos < _prefix.length()) { + // Write prefix + if (1 != stream.write(_prefix[_readpos])) { return bytesWritten; } + ++bytesWritten; + ++_readpos; + } else if (!_prefix.isEmpty()) { + // Clear prefix + _prefix.clear(); + _readpos = 0; + } else if (_readpos < _message.length()) { + // Write message + if (1 != stream.write(_message[_readpos])) { return bytesWritten; } + ++bytesWritten; + ++_readpos; + } else { + if ((bytesWritten + 2) > nrBytesToWrite) { return bytesWritten; } + bytesWritten += stream.println(); + + // Done with entry, cleanup and leave + clear(); + done = true; + } + } + return bytesWritten; +} + +size_t LogStreamWriter::write_skipping(Stream& stream) { return 0; } + +void LogStreamWriter::prepare_prefix() {} diff --git a/src/src/Helpers/LogStreamWriter.h b/src/src/Helpers/LogStreamWriter.h new file mode 100644 index 000000000..2e94453ed --- /dev/null +++ b/src/src/Helpers/LogStreamWriter.h @@ -0,0 +1,54 @@ +#pragma once + +#include "../../ESPEasy_common.h" + +class LogStreamWriter +{ + +public: + + LogStreamWriter(LogDestination log_destination) : _log_destination(log_destination) {} + + virtual ~LogStreamWriter() {} + + virtual bool process(Stream* stream, size_t availableForWrite); + + // Only use this from derived classes, as we need a Stream to further process + virtual bool process(); + + virtual uint32_t getNrMessages() const; + + virtual void clear(); + +protected: + + // Write continuously until either nrBytesToWrite was reached or no new messages were available to process. + // @retval Number of bytes written. Zero when no new message was available to process. + virtual size_t write(Stream& stream, + size_t nrBytesToWrite); + + // Write single item and clear() on return. + // This way each call starts with a new item and long messages may get truncated based on nrBytesToWrite + // @retval Number of bytes written. Zero when no new message was available to process. + virtual size_t write_single_item(Stream& stream, + size_t nrBytesToWrite); + + virtual size_t write_item(Stream& stream, + size_t nrBytesToWrite); + + + virtual size_t write_skipping(Stream& stream); + + virtual void prepare_prefix(); + + + String _prefix; + String _message; + uint32_t _timestamp{}; + uint32_t _readpos{}; + uint8_t _loglevel{}; + + const LogDestination _log_destination; + + +}; // class LogStreamWriter diff --git a/src/src/Helpers/Log_Helper.cpp b/src/src/Helpers/Log_Helper.cpp new file mode 100644 index 000000000..b96f3e00e --- /dev/null +++ b/src/src/Helpers/Log_Helper.cpp @@ -0,0 +1,130 @@ +#include "../Helpers/Log_Helper.h" + + +#include "../Globals/ESPEasy_Console.h" +#include "../Helpers/Networking.h" +#include "../Helpers/StringConverter.h" + + +#include + +#if FEATURE_SD +# include +# include "../Helpers/ESPEasy_Storage.h" +#endif // if FEATURE_SD + +#if FEATURE_SD +void addToSDLog(uint8_t logLevel, const String& str) +{ + if (!str.isEmpty() && loglevelActiveFor(LOG_TO_SDCARD, logLevel)) { + String logName = patch_fname(F("log.txt")); + fs::File logFile = SD.open(logName, "a+"); + + if (logFile) { + const size_t stringLength = str.length(); + + for (size_t i = 0; i < stringLength; ++i) { + logFile.print(str[i]); + } + logFile.println(); + } + logFile.close(); + } +} +#endif // if FEATURE_SD + +void LogHelper::addLogEntry(LogEntry_t&& logEntry) +{ + #ifdef ESP32 + + if (xPortInIsrContext()) { + // When called from an ISR, you should not send out logs. + // Allocating memory from within an ISR is a big no-no. + // Also long-time blocking like sending logs (especially to a syslog server) + // is also really not a good idea from an ISR call. + return; + } + #endif // ifdef ESP32 + + logEntry.setSubscribers(); + + if (!logEntry) { return; } + + _logBuffer.add(std::move(logEntry)); + + loop(); +} + +bool LogHelper::getNext(LogDestination logDestination, uint32_t& timestamp, String& message, uint8_t& loglevel) +{ + return _logBuffer.getNext(logDestination, timestamp, message, loglevel); +} + +uint32_t LogHelper::getNrMessages(LogDestination logDestination) const +{ + return _logBuffer.getNrMessages(logDestination); +} + +void LogHelper::loop() +{ +#if FEATURE_SD + String message; + uint32_t timestamp{}; + uint8_t loglevel{}; + + if (_logBuffer.getNext(LOG_TO_SDCARD, timestamp, message, loglevel)) + { + addToSDLog(loglevel, message); + } +#endif + _logBuffer.clearExpiredEntries(); +} + +bool LogHelper::logActiveRead(LogDestination logDestination) +{ + return _logBuffer.logActiveRead(logDestination); +} + +void LogHelper::consolePrint(const __FlashStringHelper *text) { _tmpConsoleOutput += text; } + +void LogHelper::consolePrint(const String& text) { _tmpConsoleOutput += text; } + +void LogHelper::consolePrintln(const __FlashStringHelper *text) +{ + if (_tmpConsoleOutput.isEmpty()) { + // A complete console line, thus send directly to _logBuffer + addLogEntry({ LOG_LEVEL_NONE, text }); + } else { + _tmpConsoleOutput += text; + consolePrintln(); + } +} + +void LogHelper::consolePrintln(const String& text) +{ + if (_tmpConsoleOutput.isEmpty()) { + // A complete console line, thus send directly to _logBuffer + addLogEntry({ LOG_LEVEL_NONE, text }); + } else { + _tmpConsoleOutput += text; + consolePrintln(); + } +} + +void LogHelper::consolePrintln(String&& text) +{ + if (_tmpConsoleOutput.isEmpty()) { + // A complete console line, thus send directly to _logBuffer + addLogEntry({ LOG_LEVEL_NONE, std::move(text) }); + } else { + _tmpConsoleOutput += text; + consolePrintln(); + } +} + +void LogHelper::consolePrintln() +{ + if (!_tmpConsoleOutput.isEmpty()) { + addLogEntry({ LOG_LEVEL_NONE, std::move(_tmpConsoleOutput) }); + } +} diff --git a/src/src/Helpers/Log_Helper.h b/src/src/Helpers/Log_Helper.h new file mode 100644 index 000000000..a858f7915 --- /dev/null +++ b/src/src/Helpers/Log_Helper.h @@ -0,0 +1,44 @@ +#pragma once + +#include "../../ESPEasy_common.h" + +#include "../DataStructs/LogBuffer.h" + +#include "../DataTypes/LogLevels.h" + +class LogHelper +{ +public: + + LogHelper() {} + + void addLogEntry(LogEntry_t&& logEntry); + + bool getNext(LogDestination logDestination, + uint32_t& timestamp, + String & message, + uint8_t & loglevel); + + uint32_t getNrMessages(LogDestination logDestination) const; + + void loop(); + + bool logActiveRead(LogDestination logDestination); + + + // Append to internal buffer, which will only be flushed on consolePrintln + void consolePrint(const __FlashStringHelper *text); + void consolePrint(const String& text); + + void consolePrintln(const __FlashStringHelper *text); + void consolePrintln(const String& text); + void consolePrintln(String&& text); + + void consolePrintln(); + +private: + + String _tmpConsoleOutput; + + LogBuffer _logBuffer{}; +}; // class LogHelper diff --git a/src/src/Helpers/LongTermOnOffTimer.cpp b/src/src/Helpers/LongTermOnOffTimer.cpp new file mode 100644 index 000000000..9569c1ccd --- /dev/null +++ b/src/src/Helpers/LongTermOnOffTimer.cpp @@ -0,0 +1,95 @@ +#include "../Helpers/LongTermOnOffTimer.h" + +void LongTermOnOffTimer::clear() +{ + _changedSinceLastCheck = _onTimer.isSet() || _offTimer.isSet(); + _onTimer.clear(); + _offTimer.clear(); + _prevDuration = 0; + resetCount(); +} + +bool LongTermOnOffTimer::setOn() +{ + if (isOn()) { return false; } + _changedSinceLastCheck = true; + ++_changeToOnCount; + + if (isOff()) { + _prevDuration = _offTimer.usecPassedSince(); + } + _onTimer.setNow(); + return true; +} + +bool LongTermOnOffTimer::setOff() +{ + if (isOff()) { return false; } + _changedSinceLastCheck = true; + ++_changeToOffCount; + + if (isOn()) { + _prevDuration = _onTimer.usecPassedSince(); + } + _offTimer.setNow(); + return true; +} + +bool LongTermOnOffTimer::set(bool onState) +{ + if (onState) { return setOn(); } + return setOff(); +} + +bool LongTermOnOffTimer::forceSet(bool onState) +{ + set(!onState); + return set(onState); +} + +bool LongTermOnOffTimer::isSet() const +{ + return _onTimer.isSet() || _offTimer.isSet(); +} + +bool LongTermOnOffTimer::isOn() const +{ + if (!_onTimer.isSet()) { return false; } + + if (!_offTimer.isSet()) { return true; } + + // return true when 'onTimer' is later than 'offTimer' + return _offTimer < _onTimer; +} + +bool LongTermOnOffTimer::isOff() const +{ + if (!_offTimer.isSet()) { return false; } + + if (!_onTimer.isSet()) { return true; } + + // return true when 'offTimer' is later than 'onTimer' + return _offTimer > _onTimer; +} + +LongTermTimer::Duration LongTermOnOffTimer::getLastOnDuration_ms() const +{ + return getLastOnDuration_usec() / 1000ll; +} + +LongTermTimer::Duration LongTermOnOffTimer::getLastOffDuration_ms() const +{ + return getLastOffDuration_usec() / 1000ll; +} + +LongTermTimer::Duration LongTermOnOffTimer::getLastOnDuration_usec() const +{ + if (isOn()) { return _onTimer.usecPassedSince(); } + return _prevDuration; +} + +LongTermTimer::Duration LongTermOnOffTimer::getLastOffDuration_usec() const +{ + if (isOff()) { return _offTimer.usecPassedSince(); } + return _prevDuration; +} diff --git a/src/src/Helpers/LongTermOnOffTimer.h b/src/src/Helpers/LongTermOnOffTimer.h new file mode 100644 index 000000000..afe892ea4 --- /dev/null +++ b/src/src/Helpers/LongTermOnOffTimer.h @@ -0,0 +1,67 @@ +#pragma once + +#include "../Helpers/LongTermTimer.h" + +class LongTermOnOffTimer +{ +public: + + void clear(); + + void resetCount() { _changeToOffCount = 0; _changeToOnCount = 0; } + + // Return true when state changed + bool setOn(); + + // Return true when state changed + bool setOff(); + + bool set(bool onState); + + // Set to specific on/off state + reset last change timer to now + bool forceSet(bool onState); + + // Return true if either 'on' or 'off' was set at least once (since 'clear()' or construction) + bool isSet() const; + + // Returns true if the last set state was to 'on' + bool isOn() const; + + // Returns true if the last set state was to 'off' + bool isOff() const; + + LongTermTimer::Duration getLastOnDuration_ms() const; + LongTermTimer::Duration getLastOffDuration_ms() const; + + LongTermTimer::Duration getLastOnDuration_usec() const; + LongTermTimer::Duration getLastOffDuration_usec() const; + + size_t getCycleCount() const { return (_changeToOnCount + _changeToOffCount) / 2; } + + size_t getChangeToOnCount() const { return _changeToOnCount; } + + size_t getChangeToOffCount() const { return _changeToOffCount; } + + bool changedSinceLastCheck() const { return _changedSinceLastCheck; } + + bool changedSinceLastCheck_and_clear() + { + if (_changedSinceLastCheck) { + _changedSinceLastCheck = false; + return true; + } + return false; + } + +private: + + LongTermTimer _onTimer; + LongTermTimer _offTimer; + + LongTermTimer::Duration _prevDuration{}; + size_t _changeToOnCount{}; + size_t _changeToOffCount{}; + + bool _changedSinceLastCheck{}; + +}; // class LongTermOnOffTimer diff --git a/src/src/Helpers/MDNS_Helper.cpp b/src/src/Helpers/MDNS_Helper.cpp index 48aeaa365..bef39c021 100644 --- a/src/src/Helpers/MDNS_Helper.cpp +++ b/src/src/Helpers/MDNS_Helper.cpp @@ -3,11 +3,11 @@ #if FEATURE_MDNS #include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/ESPEasyEth.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../../ESPEasy/net/eth/ESPEasyEth.h" +#include "../../ESPEasy/net/ESPEasyNetwork.h" -#include "../Globals/ESPEasyWiFiEvent.h" -#include "../Globals/NetworkState.h" +#include "../../ESPEasy/net/Globals/ESPEasyWiFiEvent.h" +#include "../../ESPEasy/net/Globals/NetworkState.h" #include "../Globals/Services.h" #include "../Globals/Settings.h" @@ -16,8 +16,7 @@ void set_mDNS() { #if FEATURE_MDNS - - if (!WiFiEventData.WiFiServicesInitialized()) { return; } + if (!ESPEasy::net::NetworkConnected(true)) return; update_mDNS(); #endif // if FEATURE_MDNS diff --git a/src/src/Helpers/Memory.cpp b/src/src/Helpers/Memory.cpp index 3fd4a35ad..287b31e2d 100644 --- a/src/src/Helpers/Memory.cpp +++ b/src/src/Helpers/Memory.cpp @@ -64,7 +64,7 @@ bool allocatedOnStack(const void *address) { /********************************************************************************************\ Get free system mem \*********************************************************************************************/ -unsigned long FreeMem() +uint32_t FreeMem() { #if defined(ESP8266) return system_get_free_heap_size(); @@ -75,7 +75,7 @@ unsigned long FreeMem() } #ifdef USE_SECOND_HEAP -unsigned long FreeMem2ndHeap() +uint32_t FreeMem2ndHeap() { HeapSelectIram ephemeral; @@ -85,9 +85,9 @@ unsigned long FreeMem2ndHeap() #endif // ifdef USE_SECOND_HEAP -unsigned long getMaxFreeBlock() +uint32_t getMaxFreeBlock() { - const unsigned long freemem = FreeMem(); + const uint32_t freemem = FreeMem(); // computing max free block is a rather extensive operation, so only perform when free memory is already low. if (freemem < 6144) { diff --git a/src/src/Helpers/Memory.h b/src/src/Helpers/Memory.h index 9f7faab34..c795ed80d 100644 --- a/src/src/Helpers/Memory.h +++ b/src/src/Helpers/Memory.h @@ -38,13 +38,13 @@ bool allocatedOnStack(const void *address); /********************************************************************************************\ Get free system mem \*********************************************************************************************/ -unsigned long FreeMem(); +uint32_t FreeMem(); #ifdef USE_SECOND_HEAP -unsigned long FreeMem2ndHeap(); +uint32_t FreeMem2ndHeap(); #endif -unsigned long getMaxFreeBlock(); +uint32_t getMaxFreeBlock(); #endif diff --git a/src/src/Helpers/Misc.cpp b/src/src/Helpers/Misc.cpp index 6ce9bf7dc..a536c13a5 100644 --- a/src/src/Helpers/Misc.cpp +++ b/src/src/Helpers/Misc.cpp @@ -7,6 +7,7 @@ #include "../ESPEasyCore/Serial.h" #include "../Globals/ESPEasy_time.h" #include "../Globals/Statistics.h" +#include "../Helpers/_CPlugin_init.h" #include "../Helpers/ESPEasy_FactoryDefault.h" #include "../Helpers/ESPEasy_Storage.h" #include "../Helpers/Numerical.h" @@ -68,6 +69,37 @@ void delayBackground(unsigned long dsdelay) } } +/********************************************************************************************\ + Toggle network enabled state + \*********************************************************************************************/ +bool setNetworkEnableStatus(ESPEasy::net::networkIndex_t networkIndex, bool enabled) +{ + if (!validNetworkIndex(networkIndex)) { return false; } + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("setNetworkEnableStatus")); + #endif // ifndef BUILD_NO_RAM_TRACKER + + // Only enable network if it has a network interface configured + if (Settings.getNWPluginID_for_network(networkIndex) != ESPEasy::net::INVALID_NW_PLUGIN_ID || !enabled) { + struct EventStruct TempEvent; + TempEvent.NetworkIndex = networkIndex; + String dummy; + + if (!enabled) { + ESPEasy::net::NWPluginCall(NWPlugin::Function::NWPLUGIN_EXIT, &TempEvent, dummy); + } + Settings.setNetworkEnabled(networkIndex, enabled); + if (enabled) { + if (!ESPEasy::net::NWPluginCall(NWPlugin::Function::NWPLUGIN_INIT, &TempEvent, dummy)) + return false; + } + + return true; + } + return false; +} + + /********************************************************************************************\ Toggle controller enabled state \*********************************************************************************************/ @@ -80,8 +112,24 @@ bool setControllerEnableStatus(controllerIndex_t controllerIndex, bool enabled) // Only enable controller if it has a protocol configured if ((Settings.Protocol[controllerIndex] != 0) || !enabled) { + struct EventStruct TempEvent; + TempEvent.ControllerIndex = controllerIndex; + String dummy; + if (!enabled) { + CPluginCall(CPlugin::Function::CPLUGIN_EXIT, &TempEvent, dummy); + } + Settings.ControllerEnabled[controllerIndex] = enabled; - return true; + const protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(controllerIndex); + + if (validProtocolIndex(ProtocolIndex)) { + struct EventStruct TempEvent; + TempEvent.ControllerIndex = controllerIndex; + String dummy; + const CPlugin::Function cfunction = + enabled ? CPlugin::Function::CPLUGIN_INIT : CPlugin::Function::CPLUGIN_EXIT; + return do_CPluginCall(ProtocolIndex, cfunction, &TempEvent, dummy) || CPlugin::Function::CPLUGIN_EXIT == cfunction; + } } return false; } @@ -98,24 +146,27 @@ bool setTaskEnableStatus(struct EventStruct *event, bool enabled) // Only enable task if it has a Plugin configured if (validPluginID(Settings.getPluginID_for_task(event->TaskIndex)) || !enabled) { - String dummy; + if (enabled != Settings.TaskDeviceEnabled[event->TaskIndex]) + { + String dummy; - if (!enabled) { - PluginCall(PLUGIN_EXIT, event, dummy); - } + if (!enabled) { + PluginCall(PLUGIN_EXIT, event, dummy); + } - // Toggle enable/disable state via command - // FIXME TD-er: Should this be a 'runtime' change, or actually change the intended state? - // Settings.TaskDeviceEnabled[event->TaskIndex].enabled = enabled; - Settings.TaskDeviceEnabled[event->TaskIndex] = enabled; + // Toggle enable/disable state via command + // FIXME TD-er: Should this be a 'runtime' change, or actually change the intended state? + // Settings.TaskDeviceEnabled[event->TaskIndex].enabled = enabled; + Settings.TaskDeviceEnabled[event->TaskIndex] = enabled; - if (enabled) { - // Schedule the plugin to be read. - // Do this before actual init, to allow the plugin to schedule a specific first read. - Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + 10); + if (enabled) { + // Schedule the plugin to be read. + // Do this before actual init, to allow the plugin to schedule a specific first read. + Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + 10); - if (!PluginCall(PLUGIN_INIT, event, dummy)) { - return false; + if (!PluginCall(PLUGIN_INIT, event, dummy)) { + return false; + } } } return true; @@ -144,7 +195,7 @@ void taskClear(taskIndex_t taskIndex, bool save) ExtraTaskSettings.TaskIndex = taskIndex; if (save) { - #ifndef BUILD_MINIMAL_OTA + #ifndef LIMIT_BUILD_SIZE addLog(LOG_LEVEL_INFO, F("taskClear() save settings")); #endif // ifndef BUILD_MINIMAL_OTA SaveTaskSettings(taskIndex); diff --git a/src/src/Helpers/Misc.h b/src/src/Helpers/Misc.h index 0f94ffad4..5b263a374 100644 --- a/src/src/Helpers/Misc.h +++ b/src/src/Helpers/Misc.h @@ -9,7 +9,9 @@ #include "../DataTypes/TaskIndex.h" #include "../Helpers/Scheduler.h" +#include "../../ESPEasy/net/DataTypes/NetworkIndex.h" +#define bitReadULL(value, bit) ((value) >> (bit) & 1) #define bitSetULL(value, bit) ((value) |= (1ULL << (bit))) #define bitClearULL(value, bit) ((value) &= ~(1ULL << (bit))) #define bitWriteULL(value, bit, bitvalue) (bitvalue ? bitSetULL(value, bit) : bitClearULL(value, bit)) @@ -40,6 +42,12 @@ bool remoteConfig(struct EventStruct *event, \*********************************************************************************************/ void delayBackground(unsigned long dsdelay); +/********************************************************************************************\ + Toggle network enabled state + \*********************************************************************************************/ +bool setNetworkEnableStatus(ESPEasy::net::networkIndex_t networkIndex, + bool enabled); + /********************************************************************************************\ Toggle controller enabled state diff --git a/src/src/Helpers/Modbus_RTU.cpp b/src/src/Helpers/Modbus_RTU.cpp index 7e452360c..60b018573 100644 --- a/src/src/Helpers/Modbus_RTU.cpp +++ b/src/src/Helpers/Modbus_RTU.cpp @@ -64,13 +64,14 @@ bool ModbusRTU_struct::init(const ESPEasySerialPort port, const int16_t serial_r } detected_device_description = getDevice_description(_modbus_address); - +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { String log; // = F("Modbus detected: "); log += detected_device_description; addLogMove(LOG_LEVEL_INFO, log); modbus_log_MEI(_modbus_address); } +#endif return true; } @@ -252,8 +253,10 @@ String ModbusRTU_struct::parse_modbus_MEI_response(unsigned int& object_value_in if (_recv_buf_used < 8) { // Too small. +#ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, concat(F("MEI response too small: "), _recv_buf_used)); +#endif next_object_id = 0xFF; more_follows = false; return result; diff --git a/src/src/Helpers/Network.cpp b/src/src/Helpers/NetworkStatusLED.cpp similarity index 85% rename from src/src/Helpers/Network.cpp rename to src/src/Helpers/NetworkStatusLED.cpp index 1176099d7..1db172a3c 100644 --- a/src/src/Helpers/Network.cpp +++ b/src/src/Helpers/NetworkStatusLED.cpp @@ -1,9 +1,10 @@ -#include "../Helpers/Network.h" +#include "../Helpers/NetworkStatusLED.h" #include "../../ESPEasy_common.h" -#include "../ESPEasyCore/ESPEasyWifi.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../../ESPEasy/net/wifi/ESPEasyWifi.h" + +#include "../../ESPEasy/net/ESPEasyNetwork.h" #include "../Globals/Settings.h" #include "../Globals/Services.h" @@ -18,6 +19,10 @@ # include #endif // ifdef ESP32 +#define PWMRANGE_FULL 1023 +#define STATUS_PWM_NORMALVALUE (PWMRANGE_FULL >> 2) +#define STATUS_PWM_NORMALFADE (PWMRANGE_FULL >> 8) +#define STATUS_PWM_TRAFFICRISE (PWMRANGE_FULL >> 1) /********************************************************************************************\ Status LED @@ -56,7 +61,7 @@ void statusLED(bool traffic) } else { - if (NetworkConnected()) + if (ESPEasy::net::NetworkConnected()) { long int delta = timePassedSince(gnLastUpdate); @@ -69,7 +74,7 @@ void statusLED(bool traffic) } // AP mode is active - else if (WifiIsAP(WiFi.getMode())) + else if ( ESPEasy::net::wifi::WifiIsAP(WiFi.getMode())) { nStatusValue = ((millis() >> 1) & PWMRANGE_FULL) - (PWMRANGE_FULL >> 2); // ramp up for 2 sec, 3/4 luminosity } diff --git a/src/src/Helpers/Network.h b/src/src/Helpers/NetworkStatusLED.h similarity index 62% rename from src/src/Helpers/Network.h rename to src/src/Helpers/NetworkStatusLED.h index c2b859848..ad4edb5ce 100644 --- a/src/src/Helpers/Network.h +++ b/src/src/Helpers/NetworkStatusLED.h @@ -4,10 +4,6 @@ /********************************************************************************************\ Status LED \*********************************************************************************************/ -#define PWMRANGE_FULL 1023 -#define STATUS_PWM_NORMALVALUE (PWMRANGE_FULL >> 2) -#define STATUS_PWM_NORMALFADE (PWMRANGE_FULL >> 8) -#define STATUS_PWM_TRAFFICRISE (PWMRANGE_FULL >> 1) void statusLED(bool traffic); diff --git a/src/src/Helpers/Networking.cpp b/src/src/Helpers/Networking.cpp index c91a3aaef..48ae6fd4e 100644 --- a/src/src/Helpers/Networking.cpp +++ b/src/src/Helpers/Networking.cpp @@ -7,20 +7,20 @@ #include "../DataTypes/EventValueSource.h" #include "../ESPEasyCore/ESPEasy_Log.h" #include "../ESPEasyCore/ESPEasy_backgroundtasks.h" -#include "../ESPEasyCore/ESPEasyEth.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyWifi.h" +#include "../../ESPEasy/net/eth/ESPEasyEth.h" +#include "../../ESPEasy/net/ESPEasyNetwork.h" +#include "../../ESPEasy/net/wifi/ESPEasyWifi.h" #include "../ESPEasyCore/Serial.h" -#include "../Globals/ESPEasyEthEvent.h" -#include "../Globals/ESPEasyWiFiEvent.h" +#include "../../ESPEasy/net/Globals/ESPEasyWiFiEvent.h" #include "../Globals/ESPEasy_Scheduler.h" +#include "../Globals/ESPEasy_time.h" #ifdef USES_ESPEASY_NOW -#include "../Globals/ESPEasy_now_handler.h" +# include "../Globals/ESPEasy_now_handler.h" #endif #include "../Globals/EventQueue.h" -#include "../Globals/NetworkState.h" +#include "../../ESPEasy/net/Globals/NetworkState.h" #include "../Globals/Nodes.h" #include "../Globals/ResetFactoryDefaultPref.h" #include "../Globals/Settings.h" @@ -28,7 +28,7 @@ #include "../Helpers/ESPEasy_time_calc.h" #include "../Helpers/Hardware.h" #include "../Helpers/Misc.h" -#include "../Helpers/Network.h" +#include "../Helpers/NetworkStatusLED.h" #include "../Helpers/Numerical.h" #include "../Helpers/StringConverter.h" #include "../Helpers/StringProvider.h" @@ -55,18 +55,18 @@ // SSDP // #if LWIP_VERSION_MAJOR == 2 #define IPADDR2STR(addr) (uint8_t)((uint32_t)addr & 0xFF), (uint8_t)(((uint32_t)addr >> 8) & 0xFF), \ - (uint8_t)(((uint32_t)addr >> 16) & 0xFF), (uint8_t)(((uint32_t)addr >> 24) & 0xFF) + (uint8_t)(((uint32_t)addr >> 16) & 0xFF), (uint8_t)(((uint32_t)addr >> 24) & 0xFF) // #endif #include #ifdef ESP8266 -#include -#include -#include -#include -#endif +# include +# include +# include +# include +#endif // ifdef ESP8266 #ifdef SUPPORT_ARP # include @@ -75,13 +75,9 @@ # include # include -void _etharp_gratuitous_func(struct netif *netif) { - etharp_gratuitous(netif); -} +void _etharp_gratuitous_func(struct netif *netif) { etharp_gratuitous(netif); } -void etharp_gratuitous_r(struct netif *netif) { - tcpip_callback_with_block((tcpip_callback_fn)_etharp_gratuitous_func, netif, 0); -} +void etharp_gratuitous_r(struct netif *netif) { tcpip_callback_with_block((tcpip_callback_fn)_etharp_gratuitous_func, netif, 0); } # endif // ifdef ESP32 @@ -99,66 +95,6 @@ void etharp_gratuitous_r(struct netif *netif) { #include -/*********************************************************************************************\ - Syslog client -\*********************************************************************************************/ -void sendSyslog(uint8_t logLevel, const String& message) -{ - if ((Settings.Syslog_IP[0] != 0) && NetworkConnected()) - { - IPAddress broadcastIP(Settings.Syslog_IP[0], Settings.Syslog_IP[1], Settings.Syslog_IP[2], Settings.Syslog_IP[3]); - - FeedSW_watchdog(); - - if (portUDP.beginPacket(broadcastIP, Settings.SyslogPort) == 0) { - // problem resolving the hostname or port - return; - } - unsigned int prio = Settings.SyslogFacility * 8; - - if (logLevel == LOG_LEVEL_ERROR) { - prio += 3; // syslog error - } - else if (logLevel == LOG_LEVEL_INFO) { - prio += 5; // syslog notice - } - else { - prio += 7; - } - - // An RFC3164 compliant message must be formated like : "[TimeStamp ]Hostname TaskName: Message" - - // Using Settings.Name as the Hostname (Hostname must NOT content space) - { - String header; - header += '<'; - header += prio; - header += '>'; - header += NetworkCreateRFCCompliantHostname(true); - header += F(" EspEasy: "); - header.trim(); - header.replace(' ', '_'); - - #ifdef ESP8266 - portUDP.write(header.c_str(), header.length()); - #endif // ifdef ESP8266 - #ifdef ESP32 - portUDP.write(reinterpret_cast(header.c_str()), header.length()); - #endif // ifdef ESP32 - } - - #ifdef ESP8266 - portUDP.write(message.c_str(), message.length()); - #endif // ifdef ESP8266 - #ifdef ESP32 - portUDP.write(reinterpret_cast(message.c_str()), message.length()); - #endif // ifdef ESP32 - - portUDP.endPacket(); - FeedSW_watchdog(); - delay(0); - } -} #if FEATURE_ESPEASY_P2P @@ -204,11 +140,11 @@ void sendUDP(uint8_t unit, const uint8_t *data, uint8_t size) # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { - addLogMove(LOG_LEVEL_DEBUG_MORE, strformat( - F("UDP : Send UDP message to %d (%s)"), - unit, - remoteNodeIP.toString().c_str() - )); + addLogMove(LOG_LEVEL_DEBUG_MORE, strformat( + F("UDP : Send UDP message to %d (%s)"), + unit, + remoteNodeIP.toString().c_str() + )); } # endif // ifndef BUILD_NO_DEBUG @@ -228,30 +164,42 @@ void updateUDPport(bool force) { static uint16_t lastUsedUDPPort = 0; - if (!force && Settings.UDPPort == lastUsedUDPPort) { + if (!force && (Settings.UDPPort == lastUsedUDPPort)) { return; } - if (lastUsedUDPPort != 0) { - portUDP.stop(); - lastUsedUDPPort = 0; - } - - if (!NetworkConnected()) { - return; + // FIXME TD-er: For now, all interfaces that can handle UDP are the same as those that need the webserver to run + // May need a separate NWPlugin function for check if default interface can handle UDP p2p packets + // Or we may need to look into AsyncUDP as that allows to send to specific interfaces. + const bool connected = ESPEasy::net::NWPluginCall(NWPlugin::Function::NWPLUGIN_WEBSERVER_SHOULD_RUN); + //const bool connected = ESPEasy::net::NetworkConnected(); + if (!connected || lastUsedUDPPort != 0) { + if (lastUsedUDPPort != 0) { + portUDP.stop(); + lastUsedUDPPort = 0; +#ifndef BUILD_NO_DEBUG + addLogMove(LOG_LEVEL_INFO, concat(F("UDP : Stop listening on port "), Settings.UDPPort)); +#endif + } + if (!connected) { + return; + } } if (Settings.UDPPort != 0) { if (portUDP.begin(Settings.UDPPort) == 0) { +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_ERROR)) { addLogMove(LOG_LEVEL_ERROR, concat(F("UDP : Cannot bind to ESPEasy p2p UDP port "), Settings.UDPPort)); } +#endif } else { lastUsedUDPPort = Settings.UDPPort; - +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove(LOG_LEVEL_INFO, concat(F("UDP : Start listening on port "), Settings.UDPPort)); } +#endif } } } @@ -260,10 +208,15 @@ void updateUDPport(bool force) Check UDP messages (ESPEasy propiertary protocol) \*********************************************************************************************/ boolean runningUPDCheck = false; + void checkUDP() { - if (!NetworkConnected()) + if (!ESPEasy::net::NetworkConnected(true)) { + portUDP.PR_9453_FLUSH_TO_CLEAR(); + portUDP.stop(); return; + } + if (Settings.UDPPort == 0) { return; } @@ -273,7 +226,7 @@ void checkUDP() } START_TIMER - runningUPDCheck = true; + runningUPDCheck = true; // UDP events int packetSize = portUDP.parsePacket(); @@ -302,7 +255,7 @@ void checkUDP() // and then crash due to memory allocation failures if ((packetSize >= 2) && (packetSize < UDP_PACKETSIZE_MAX)) { // Allocate buffer to process packet. - // Resize it to be 1 byte larger so we can 0-terminate it + // Resize it to be 1 byte larger so we can 0-terminate it // in case it is some plain text string std::vector packetBuffer; packetBuffer.resize(packetSize + 1); @@ -318,14 +271,14 @@ void checkUDP() # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLogMove(LOG_LEVEL_DEBUG, - strformat(F("UDP : %s Command: %s"), - formatIP(remoteIP, true).c_str(), - wrapWithQuotesIfContainsParameterSeparatorChar(String(&packetBuffer[0])).c_str() - )); + addLogMove(LOG_LEVEL_DEBUG, + strformat(F("UDP : %s Command: %s"), + formatIP(remoteIP, true).c_str(), + wrapWithQuotesIfContainsParameterSeparatorChar(String(&packetBuffer[0])).c_str() + )); } - #endif - ExecuteCommand_all({EventValueSource::Enum::VALUE_SOURCE_SYSTEM, &packetBuffer[0]}, true); + # endif // ifndef BUILD_NO_DEBUG + ExecuteCommand_all({ EventValueSource::Enum::VALUE_SOURCE_UDP, &packetBuffer[0] }, true); } else { @@ -338,6 +291,7 @@ void checkUDP() break; } int copy_length = sizeof(NodeStruct); + // Older versions sent 80 bytes, regardless of the size of NodeStruct // Make sure the extra data received is ignored as it was also not initialized if (len == 80) { @@ -356,16 +310,16 @@ void checkUDP() # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { - addLogMove(LOG_LEVEL_DEBUG_MORE, - strformat(F("UDP : %s (%d) %s,%s,%d"), - formatIP(remoteIP).c_str(), - received.unit, - received.STA_MAC().toString().c_str(), - formatIP(received.IP(), true).c_str(), - received.unit)); + addLogMove(LOG_LEVEL_DEBUG_MORE, + strformat(F("UDP : %s (%d) %s,%s,%d"), + formatIP(remoteIP).c_str(), + received.unit, + received.STA_MAC().toString().c_str(), + formatIP(received.IP(), true).c_str(), + received.unit)); } -#endif // ifndef BUILD_NO_DEBUG +# endif // ifndef BUILD_NO_DEBUG } break; } @@ -377,8 +331,9 @@ void checkUDP() TempEvent.Par1 = remoteIP[3]; TempEvent.Par2 = len; String dummy; + // TD-er: Disabled the PLUGIN_UDP_IN call as we don't have any plugin using this. - //PluginCall(PLUGIN_UDP_IN, &TempEvent, dummy); + // PluginCall(PLUGIN_UDP_IN, &TempEvent, dummy); CPluginCall(CPlugin::Function::CPLUGIN_UDP_IN, &TempEvent); break; } @@ -406,7 +361,8 @@ String formatUnitToIPAddress(uint8_t unit, uint8_t formatCode) { IPAddress unitIPAddress = getIPAddressForUnit(unit); if (unitIPAddress[0] == 0) { // Invalid? - switch (formatCode) { + switch (formatCode) + { case 1: // Return empty string { return EMPTY_STRING; @@ -430,29 +386,29 @@ IPAddress getIPAddressForUnit(uint8_t unit) { } auto it = Nodes.find(unit); - if (it == Nodes.end() || it->second.ip[0] == 0) { + if ((it == Nodes.end()) || (it->second.ip[0] == 0)) { IPAddress ip; return ip; } -#if FEATURE_USE_IPV6 -/* - // FIXME TD-er: for now do not try to send to IPv6 - if (it->second.hasIPv6_mac_based_link_local) { - return it->second.IPv6_link_local(); - } - if (it->second.hasIPv6_mac_based_link_global) { - return it->second.IPv6_global(); - } -*/ -#endif +# if FEATURE_USE_IPV6 + + /* + // FIXME TD-er: for now do not try to send to IPv6 + if (it->second.hasIPv6_mac_based_link_local) { + return it->second.IPv6_link_local(); + } + if (it->second.hasIPv6_mac_based_link_global) { + return it->second.IPv6_global(); + } + */ +# endif // if FEATURE_USE_IPV6 return it->second.IP(); } - String getNameForUnit(uint8_t unit) { auto it = Nodes.find(unit); - if (it == Nodes.end() || it->second.getNodeName().isEmpty()) { + if ((it == Nodes.end()) || it->second.getNodeName().isEmpty()) { return EMPTY_STRING; } return it->second.getNodeName(); @@ -470,7 +426,7 @@ long getAgeForUnit(uint8_t unit) { uint16_t getBuildnrForUnit(uint8_t unit) { auto it = Nodes.find(unit); - if (it == Nodes.end() || it->second.build == 0) { + if ((it == Nodes.end()) || (it->second.build == 0)) { return 0; } return it->second.build; @@ -494,11 +450,11 @@ uint8_t getTypeForUnit(uint8_t unit) { return it->second.nodeType; } -const __FlashStringHelper* getTypeStringForUnit(uint8_t unit) { +String getTypeStringForUnit(uint8_t unit) { auto it = Nodes.find(unit); if (it == Nodes.end()) { - return F(""); + return EMPTY_STRING; } return it->second.getNodeTypeDisplayString(); } @@ -513,20 +469,22 @@ void refreshNodeList() Nodes.refreshNodeList(max_age_allowed, max_age); - #ifdef USES_ESPEASY_NOW - #ifdef ESP8266 + # ifdef USES_ESPEASY_NOW + # ifdef ESP8266 + // FIXME TD-er: Do not perform regular scans on ESP32 as long as we cannot scan per channel if (!Nodes.isEndpoint()) { - WifiScan(true, Nodes.getESPEasyNOW_channel()); + ESPEasy::net::wifi::WifiScan(true, Nodes.getESPEasyNOW_channel()); } - #endif - #endif + # endif // ifdef ESP8266 + # endif // ifdef USES_ESPEASY_NOW if (max_age > (0.75 * max_age_allowed)) { Scheduler.sendGratuitousARP_now(); } sendSysInfoUDP(1); - #ifdef USES_ESPEASY_NOW + # ifdef USES_ESPEASY_NOW + if (Nodes.recentlyBecameDistanceZero()) { // Send to all channels ESPEasy_now_handler.sendDiscoveryAnnounce(-1); @@ -535,7 +493,7 @@ void refreshNodeList() } ESPEasy_now_handler.sendNTPquery(); ESPEasy_now_handler.sendTraceRoute(); - #endif // ifdef USES_ESPEASY_NOW + # endif // ifdef USES_ESPEASY_NOW } /*********************************************************************************************\ @@ -565,7 +523,7 @@ void sendSysInfoUDP(uint8_t repeats) // Prepare UDP packet to send constexpr size_t data_size = sizeof(NodeStruct) + 2; - uint8_t data[data_size] = {0}; + uint8_t data[data_size] = { 0 }; data[0] = 255; data[1] = 1; memcpy(&data[2], thisNode, sizeof(NodeStruct)); @@ -611,42 +569,42 @@ void SSDP_schema() { (uint16_t)chipId & 0xff); web_server.client().print(F( - "HTTP/1.1 200 OK\r\n" - "Content-Type: text/xml\r\n" - "Connection: close\r\n" - "Access-Control-Allow-Origin: *\r\n" - "\r\n" - "" - "" - "" - "1" - "0" - "" - "http://")); + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/xml\r\n" + "Connection: close\r\n" + "Access-Control-Allow-Origin: *\r\n" + "\r\n" + "" + "" + "" + "1" + "0" + "" + "http://")); web_server.client().print(formatIP(ip)); web_server.client().print(F(":80/" - "" - "urn:schemas-upnp-org:device:BinaryLight:1" - "")); + "" + "urn:schemas-upnp-org:device:BinaryLight:1" + "")); web_server.client().print(Settings.getName()); web_server.client().print(F("" - "/" - "")); + "/" + "")); web_server.client().print(String(ESP.getChipId())); web_server.client().print(F("" - "ESP Easy" - "")); + "ESP Easy" + "")); web_server.client().print(getValue(LabelType::GIT_BUILD)); web_server.client().print(F("" - "http://www.letscontrolit.com" - "http://www.letscontrolit.com" - "http://www.letscontrolit.com" - "uuid:")); + "http://www.letscontrolit.com" + "http://www.letscontrolit.com" + "http://www.letscontrolit.com" + "uuid:")); web_server.client().print(String(uuid)); web_server.client().print(F("" - "\r\n" - "\r\n")); + "\r\n" + "\r\n")); } /********************************************************************************************\ @@ -672,7 +630,6 @@ unsigned long _notify_time; static const IPAddress SSDP_MULTICAST_ADDR(239, 255, 255, 250); - /********************************************************************************************\ Launch SSDP listener and send initial notify \*********************************************************************************************/ @@ -826,10 +783,24 @@ void SSDP_update() { _respondToAddr = _server->getRemoteAddress(); _respondToPort = _server->getRemotePort(); - typedef enum { METHOD, URI, PROTO, KEY, VALUE, ABORT } states; + typedef enum { + METHOD, + URI, + PROTO, + KEY, + VALUE, + ABORT + + } states; states state = METHOD; - typedef enum { START, MAN, ST, MX } headers; + typedef enum { + START, + MAN, + ST, + MX + + } headers; headers header = START; uint8_t cursor = 0; @@ -842,7 +813,8 @@ void SSDP_update() { (c == '\r' || c == '\n') ? cr++ : cr = 0; - switch (state) { + switch (state) + { case METHOD: if (c == ' ') { @@ -893,7 +865,8 @@ void SSDP_update() { case VALUE: if (cr == 2) { - switch (header) { + switch (header) + { case START: break; case MAN: @@ -935,14 +908,16 @@ void SSDP_update() { } break; case ABORT: - _pending = false; _delay = 0; + _pending = false; + _delay = 0; break; } } } if (_pending && timeOutReached(_process_time + _delay)) { - _pending = false; _delay = 0; + _pending = false; + _delay = 0; SSDP_send(NONE); } else if ((_notify_time == 0) || timeOutReached(_notify_time + (SSDP_INTERVAL * 1000L))) { _notify_time = millis(); @@ -959,29 +934,13 @@ void SSDP_update() { # endif // if FEATURE_SSDP #endif // if defined(ESP8266) - // ******************************************************************************** -// Return subnet range of WiFi. +// Return subnet range of active network interface. // ******************************************************************************** bool getSubnetRange(IPAddress& low, IPAddress& high) { - if (!WiFiEventData.WiFiGotIP()) { - return false; - } - - const IPAddress ip = NetworkLocalIP(); - const IPAddress subnet = NetworkSubnetMask(); - - low = ip; - high = ip; - - // Compute subnet range. - for (uint8_t i = 0; i < 4; ++i) { - if (subnet[i] != 255) { - low[i] = low[i] & subnet[i]; - high[i] = high[i] | ~subnet[i]; - } - } + low = ESPEasy::net::NetworkID(); + high = ESPEasy::net::NetworkBroadcast(); return true; } @@ -993,7 +952,6 @@ bool getSubnetRange(IPAddress& low, IPAddress& high) # include #endif // ifdef CORE_POST_2_5_0 - bool hasIPaddr() { if (useStaticIP()) { return true; } @@ -1019,22 +977,24 @@ bool hasIPaddr() { bool useStaticIP() { #if FEATURE_ETHERNET - if (active_network_medium == NetworkMedium_t::Ethernet) { - return ethUseStaticIP(); + + if (active_network_medium == ESPEasy::net::NetworkMedium_t::Ethernet) { + return ESPEasy::net::eth::ethUseStaticIP(); } - #endif - return WiFiUseStaticIP(); + #endif // if FEATURE_ETHERNET + return ESPEasy::net::wifi::WiFiUseStaticIP(); } // Check connection. Maximum timeout 500 msec. bool NetworkConnected(uint32_t timeout_ms) { - if (NetworkConnected()) return true; + if (ESPEasy::net::NetworkConnected()) return true; #ifdef USES_ESPEASY_NOW + if (isESPEasy_now_only()) { return false; } -#endif +#endif // ifdef USES_ESPEASY_NOW if (timeout_ms > 500) { timeout_ms = 500; @@ -1049,7 +1009,7 @@ bool NetworkConnected(uint32_t timeout_ms) { } // Apparently something needs network, perform check to see if it is ready now. - while (!NetworkConnected()) { + while (!ESPEasy::net::NetworkConnected()) { if (timeOutReached(timer)) { return false; } @@ -1059,7 +1019,7 @@ bool NetworkConnected(uint32_t timeout_ms) { } bool hostReachable(const IPAddress& ip) { - if (!NetworkConnected()) { return false; } + if (!ESPEasy::net::NetworkConnected()) { return false; } return true; // Disabled ping as requested here: // https://github.com/letscontrolit/ESPEasy/issues/1494#issuecomment-397872538 @@ -1093,6 +1053,7 @@ bool hostReachable(const IPAddress& ip) { } #if FEATURE_HTTP_CLIENT + bool connectClient(WiFiClient& client, const char *hostname, uint16_t port, uint32_t timeout_ms) { IPAddress ip; @@ -1106,18 +1067,19 @@ bool connectClient(WiFiClient& client, IPAddress ip, uint16_t port, uint32_t tim { START_TIMER; - if (!NetworkConnected()) { + if (!ESPEasy::net::NetworkConnected(true)) { client.stop(); return false; } -#ifndef BUILD_NO_DEBUG +# ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { addLog(LOG_LEVEL_DEBUG, strformat( - F("connectClient: '%s' port: %u"), - ip.toString().c_str(), - port)); + F("connectClient: '%s' port: %u"), + ip.toString().c_str(), + port)); } -#endif +# endif // ifndef BUILD_NO_DEBUG // In case of domain name resolution error result can be negative. // https://github.com/esp8266/Arduino/blob/18f643c7e2d6a0da9d26ff2b14c94e6536ab78c1/libraries/Ethernet/src/Dns.cpp#L44 @@ -1127,96 +1089,106 @@ bool connectClient(WiFiClient& client, IPAddress ip, uint16_t port, uint32_t tim delay(0); if (!connected) { -#ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLog(LOG_LEVEL_ERROR, strformat( - F("connectClient: connect failed to '%s' port: %u"), - ip.toString().c_str(), - port)); - } -#endif +# ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLog(LOG_LEVEL_ERROR, strformat( + F("connectClient: connect failed to '%s' port: %u"), + ip.toString().c_str(), + port)); + } +# endif // ifndef BUILD_NO_DEBUG Scheduler.sendGratuitousARP_now(); client.stop(); // Make sure to start over without some stale connection } STOP_TIMER(CONNECT_CLIENT_STATS); -#if defined(ESP32) || defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) -#else +# if defined(ESP32) || defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) +# else if (connected) { client.keepAlive(); // Use default keep alive values } -#endif // if defined(ESP32) || defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) +# endif // if defined(ESP32) || defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) return connected; } + #endif // FEATURE_HTTP_CLIENT void scrubDNS() { + /* #if FEATURE_ETHERNET - if (active_network_medium == NetworkMedium_t::Ethernet) { + + if (active_network_medium == ESPEasy::net::NetworkMedium_t::Ethernet) { if (EthEventData.EthServicesInitialized()) { setDNS(0, EthEventData.dns0_cache); setDNS(1, EthEventData.dns1_cache); } return; } - #endif - if (WiFiEventData.WiFiServicesInitialized()) { - setDNS(0, WiFiEventData.dns0_cache); - setDNS(1, WiFiEventData.dns1_cache); - } + #endif // if FEATURE_ETHERNET + */ } bool valid_DNS_address(const IPAddress& dns) { - return (/*dns.v4() != (uint32_t)0x00000000 && */ - dns != IPAddress((uint32_t)0xFD000000) && + return /*dns.v4() != (uint32_t)0x00000000 && */ + dns != IPAddress((uint32_t)0xFD000000) && #ifdef ESP32 - // Bug where IPv6 global prefix is set as DNS - // Global IPv6 prefixes currently start with 2xxx:: - (dns[0] & 0xF0) != 0x20 && -#endif - dns != INADDR_NONE); + + // Bug where IPv6 global prefix is set as DNS + // Global IPv6 prefixes currently start with 2xxx:: + (dns[0] & 0xF0) != 0x20 && +#endif // ifdef ESP32 + dns != INADDR_NONE; } bool setDNS(int index, const IPAddress& dns) { - if (index >= 2) return false; + if (index >= 2) { return false; } #ifdef ESP8266 - if(dns.isSet() && dns != WiFi.dnsIP(index)) { - dns_setserver(index, dns); - #ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, concat(F("IP : Set DNS: "), formatIP(dns))); - } - #endif - return true; - } - #endif - #ifdef ESP32 - ip_addr_t d; - d.type = IPADDR_TYPE_V4; - if (valid_DNS_address(dns) || dns == INADDR_NONE) { - // Set DNS0-Server - d.u_addr.ip4.addr = static_cast(dns); - const ip_addr_t* cur_dns = dns_getserver(index); - if (cur_dns != nullptr && cur_dns->u_addr.ip4.addr == d.u_addr.ip4.addr) { - // Still the same as before - return false; - } - dns_setserver(index, &d); + if (dns.isSet() && (dns != WiFi.dnsIP(index))) { + dns_setserver(index, dns); + # ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, concat(F("IP : Set DNS: "), formatIP(dns))); + addLogMove(LOG_LEVEL_INFO, strformat(F("IP : Set DNS(%d): %s"), index, formatIP(dns).c_str())); + } + # endif // ifndef BUILD_NO_DEBUG + return true; + } + #endif // ifdef ESP8266 + #ifdef ESP32 + + if (valid_DNS_address(dns)) { + const ip_addr_t*cur_dns = dns_getserver(index); + + if (cur_dns != nullptr) { + const IPAddress cur_dns_IP(cur_dns); + + if (valid_DNS_address(cur_dns_IP) && (cur_dns_IP == dns)) { + // Still the same as before + return false; + } + } + + // Set DNS-Server + ip_addr_t d; + dns.to_ip_addr_t(&d); + dns_setserver(index, &d); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, strformat(F("IP : Set DNS(%d): %s"), index, formatIP(dns).c_str())); } return true; } - #endif + #endif // ifdef ESP32 return false; } bool resolveHostByName(const char *aHostname, IPAddress& aResult, uint32_t timeout_ms) { START_TIMER; - if (!NetworkConnected()) { + if (!ESPEasy::net::NetworkConnected(true)) { return false; } @@ -1248,7 +1220,7 @@ bool hostReachable(const String& hostname) { } if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - addLogMove(LOG_LEVEL_ERROR, concat(F("Hostname cannot be resolved: "), hostname)); + addLogMove(LOG_LEVEL_ERROR, concat(F("Hostname cannot be resolved: "), hostname)); } return false; } @@ -1256,7 +1228,7 @@ bool hostReachable(const String& hostname) { // Create a random port for the UDP connection. // Return true when successful. bool beginWiFiUDP_randomPort(WiFiUDP& udp) { - if (!NetworkConnected()) { + if (!ESPEasy::net::NetworkConnected(true)) { return false; } unsigned int attempts = 3; @@ -1273,7 +1245,7 @@ bool beginWiFiUDP_randomPort(WiFiUDP& udp) { } void sendGratuitousARP() { - if (!NetworkConnected()) { + if (!ESPEasy::net::NetworkConnected()) { return; } #ifdef SUPPORT_ARP @@ -1353,6 +1325,17 @@ String splitURL(const String& fullURL, String& user, String& pass, String& host, return EMPTY_STRING; } + #if FEATURE_HTTP_TLS + if ((starthost > 3) && (port == 80)) { // 'Upgrade' from port 80 to port 443 for HTTPS url + String proto = fullURL.substring(0, starthost - 3); + proto.toLowerCase(); + + if (equals(proto, F("https"))) { + port = 443; + } + } + #endif // if FEATURE_HTTP_TLS + int startfile = fullURL.lastIndexOf('/'); if (startfile >= 0) { @@ -1408,6 +1391,7 @@ String extractParam(const String& authReq, const String& param, const char delim } #if FEATURE_HTTP_CLIENT + String getCNonce(const int len) { static const char alphanum[] = "0123456789" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" @@ -1457,11 +1441,11 @@ String getDigestAuth(const String& authReq, // return authorization return strformat( F("Digest username=\"%s\"" - ", realm=\"%s\"" - ", nonce=\"%s\"" - ", uri=\"%s\"" - ", algorithm=\"MD5\", qop=auth, nc=%s, cnonce=\"%s\"" - ", response=\"%s\""), + ", realm=\"%s\"" + ", nonce=\"%s\"" + ", uri=\"%s\"" + ", algorithm=\"MD5\", qop=auth, nc=%s, cnonce=\"%s\"" + ", response=\"%s\""), username.c_str(), realm.c_str(), nonce.c_str(), @@ -1471,7 +1455,8 @@ String getDigestAuth(const String& authReq, md5.toString().c_str()); // response } -#ifndef BUILD_NO_DEBUG +# ifndef BUILD_NO_DEBUG + void log_http_result(const HTTPClient& http, const String & logIdentifier, const String & host, @@ -1491,8 +1476,8 @@ void log_http_result(const HTTPClient& http, } if (loglevelActiveFor(loglevel)) { - String log = strformat(F("HTTP : %s %s %s"), - logIdentifier.c_str(), host.c_str(), HttpMethod.c_str()); + String log = strformat(F("HTTP : %s %s %s"), + logIdentifier.c_str(), host.c_str(), HttpMethod.c_str()); if (!success) { log += F("failed "); @@ -1510,7 +1495,8 @@ void log_http_result(const HTTPClient& http, addLogMove(loglevel, log); } } -#endif + +# endif // ifndef BUILD_NO_DEBUG int http_authenticate(const String& logIdentifier, WiFiClient & client, @@ -1542,17 +1528,17 @@ int http_authenticate(const String& logIdentifier, postStr, must_check_reply); } - int httpCode = 0; + int httpCode = 0; const bool hasCredentials = !user.isEmpty() && !pass.isEmpty(); if (hasCredentials) { must_check_reply = true; http.setAuthorization(user.c_str(), pass.c_str()); } else { - http.setAuthorization(""); // Clear Basic authorization -#ifdef ESP32 + http.setAuthorization(""); // Clear Basic authorization +# ifdef ESP32 http.setAuthorizationType(""); // Default type is "Basic" -#endif +# endif } http.setTimeout(timeout); http.setUserAgent(get_user_agent_string()); @@ -1562,31 +1548,31 @@ int http_authenticate(const String& logIdentifier, http.setRedirectLimit(2); } - #ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + # ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS // See: https://github.com/espressif/arduino-esp32/pull/6676 client.setTimeout((timeout + 500) / 1000); // in seconds!!!! Client *pClient = &client; pClient->setTimeout(timeout); - #else // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS - client.setTimeout(timeout); // in msec as it should be! - #endif // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + # else // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + client.setTimeout(timeout); // in msec as it should be! + # endif // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS // Add request header as fall back. // When adding another "accept" header, it may be interpreted as: // "if you have XXX, send it; or failing that, just give me what you've got." - http.addHeader(F("Accept"), F("*/*;q=0.1")); + http.addHeader(F("Accept"), F("*/*;q=0.1")); // Add client IP - http.addHeader(F("X-Forwarded-For"), formatIP(NetworkLocalIP())); + http.addHeader(F("X-Forwarded-For"), formatIP(ESPEasy::net::NetworkLocalIP())); delay(0); scrubDNS(); -#if defined(CORE_POST_2_6_0) || defined(ESP32) +# if defined(CORE_POST_2_6_0) || defined(ESP32) http.begin(client, host, port, uri, false); // HTTP -#else // if defined(CORE_POST_2_6_0) || defined(ESP32) +# else // if defined(CORE_POST_2_6_0) || defined(ESP32) http.begin(client, host, port, uri); -#endif // if defined(CORE_POST_2_6_0) || defined(ESP32) +# endif // if defined(CORE_POST_2_6_0) || defined(ESP32) const char *keys[] = { "WWW-Authenticate" }; http.collectHeaders(keys, 1); @@ -1596,17 +1582,18 @@ int http_authenticate(const String& logIdentifier, String name, value; while (splitHeaders(headerpos, header, name, value)) { - // Disabled the check to exclude "Authorization", due to: + // Disabled the check to exclude "Authorization", due to: // https://github.com/letscontrolit/ESPEasy/issues/4364 // Check was added for: https://github.com/letscontrolit/ESPEasy/issues/4355 // However, I doubt this was the actual bug. More likely the supplied credential strings were not entirely empty for whatever reason. // // Work-around to not add Authorization header since the HTTPClient code // only ignores this when base64Authorication is set. - -// if (!name.equalsIgnoreCase(F("Authorization"))) { - http.addHeader(name, value); -// } + + // if (!name.equalsIgnoreCase(F("Authorization"))) { + http.addHeader(name, value); + + // } } } @@ -1623,22 +1610,24 @@ int http_authenticate(const String& logIdentifier, if (authReq.indexOf(F("Digest")) != -1) { // Use Digest authorization +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove(LOG_LEVEL_INFO, concat(F("HTTP : Start Digest Authorization for "), host)); } - - http.setAuthorization(""); // Clear Basic authorization -#ifdef ESP32 - http.setAuthorizationType(""); // Default type is "Basic" and "Digest" is already part of the string generated by getDigestAuth() #endif + + http.setAuthorization(""); // Clear Basic authorization +# ifdef ESP32 + http.setAuthorizationType(""); // Default type is "Basic" and "Digest" is already part of the string generated by getDigestAuth() +# endif const String authorization = getDigestAuth(authReq, user, pass, F("GET"), uri, 1); http.end(); -#if defined(CORE_POST_2_6_0) || defined(ESP32) +# if defined(CORE_POST_2_6_0) || defined(ESP32) http.begin(client, host, port, uri, false); // HTTP, not HTTPS -#else // if defined(CORE_POST_2_6_0) || defined(ESP32) +# else // if defined(CORE_POST_2_6_0) || defined(ESP32) http.begin(client, host, port, uri); -#endif // if defined(CORE_POST_2_6_0) || defined(ESP32) +# endif // if defined(CORE_POST_2_6_0) || defined(ESP32) http.addHeader(F("Authorization"), authorization); @@ -1671,12 +1660,17 @@ int http_authenticate(const String& logIdentifier, } // ----------------------------------------------------------- -#ifndef BUILD_NO_DEBUG +# ifndef BUILD_NO_DEBUG log_http_result(http, logIdentifier, host + ':' + port, HttpMethod, httpCode, EMPTY_STRING); -#endif +# endif return httpCode; } +# if FEATURE_HTTP_TLS +# include +# include "../CustomBuild/Certificate_CA.h" +# endif // if FEATURE_HTTP_TLS + String send_via_http(const String& logIdentifier, uint16_t timeout, const String& user, @@ -1688,13 +1682,61 @@ String send_via_http(const String& logIdentifier, const String& header, const String& postStr, int & httpCode, - bool must_check_reply) { + bool must_check_reply + #if FEATURE_HTTP_TLS + , TLS_types tlsType + #endif // if FEATURE_HTTP_TLS + ) { WiFiClient client; + #if FEATURE_HTTP_TLS + BearSSL::WiFiClientSecure_light *client_secure = nullptr; + if (tlsType != TLS_types::NoTLS) { + client_secure = new (std::nothrow) BearSSL::WiFiClientSecure_light(2048, 2048); + if (nullptr == client_secure) { + return F("HTTPS: Could not create TLS client, out of memory"); + } + client_secure->setUtcTime_fcn(getUnixTime); + client_secure->setCfgTime_fcn(get_build_unixtime); + switch (tlsType) { + case TLS_types::NoTLS: + break; + // TODO: Implement? + // case TLS_types::TLS_PSK: + // case TLS_types::TLS_CA_CLI_CERT: + // break; + case TLS_types::TLS_FINGERPRINT: + // TODO: Implement + addLog(LOG_LEVEL_ERROR, F("HTTPS: TLS_FINGERPRINT Not implemented yet.")); + client_secure->setInsecure(); + break; + case TLS_types::TLS_CA_CERT: + // TODO: Read from filesystem? + client_secure->setTrustAnchor(Tasmota_TA, Tasmota_TA_size); + break; + case TLS_types::TLS_insecure: + client_secure->setInsecure(); + break; + } + # ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + + // See: https://github.com/espressif/arduino-esp32/pull/6676 + client_secure->setTimeout((timeout + 500) / 1000); // in seconds!!!! + Client *pClient = client_secure; + pClient->setTimeout(timeout); + # else // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + client_secure->setTimeout(timeout); // in msec as it should be! + # endif // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + } + #endif // if FEATURE_HTTP_TLS HTTPClient http; + http.setReuse(false); httpCode = http_authenticate( logIdentifier, + #if FEATURE_HTTP_TLS + tlsType != TLS_types::NoTLS ? *client_secure : + #endif // if FEATURE_HTTP_TLS client, http, timeout, @@ -1712,19 +1754,30 @@ String send_via_http(const String& logIdentifier, if ((httpCode > 0) && must_check_reply) { response = http.getString(); -#ifndef BUILD_NO_DEBUG +# ifndef BUILD_NO_DEBUG + if (!response.isEmpty()) { log_http_result(http, logIdentifier, host, HttpMethod, httpCode, response); } -#endif +# endif // ifndef BUILD_NO_DEBUG } + http.end(); + // http.end() does not call client.stop() if it is no longer connected. - // However the client may still keep its internal state which may prevent + // However the client may still keep its internal state which may prevent // future connections to the same host until there has been a connection to another host inbetween. - client.stop(); + client.stop(); + + #if FEATURE_HTTP_TLS + if (nullptr != client_secure) { + client_secure->stop(); + } + #endif // if FEATURE_HTTP_TLS + return response; } + #endif // FEATURE_HTTP_CLIENT #if FEATURE_DOWNLOAD @@ -1766,7 +1819,7 @@ bool start_downloadFile(WiFiClient & client, # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLogMove(LOG_LEVEL_DEBUG, strformat(F("downloadFile: URL: %s decoded: %s:%d%s"), + addLogMove(LOG_LEVEL_DEBUG, strformat(F("downloadFile: URL: %s decoded: %s:%d%s"), url.c_str(), host.c_str(), port, uri.c_str())); } # endif // ifndef BUILD_NO_DEBUG @@ -1794,7 +1847,7 @@ bool start_downloadFile(WiFiClient & client, ); if (httpCode != HTTP_CODE_OK) { - error = strformat(F("HTTP code: %d %s"), httpCode, url.c_str()); + error = strformat(F("HTTP code: %d %s"), httpCode, url.c_str()); addLog(LOG_LEVEL_ERROR, error); http.end(); @@ -1807,6 +1860,7 @@ bool start_downloadFile(WiFiClient & client, bool downloadFile(const String& url, String file_save, const String& user, const String& pass, String& error) { WiFiClient client; HTTPClient http; + http.setReuse(false); if (!start_downloadFile(client, http, url, file_save, user, pass, error)) { @@ -1814,7 +1868,7 @@ bool downloadFile(const String& url, String file_save, const String& user, const } if (fileExists(file_save)) { - error = concat(F("File exists: "), file_save); + error = concat(F("File exists: "), file_save); addLog(LOG_LEVEL_ERROR, error); http.end(); client.stop(); @@ -1847,7 +1901,7 @@ bool downloadFile(const String& url, String file_save, const String& user, const timeout = millis() + DOWNLOAD_FILE_TIMEOUT; if (f.write(buff, c) != c) { - error = strformat(F("Error saving file: %s %d Bytes written"), file_save.c_str(), bytesWritten); + error = strformat(F("Error saving file: %s %d Bytes written"), file_save.c_str(), bytesWritten); addLog(LOG_LEVEL_ERROR, error); http.end(); client.stop(); @@ -1859,7 +1913,7 @@ bool downloadFile(const String& url, String file_save, const String& user, const } if (timeOutReached(timeout)) { - error = concat(F("Timeout: "), file_save); + error = concat(F("Timeout: "), file_save); addLog(LOG_LEVEL_ERROR, error); delay(0); http.end(); @@ -1871,15 +1925,16 @@ bool downloadFile(const String& url, String file_save, const String& user, const f.close(); http.end(); client.stop(); - +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, strformat(F("downloadFile: %s Success"), file_save.c_str())); } +#endif return true; } http.end(); client.stop(); - error = concat(F("Failed to open file for writing: "), file_save); + error = concat(F("Failed to open file for writing: "), file_save); addLog(LOG_LEVEL_ERROR, error); return false; } @@ -1887,18 +1942,20 @@ bool downloadFile(const String& url, String file_save, const String& user, const bool downloadFirmware(String filename, String& error) { String baseurl, user, pass; + # if FEATURE_CUSTOM_PROVISIONING MakeProvisioningSettings(ProvisioningSettings); if (ProvisioningSettings.get()) { loadProvisioningSettings(*ProvisioningSettings); + if (!ProvisioningSettings->allowedFlags.allowFetchFirmware) { error = F("Not Allowed"); return false; } baseurl = ProvisioningSettings->url; - user = ProvisioningSettings->user; - pass = ProvisioningSettings->pass; + user = ProvisioningSettings->user; + pass = ProvisioningSettings->pass; } # endif // if FEATURE_CUSTOM_PROVISIONING @@ -1911,6 +1968,7 @@ bool downloadFirmware(const String& url, String& file_save, String& user, String { WiFiClient client; HTTPClient http; + error.clear(); if (!start_downloadFile(client, http, url, file_save, user, pass, error)) { @@ -1941,8 +1999,8 @@ bool downloadFirmware(const String& url, String& file_save, String& user, String timeout = millis() + DOWNLOAD_FILE_TIMEOUT; if (Update.write(buff, c) != c) { - error = strformat(F("Error saving firmware update: %s %d Bytes written"), - file_save.c_str(), bytesWritten); + error = strformat(F("Error saving firmware update: %s %d Bytes written"), + file_save.c_str(), bytesWritten); break; } bytesWritten += c; @@ -1951,7 +2009,7 @@ bool downloadFirmware(const String& url, String& file_save, String& user, String } if (timeOutReached(timeout)) { - error = concat(F("Timeout: "), file_save); + error = concat(F("Timeout: "), file_save); break; } @@ -1964,35 +2022,52 @@ bool downloadFirmware(const String& url, String& file_save, String& user, String } http.end(); client.stop(); - +#ifndef BUILD_NO_DEBUG if (error.isEmpty() && loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, strformat(F("downloadFile: %s Success"), file_save.c_str())); } +#endif uint8_t errorcode = 0; + if (!Update.end()) { errorcode = Update.getError(); -#ifdef ESP32 - const __FlashStringHelper * err_fstr = F("Unknown"); - switch (errorcode) { - case UPDATE_ERROR_OK: err_fstr = F("OK"); break; - case UPDATE_ERROR_WRITE: err_fstr = F("WRITE"); break; - case UPDATE_ERROR_ERASE: err_fstr = F("ERASE"); break; - case UPDATE_ERROR_READ: err_fstr = F("READ"); break; - case UPDATE_ERROR_SPACE: err_fstr = F("SPACE"); break; - case UPDATE_ERROR_SIZE: err_fstr = F("SIZE"); break; - case UPDATE_ERROR_STREAM: err_fstr = F("STREAM"); break; - case UPDATE_ERROR_MD5: err_fstr = F("MD5"); break; - case UPDATE_ERROR_MAGIC_BYTE: err_fstr = F("MAGIC_BYTE"); break; - case UPDATE_ERROR_ACTIVATE: err_fstr = F("ACTIVATE"); break; - case UPDATE_ERROR_NO_PARTITION: err_fstr = F("NO_PARTITION"); break; - case UPDATE_ERROR_BAD_ARGUMENT: err_fstr = F("BAD_ARGUMENT"); break; - case UPDATE_ERROR_ABORT: err_fstr = F("ABORT"); break; +# ifdef ESP32 + const __FlashStringHelper *err_fstr = F("Unknown"); + + switch (errorcode) + { + case UPDATE_ERROR_OK: err_fstr = F("OK"); + break; + case UPDATE_ERROR_WRITE: err_fstr = F("WRITE"); + break; + case UPDATE_ERROR_ERASE: err_fstr = F("ERASE"); + break; + case UPDATE_ERROR_READ: err_fstr = F("READ"); + break; + case UPDATE_ERROR_SPACE: err_fstr = F("SPACE"); + break; + case UPDATE_ERROR_SIZE: err_fstr = F("SIZE"); + break; + case UPDATE_ERROR_STREAM: err_fstr = F("STREAM"); + break; + case UPDATE_ERROR_MD5: err_fstr = F("MD5"); + break; + case UPDATE_ERROR_MAGIC_BYTE: err_fstr = F("MAGIC_BYTE"); + break; + case UPDATE_ERROR_ACTIVATE: err_fstr = F("ACTIVATE"); + break; + case UPDATE_ERROR_NO_PARTITION: err_fstr = F("NO_PARTITION"); + break; + case UPDATE_ERROR_BAD_ARGUMENT: err_fstr = F("BAD_ARGUMENT"); + break; + case UPDATE_ERROR_ABORT: err_fstr = F("ABORT"); + break; } error += concat(F(" Error: "), err_fstr); -#else +# else // ifdef ESP32 error += concat(F(" Error: "), errorcode); -#endif +# endif // ifdef ESP32 } else { if (Settings.UseRules) { eventQueue.addMove(concat(F("ProvisionFirmware#success="), file_save)); @@ -2001,6 +2076,7 @@ bool downloadFirmware(const String& url, String& file_save, String& user, String } backgroundtasks(); + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { addLog(LOG_LEVEL_ERROR, concat(F("Failed update firmware: "), error)); } @@ -2035,4 +2111,3 @@ String joinUrlFilename(const String& url, String& filename) } #endif // if FEATURE_DOWNLOAD - diff --git a/src/src/Helpers/Networking.h b/src/src/Helpers/Networking.h index ad8e39154..a7bd79a10 100644 --- a/src/src/Helpers/Networking.h +++ b/src/src/Helpers/Networking.h @@ -16,11 +16,10 @@ #endif // ifdef ESP32 #endif +#if FEATURE_HTTP_TLS +#include "../DataTypes/TLS_types.h" +#endif // if FEATURE_HTTP_TLS -/*********************************************************************************************\ - Syslog client -\*********************************************************************************************/ -void sendSyslog(uint8_t logLevel, const String& message); #if FEATURE_ESPEASY_P2P @@ -81,7 +80,7 @@ uint8_t getTypeForUnit(uint8_t unit); /*********************************************************************************************\ Get nodeTypeString for specific unit \*********************************************************************************************/ -const __FlashStringHelper* getTypeStringForUnit(uint8_t unit); +String getTypeStringForUnit(uint8_t unit); /*********************************************************************************************\ Send UDP message to specific unit (unit 255=broadcast) @@ -248,7 +247,11 @@ String send_via_http(const String& logIdentifier, const String& header, const String& postStr, int & httpCode, - bool must_check_reply); + bool must_check_reply + #if FEATURE_HTTP_TLS + , TLS_types tlsType = TLS_types::NoTLS + #endif // if FEATURE_HTTP_TLS + ); #endif // FEATURE_HTTP_CLIENT #if FEATURE_DOWNLOAD diff --git a/src/src/Helpers/PeriodicalActions.cpp b/src/src/Helpers/PeriodicalActions.cpp index 8c0f3bc22..d7dead409 100644 --- a/src/src/Helpers/PeriodicalActions.cpp +++ b/src/src/Helpers/PeriodicalActions.cpp @@ -10,25 +10,19 @@ #include "../ESPEasyCore/Controller.h" #include "../ESPEasyCore/ESPEasyGPIO.h" #include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyWifi.h" +#include "../../ESPEasy/net/ESPEasyNetwork.h" #include "../ESPEasyCore/ESPEasyRules.h" #include "../ESPEasyCore/Serial.h" -#include "../Globals/ESPEasyWiFiEvent.h" -#if FEATURE_ETHERNET -#include "../Globals/ESPEasyEthEvent.h" -#endif #include "../Globals/ESPEasy_Scheduler.h" #include "../Globals/ESPEasy_time.h" #include "../Globals/EventQueue.h" #include "../Globals/MainLoopCommand.h" #include "../Globals/MQTT.h" -#include "../Globals/NetworkState.h" #include "../Globals/RTC.h" #include "../Globals/Services.h" #include "../Globals/Settings.h" #include "../Globals/Statistics.h" -#include "../Globals/WiFi_AP_Candidates.h" +#include "../../ESPEasy/net/Globals/WiFi_AP_Candidates.h" #include "../Helpers/ESPEasyRTC.h" #include "../Helpers/FS_Helper.h" #include "../Helpers/Hardware_temperature_sensor.h" @@ -39,6 +33,14 @@ #include "../Helpers/StringGenerator_WiFi.h" #include "../Helpers/StringProvider.h" +#include "../../ESPEasy/net/wifi/ESPEasyWifi.h" +#include "../../ESPEasy/net/Globals/ESPEasyWiFi.h" +#include "../../ESPEasy/net/Globals/ESPEasyWiFiEvent.h" +#include "../../ESPEasy/net/Globals/NetworkState.h" +#include "../../ESPEasy/net/Globals/NWPlugins.h" +#include "../../ESPEasy/net/wifi/WiFi_State.h" + + #ifdef USES_C015 #include "../../ESPEasy_fdwdecl.h" #endif @@ -54,6 +56,26 @@ void run50TimesPerSecond() { String dummy; + { + // Do network calls first, so any needed checks or updates are done + // before any controller may need to use the network +#ifdef ESP32 + static const NetworkInterface *lastDefaultInterface = nullptr; + NetworkInterface * currentDefaultInterface = Network.getDefaultInterface(); + if (nonDefaultNetworkInterface_gotIP || lastDefaultInterface != currentDefaultInterface) { + nonDefaultNetworkInterface_gotIP = false; + ESPEasy::net::NWPluginCall(NWPlugin::Function::NWPLUGIN_PRIORITY_ROUTE_CHANGED, 0, dummy); + lastDefaultInterface = currentDefaultInterface; + } +#endif + + START_TIMER; + ESPEasy::net::NWPluginCall(NWPlugin::Function::NWPLUGIN_FIFTY_PER_SECOND, 0, dummy); + STOP_TIMER(NWPLUGIN_CALL_50PS); + } + { + ESPEasy::net::processNetworkEvents(); + } { START_TIMER; PluginCall(PLUGIN_FIFTY_PER_SECOND, 0, dummy); @@ -64,6 +86,7 @@ void run50TimesPerSecond() { CPluginCall(CPlugin::Function::CPLUGIN_FIFTY_PER_SECOND, 0, dummy); STOP_TIMER(CPLUGIN_CALL_50PS); } + processNextEvent(); } @@ -78,6 +101,11 @@ void run10TimesPerSecond() { GPIO_Monitor10xSec(); STOP_TIMER(PLUGIN_CALL_10PSU); } + { + START_TIMER; + ESPEasy::net::NWPluginCall(NWPlugin::Function::NWPLUGIN_TEN_PER_SECOND, 0, dummy); + STOP_TIMER(NWPLUGIN_CALL_10PS); + } { START_TIMER; PluginCall(PLUGIN_TEN_PER_SECOND, 0, dummy); @@ -96,11 +124,13 @@ void run10TimesPerSecond() { } #ifdef USES_C015 - if (NetworkConnected()) { + if (ESPEasy::net::NetworkConnected()) { Blynk_Run_c015(); } #endif - if (!UseRTOSMultitasking) { + if (!UseRTOSMultitasking && + (ESPEasy::net::NetworkConnected() || ESPEasy::net::wifi::wifiAPmodeActivelyUsed())) { + // FIXME TD-er: What about client connected via AP? START_TIMER web_server.handleClient(); STOP_TIMER(WEBSERVER_HANDLE_CLIENT); @@ -121,22 +151,23 @@ void runOncePerSecond() RTC.flashDayCounter=0; saveToRTC(); dailyResetCounter=0; - #ifndef BUILD_MINIMAL_OTA + #ifndef LIMIT_BUILD_SIZE addLog(LOG_LEVEL_INFO, F("SYS : Reset 24h counters")); #endif } - if (Settings.ConnectionFailuresThreshold) - if (WiFiEventData.connectionFailures > Settings.ConnectionFailuresThreshold) + if (Settings.ConnectionFailuresThreshold) { + auto data = ESPEasy::net::getDefaultRoute_NWPluginData_static_runtime(); + if (data && data->getConnectionFailures() > Settings.ConnectionFailuresThreshold) delayedReboot(60, IntendedRebootReason_e::DelayedReboot); - + } if (cmd_within_mainloop != 0) { switch (cmd_within_mainloop) { case CMD_WIFI_DISCONNECT: { - WifiDisconnect(); + ESPEasy::net::wifi::WifiDisconnect(); break; } case CMD_REBOOT: @@ -170,6 +201,15 @@ void runOncePerSecond() PluginCall(PLUGIN_ONCE_A_SECOND, 0, dummy); // unsigned long elapsed = micros() - start; +#if FEATURE_NETWORK_STATS + for (ESPEasy::net::networkIndex_t x = 0; x < NETWORK_MAX; x++) { + if (Settings.getNetworkEnabled(x)) { + EventStruct tempEvent; + tempEvent.NetworkIndex = x; + ESPEasy::net::NWPluginCall(NWPlugin::Function::NWPLUGIN_RECORD_STATS, &tempEvent); + } + } +#endif // I2C Watchdog feed if (Settings.WDI2CAddress != 0) @@ -183,7 +223,7 @@ void runOncePerSecond() #if FEATURE_MDNS #ifdef ESP8266 // Allow MDNS processing - if (NetworkConnected()) { + if (ESPEasy::net::NetworkConnected()) { MDNS.announce(); } #endif @@ -206,36 +246,45 @@ void runEach30Seconds() checkRAMtoLog(); #endif wdcounter++; - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = strformat( - F("WD : Uptime %d ConnectFailures %u FreeMem %u"), - getUptimeMinutes(), - WiFiEventData.connectionFailures, - FreeMem()); - bool logWiFiStatus = true; - #if FEATURE_ETHERNET - if(active_network_medium == NetworkMedium_t::Ethernet) { - logWiFiStatus = false; - log += F( " EthSpeedState "); - log += getValue(LabelType::ETH_SPEED_STATE); - log += F(" ETH status: "); - log += EthEventData.ESPEasyEthStatusToString(); - } - #endif // if FEATURE_ETHERNET - if (logWiFiStatus) { - log += strformat( - F(" WiFiStatus: %s ESPeasy internal wifi status: %s"), - ArduinoWifiStatusToString(WiFi.status()).c_str(), - WiFiEventData.ESPeasyWifiStatusToString().c_str()); - } -// log += F(" ListenInterval "); -// log += WiFi.getListenInterval(); - addLogMove(LOG_LEVEL_INFO, log); + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + auto data = ESPEasy::net::getDefaultRoute_NWPluginData_static_runtime(); + if (!data) { + addLogMove(LOG_LEVEL_INFO, strformat( + F("WD : Uptime %d FreeMem %u"), + getUptimeMinutes(), + FreeMem())); + } else { + String log = strformat( + F("WD : Uptime %d ConnectFailures %u FreeMem %u"), + getUptimeMinutes(), + data->getConnectionFailures(), + FreeMem()); + bool logWiFiStatus = true; + #if FEATURE_ETHERNET + if(active_network_medium == ESPEasy::net::NetworkMedium_t::Ethernet) { + logWiFiStatus = false; + log += F( " EthSpeedState "); + log += getValue(LabelType::ETH_SPEED_STATE); +// log += F(" ETH status: "); +// log += EthEventData.ESPEasyEthStatusToString(); + } + #endif // if FEATURE_ETHERNET + if (logWiFiStatus) { + log += strformat( + F(" WiFiStatus: %s ESPeasy internal wifi status: %s (%s)"), + ArduinoWifiStatusToString(WiFi.status()).c_str(), + FsP(ESPEasy::net::wifi::toString(ESPEasyWiFi.getState())), + data->statusToString().c_str()); + } + // log += F(" ListenInterval "); + // log += WiFi.getListenInterval(); + addLogMove(LOG_LEVEL_INFO, log); #if FEATURE_DEFINE_SERIAL_CONSOLE_PORT -// addLogMove(LOG_LEVEL_INFO, ESPEASY_SERIAL_CONSOLE_PORT.getLogString()); + // addLogMove(LOG_LEVEL_INFO, ESPEASY_SERIAL_CONSOLE_PORT.getLogString()); #endif + } } - WiFi_AP_Candidates.purge_expired(); + ESPEasy::net::wifi::WiFi_AP_Candidates.purge_expired(); #if FEATURE_ESPEASY_P2P sendSysInfoUDP(1); refreshNodeList(); @@ -252,9 +301,9 @@ void runEach30Seconds() #endif // if FEATURE_SSDP #endif #if FEATURE_ADC_VCC - if (!WiFiEventData.wifiConnectInProgress) { +// if (!WiFiEventData.wifiConnectInProgress) { vcc = ESP.getVcc() / 1000.0f; - } +// } #endif #if FEATURE_REPORTING @@ -273,10 +322,6 @@ void scheduleNextMQTTdelayQueue() { } void schedule_all_MQTTimport_tasks() { - controllerIndex_t ControllerIndex = firstEnabledMQTT_ControllerIndex(); - - if (!validControllerIndex(ControllerIndex)) { return; } - constexpr pluginID_t PLUGIN_MQTT_IMPORT(PLUGIN_ID_MQTT_IMPORT); deviceIndex_t DeviceIndex = getDeviceIndex(PLUGIN_MQTT_IMPORT); // Check if P037_MQTTimport is present in the build @@ -287,7 +332,10 @@ void schedule_all_MQTTimport_tasks() { // Schedule a call to each enabled MQTT import plugin to notify the broker connection state EventStruct event(task); event.Par1 = MQTTclient_connected ? 1 : 0; - Scheduler.schedule_plugin_task_event_timer(DeviceIndex, PLUGIN_MQTT_CONNECTION_STATE, std::move(event)); + Scheduler.schedule_plugin_task_event_timer( + task, + PLUGIN_MQTT_CONNECTION_STATE, + std::move(event)); } } } @@ -326,8 +374,9 @@ void processMQTTdelayQueue() { } else if (!handled) { if (MQTTclient.publish(element->_topic.c_str(), element->_payload.c_str(), element->_retained)) { - if (WiFiEventData.connectionFailures > 0) { - --WiFiEventData.connectionFailures; + auto data = ESPEasy::net::getDefaultRoute_NWPluginData_static_runtime(); + if (data) { + data->markPublishSuccess(); } MQTTDelayHandler->markProcessed(true); } else { @@ -349,18 +398,24 @@ void processMQTTdelayQueue() { } void updateMQTTclient_connected() { - if (MQTTclient_connected != MQTTclient.connected()) { - MQTTclient_connected = !MQTTclient_connected; + const bool actual_MQTTclient_connected = MQTTclient.connected(); + if (MQTTclient_connected != actual_MQTTclient_connected) { + MQTTclient_connected = actual_MQTTclient_connected; + MQTTclient_connected_stats.set(actual_MQTTclient_connected); if (!MQTTclient_connected) { if (loglevelActiveFor(LOG_LEVEL_ERROR)) { String connectionError = F("MQTT : Connection lost, state: "); connectionError += getMQTT_state(); +#ifndef BUILD_NO_DEBUG + auto duration_ms = MQTTclient_connected_stats.getLastOnDuration_ms(); + if (duration_ms > 0) { + connectionError += concat(F(" Connected duration: "), format_msec_duration_HMS(duration_ms)); + connectionError += concat(F(" (successful) Reconnect Count: "), MQTTclient_connected_stats.getCycleCount()); + } +#endif addLogMove(LOG_LEVEL_ERROR, connectionError); } MQTTclient_must_send_LWT_connected = false; - } else { - // Now schedule all tasks using the MQTT controller. - schedule_all_MQTTimport_tasks(); } if (Settings.UseRules) { if (MQTTclient_connected) { @@ -369,6 +424,8 @@ void updateMQTTclient_connected() { eventQueue.add(F("MQTT#Disconnected")); } } + // Now schedule all tasks using the MQTT Import plugin. + schedule_all_MQTTimport_tasks(); } if (!MQTTclient_connected) { // As suggested here: https://github.com/letscontrolit/ESPEasy/issues/1356 @@ -380,9 +437,13 @@ void updateMQTTclient_connected() { } Scheduler.setIntervalTimer(SchedulerIntervalTimer_e::TIMER_MQTT); scheduleNextMQTTdelayQueue(); + #if FEATURE_MQTT_CONNECT_BACKGROUND + MQTTConnectInBackground(CONTROLLER_MAX, true); // Report state + #endif // if FEATURE_MQTT_CONNECT_BACKGROUND } void runPeriodicalMQTT() { + START_TIMER // MQTT_KEEPALIVE = 15 seconds. if (!NetworkConnected(10)) { updateMQTTclient_connected(); @@ -399,10 +460,18 @@ void runPeriodicalMQTT() { } } else { if (MQTTclient.connected()) { + #if FEATURE_MQTT_CONNECT_BACKGROUND + if (MQTT_task_data.taskHandle) { + vTaskDelete(MQTT_task_data.taskHandle); + MQTT_task_data.taskHandle = NULL; + } + MQTT_task_data.status = MQTT_connect_status_e::Disconnected; + #endif // if FEATURE_MQTT_CONNECT_BACKGROUND MQTTclient.disconnect(); updateMQTTclient_connected(); } } + STOP_TIMER(PERIODICAL_MQTT); } @@ -475,6 +544,13 @@ void flushAndDisconnectAllClients() { } #if FEATURE_MQTT if (mqttControllerEnabled && MQTTclient.connected()) { + #if FEATURE_MQTT_CONNECT_BACKGROUND + if (MQTT_task_data.taskHandle) { + vTaskDelete(MQTT_task_data.taskHandle); + MQTT_task_data.taskHandle = NULL; + } + MQTT_task_data.status = MQTT_connect_status_e::Disconnected; + #endif // if FEATURE_MQTT_CONNECT_BACKGROUND MQTTclient.disconnect(); updateMQTTclient_connected(); } @@ -488,14 +564,16 @@ void flushAndDisconnectAllClients() { void prepareShutdown(IntendedRebootReason_e reason) { - WiFiEventData.intent_to_reboot = true; +// WiFiEventData.intent_to_reboot = true; #if FEATURE_MQTT runPeriodicalMQTT(); // Flush outstanding MQTT messages #endif // if FEATURE_MQTT process_serialWriteBuffer(); flushAndDisconnectAllClients(); saveUserVarToRTC(); - setWifiMode(WIFI_OFF); + CPluginCall(CPlugin::Function::CPLUGIN_EXIT_ALL, 0); + ESPEasy::net::NWPluginCall(NWPlugin::Function::NWPLUGIN_EXIT_ALL, 0); +// ESPEasy::net::wifi::setWifiMode(WIFI_OFF); ESPEASY_FS.end(); process_serialWriteBuffer(); delay(100); // give the node time to flush all before reboot or sleep diff --git a/src/src/Helpers/PrintToString.cpp b/src/src/Helpers/PrintToString.cpp index 9cc44a316..cbee0ec3e 100644 --- a/src/src/Helpers/PrintToString.cpp +++ b/src/src/Helpers/PrintToString.cpp @@ -19,3 +19,8 @@ const String& PrintToString::get() const { return _str; } + +String&& PrintToString::getMove() +{ + return std::move(_str); +} \ No newline at end of file diff --git a/src/src/Helpers/PrintToString.h b/src/src/Helpers/PrintToString.h index 79113710b..2a531e6d9 100644 --- a/src/src/Helpers/PrintToString.h +++ b/src/src/Helpers/PrintToString.h @@ -20,6 +20,8 @@ public: const String& get() const; + String&& getMove(); + private: String _str; diff --git a/src/src/Helpers/Rules_calculate.cpp b/src/src/Helpers/Rules_calculate.cpp index dfa39a4c3..b590d5cc5 100644 --- a/src/src/Helpers/Rules_calculate.cpp +++ b/src/src/Helpers/Rules_calculate.cpp @@ -73,6 +73,15 @@ bool RulesCalculate_t::is_unary_operator(char c) */ } +#if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES +// binary = 2-argument functions +bool RulesCalculate_t::is_binary_operator(char c) +{ + return (c >= static_cast(BinaryOperator::ArcTan2) && + c <= static_cast(BinaryOperator::FMod)); +} +#endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES + // quinary = 5-argument functions bool RulesCalculate_t::is_quinary_operator(char c) { @@ -271,6 +280,33 @@ ESPEASY_RULES_FLOAT_TYPE RulesCalculate_t::apply_unary_operator(char op, ESPEASY return ret; } +#if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES +ESPEASY_RULES_FLOAT_TYPE RulesCalculate_t::apply_binary_operator(char op, ESPEASY_RULES_FLOAT_TYPE first, ESPEASY_RULES_FLOAT_TYPE second) +{ + ESPEASY_RULES_FLOAT_TYPE ret{}; + const BinaryOperator bin_op = static_cast(op); + + const bool useDegree = angleDegree(bin_op); + switch (bin_op) { + case BinaryOperator::ArcTan2: + case BinaryOperator::ArcTan2_d: + #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + ret = atan2(first, second); + #else + ret = atan2f(first, second); + #endif + return useDegree ? degrees(ret) : ret; + case BinaryOperator::FMod: + #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + return fmod(first, second); + #else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + return fmodf(first, second); + #endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + } + return ret; +} +#endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES + ESPEASY_RULES_FLOAT_TYPE RulesCalculate_t::apply_quinary_operator(char op, ESPEASY_RULES_FLOAT_TYPE first, ESPEASY_RULES_FLOAT_TYPE second, @@ -315,7 +351,7 @@ CalculateReturnCode RulesCalculate_t::RPNCalculate(char *token) ESPEASY_RULES_FLOAT_TYPE first = pop(); ret = push(apply_operator(token[0], first, second)); - // addLog(LOG_LEVEL_INFO, strformat(F("RPNCalculate operator %c: 1: %.2f 2: %.2f"), token[0], first, second)); + // addLog(LOG_LEVEL_INFO, strformat(F("RPNCalculate operator %c: 1: %.4f 2: %.4f"), token[0], first, second)); // FIXME TD-er: Regardless whether it is an error, all code paths return ret; // if (isError(ret)) { return ret; } @@ -324,10 +360,20 @@ CalculateReturnCode RulesCalculate_t::RPNCalculate(char *token) ESPEASY_RULES_FLOAT_TYPE first = pop(); ret = push(apply_unary_operator(token[0], first)); - // addLog(LOG_LEVEL_INFO, strformat(F("RPNCalculate unary %d: 1: %.2f"), token[0], first)); + // addLog(LOG_LEVEL_INFO, strformat(F("RPNCalculate unary %d: 1: %.4f"), token[0], first)); // FIXME TD-er: Regardless whether it is an error, all code paths return ret; // if (isError(ret)) { return ret; } + #if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES + } else if (is_binary_operator(token[0]) && (token[1] == 0)) + { + const ESPEASY_RULES_FLOAT_TYPE second = pop(); + const ESPEASY_RULES_FLOAT_TYPE first = pop(); + + ret = push(apply_binary_operator(token[0], first, second)); + // addLog(LOG_LEVEL_INFO, strformat(F("RPNCalculate binary %d: 1: %.4f 2: %.4f"), token[0], first, second)); + + #endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES } else if (is_quinary_operator(token[0]) && (token[1] == 0)) { ESPEASY_RULES_FLOAT_TYPE fifth = pop(); @@ -337,14 +383,14 @@ CalculateReturnCode RulesCalculate_t::RPNCalculate(char *token) ESPEASY_RULES_FLOAT_TYPE first = pop(); ret = push(apply_quinary_operator(token[0], first, second, third, fourth, fifth)); - // addLog(LOG_LEVEL_INFO, strformat(F("RPNCalculate quinary %d: 1: %.2f 2: %.2f 3: %.2f 4: %.2f 5: %.2f"), token[0], first, second, third, fourth, fifth)); + // addLog(LOG_LEVEL_INFO, strformat(F("RPNCalculate quinary %d: 1: %.4f 2: %.4f 3: %.4f 4: %.4f 5: %.4f"), token[0], first, second, third, fourth, fifth)); } else { // Fetch next if there is any ESPEASY_RULES_FLOAT_TYPE value{}; if (validDoubleFromString(token, value)) { - // addLog(LOG_LEVEL_INFO, strformat(F("RPNCalculate push value: %.2f token: %s"), value, token)); + // addLog(LOG_LEVEL_INFO, strformat(F("RPNCalculate push value: %.4f token: %s"), value, token)); // } else { // addLog(LOG_LEVEL_INFO, strformat(F("RPNCalculate unknown token: %s"), token)); } @@ -464,7 +510,11 @@ CalculateReturnCode RulesCalculate_t::doCalculate(const char *input, ESPEASY_RUL } // If the token is any operator, op1, then: - else if (is_operator(c) || is_unary_operator(c) || is_quinary_operator(c)) + else if (is_operator(c) || is_unary_operator(c) || + #if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES + is_binary_operator(c) || + #endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES + is_quinary_operator(c)) { *(TokenPos) = 0; // Mark end of token string error = RPNCalculate(token); @@ -540,15 +590,14 @@ CalculateReturnCode RulesCalculate_t::doCalculate(const char *input, ESPEASY_RUL *(TokenPos) = 0; // Mark end of token string // addLog(LOG_LEVEL_INFO, strformat(F("doCalculate popping stack sl: %u token: %s sc: %d"), sl, token, sc)); if (sc == '(') { - ESPEASY_RULES_FLOAT_TYPE first = pop(); // Get last value from stack - error = push(first); // push back - error = push(first); // Push as a result of () - // addLog(LOG_LEVEL_INFO, strformat(F("doCalculate pop&push 2x last value: %.2f sl: %u"), first, sl)); + // const auto first = pop(); // Get last value from stack + // push(first); // push back + // addLog(LOG_LEVEL_INFO, strformat(F("doCalculate at ( last value: %.4f sl: %u"), first, sl)); } else { error = RPNCalculate(token); - // ESPEASY_RULES_FLOAT_TYPE first = pop(); // Get last value from stack - // error = push(first); // push back - // addLog(LOG_LEVEL_INFO, strformat(F("doCalculate last value on stack: %.2f sl: %u"), first, sl)); + // const auto first = pop(); // Get last value from stack + // push(first); // push back + // addLog(LOG_LEVEL_INFO, strformat(F("doCalculate last value on stack: %.4f sl: %u"), first, sl)); } TokenPos = token; @@ -567,8 +616,18 @@ CalculateReturnCode RulesCalculate_t::doCalculate(const char *input, ESPEASY_RUL pe = true; if (sl > 1) { sc = stack[sl - 2]; - if (!(is_operator(sc) || is_unary_operator(sc) || is_quinary_operator(sc))) { - sc = '('; + if (is_operator(sc)) { // Not a function call + // Don't touch + } else if (is_unary_operator(sc) || + #if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES + is_binary_operator(sc) || + #endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES + is_quinary_operator(sc)) { // Function call, so process the function too + *TokenPos = sc; + ++TokenPos; + stack[sl - 2] = '\0'; // Don't process again on stack wind-down + } else { + sc = '\0'; // Reset } } } @@ -606,6 +665,7 @@ CalculateReturnCode RulesCalculate_t::doCalculate(const char *input, ESPEASY_RUL ++strpos; } + // addLog(LOG_LEVEL_INFO, strformat(F("doCalculate final stack content 0x%s"), formatToHex_array(reinterpret_cast(stack), sl + 1).c_str())); // When there are no more tokens to read: // While there are still operator tokens in the stack: while (sl > 0) @@ -617,8 +677,12 @@ CalculateReturnCode RulesCalculate_t::doCalculate(const char *input, ESPEASY_RUL } *(TokenPos) = 0; // Mark end of token string + // addLog(LOG_LEVEL_INFO, strformat(F("doCalculate closing up stack sl: %u token: %s sc: %d"), sl, token, sc)); error = RPNCalculate(token); TokenPos = token; + // const auto first = pop(); // Get last value from stack + // push(first); // push back + // addLog(LOG_LEVEL_INFO, strformat(F("doCalculate closing, last value on stack: %.4f sl: %u"), first, sl)); if (isError(error)) { return error; } *TokenPos = sc; @@ -629,6 +693,9 @@ CalculateReturnCode RulesCalculate_t::doCalculate(const char *input, ESPEASY_RUL *(TokenPos) = 0; // Mark end of token string error = RPNCalculate(token); TokenPos = token; + // const auto first = pop(); // Get last value from stack + // push(first); // push back + // addLog(LOG_LEVEL_INFO, strformat(F("doCalculate final value on stack: %.4f sl: %u"), first, sl)); if (isError(error)) { @@ -653,6 +720,19 @@ void preProcessReplace(String& input, UnaryOperator op) { input.replace(find, replace); } +#if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES +void preProcessReplace(String& input, BinaryOperator op) { + String find = toString(op); + + if (find.isEmpty()) { return; } + find += '('; // Add opening parenthesis. + + const String replace = String(static_cast(op)) + '('; + + input.replace(find, replace); +} +#endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES + bool angleDegree(UnaryOperator op) { switch (op) { @@ -669,6 +749,22 @@ bool angleDegree(UnaryOperator op) return false; } +#if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES +bool angleDegree(BinaryOperator op) +{ + return BinaryOperator::ArcTan2_d == op; + // switch (op) // Future extension for 4 or more BinaryOperator options and multiple _d options + // { + // case BinaryOperator::ArcTan2_d: + // return true; + // case BinaryOperator::ArcTan2: + // case BinaryOperator::FMod: + // return false; + // } + // return false; +} +#endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES + const __FlashStringHelper* toString(UnaryOperator op) { switch (op) { @@ -720,6 +816,21 @@ const __FlashStringHelper* toString(UnaryOperator op) return F(""); } +#if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES +const __FlashStringHelper* toString(BinaryOperator op) +{ + switch (op) { + case BinaryOperator::ArcTan2: + return F("atan2"); + case BinaryOperator::ArcTan2_d: + return F("atan2_d"); + case BinaryOperator::FMod: + return F("fmod"); + } + return F(""); +} +#endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES + String RulesCalculate_t::preProces(const String& input) { String preprocessed = input; @@ -751,7 +862,7 @@ String RulesCalculate_t::preProces(const String& input) ,UnaryOperator::Tan ,UnaryOperator::Tan_d #endif // if FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES - ,UnaryOperator::Map + ,UnaryOperator::Map // FIXME ?Move to new QuintaryOperator enum? ,UnaryOperator::MapC }; @@ -770,6 +881,22 @@ String RulesCalculate_t::preProces(const String& input) preProcessReplace(preprocessed, op); } } + + #if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES + + const BinaryOperator operators2[] = { + BinaryOperator::ArcTan2, + BinaryOperator::ArcTan2_d, + BinaryOperator::FMod, + }; + + constexpr size_t nrOperators2 = NR_ELEMENTS(operators2); + for (size_t i = 0; i < nrOperators2; ++i) { + const BinaryOperator op = operators2[i]; + preProcessReplace(preprocessed, op); + } + #endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES + return preprocessed; } diff --git a/src/src/Helpers/Rules_calculate.h b/src/src/Helpers/Rules_calculate.h index dd66bcffb..7b7249be8 100644 --- a/src/src/Helpers/Rules_calculate.h +++ b/src/src/Helpers/Rules_calculate.h @@ -58,10 +58,23 @@ enum class UnaryOperator : uint8_t { MapC, // Map (value, lowFrom, highFrom, lowTo, highTo) and clamp to lowTo/highTo }; +#if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES +enum class BinaryOperator : uint8_t { + + ArcTan2 = 220u, // Arc Tangent 2 (radian) + ArcTan2_d, // Arc Tangent 2 (degrees) + FMod, // Float-modulo +}; +#endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES + void preProcessReplace(String & input, UnaryOperator op); bool angleDegree(UnaryOperator op); const __FlashStringHelper* toString(UnaryOperator op); +#if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES +bool angleDegree(BinaryOperator op); +const __FlashStringHelper* toString(BinaryOperator op); +#endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES class RulesCalculate_t { private: @@ -81,6 +94,10 @@ private: bool is_unary_operator(char c); + #if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES + bool is_binary_operator(char c); + #endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES + bool is_quinary_operator(char c); CalculateReturnCode push(ESPEASY_RULES_FLOAT_TYPE value); @@ -94,6 +111,12 @@ private: ESPEASY_RULES_FLOAT_TYPE apply_unary_operator(char op, ESPEASY_RULES_FLOAT_TYPE first); + #if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES + ESPEASY_RULES_FLOAT_TYPE apply_binary_operator(char op, + ESPEASY_RULES_FLOAT_TYPE first, + ESPEASY_RULES_FLOAT_TYPE second); + #endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES + ESPEASY_RULES_FLOAT_TYPE apply_quinary_operator(char op, ESPEASY_RULES_FLOAT_TYPE first, ESPEASY_RULES_FLOAT_TYPE second, diff --git a/src/src/Helpers/Scheduler.cpp b/src/src/Helpers/Scheduler.cpp index 98ecf4b25..40f027be8 100644 --- a/src/src/Helpers/Scheduler.cpp +++ b/src/src/Helpers/Scheduler.cpp @@ -84,6 +84,9 @@ void ESPEasy_Scheduler::handle_schedule() { case SchedulerTimerType_e::GPIO_timer: process_gpio_timer(timerID, timer); break; + case SchedulerTimerType_e::NWPLUGIN_TIMER_IN_e: + process_network_timer(timerID); + break; case SchedulerTimerType_e::SystemEventQueue: case SchedulerTimerType_e::IntendedReboot: diff --git a/src/src/Helpers/Scheduler.h b/src/src/Helpers/Scheduler.h index 56d08670a..cfc77b478 100644 --- a/src/src/Helpers/Scheduler.h +++ b/src/src/Helpers/Scheduler.h @@ -144,7 +144,7 @@ public: void setPluginTaskTimer(unsigned long msecFromNow, taskIndex_t taskIndex, - PluginFunctions_e function, + const PluginFunctions_e& function, int Par1, int Par2 = 0, int Par3 = 0, @@ -153,6 +153,27 @@ void setPluginTaskTimer(unsigned long msecFromNow, void process_plugin_task_timer(SchedulerTimerID timerID); + /*********************************************************************************************\ + * Network Adapter Timer (NWPLUGIN_TASKTIMER_IN) + * Can be scheduled per combo networkIndex & Par1 (20 least significant bits) + \*********************************************************************************************/ + void setNetworkInitTimer(unsigned long msecFromNow, + ESPEasy::net::networkIndex_t networkIndex); + void setNetworkExitTimer(unsigned long msecFromNow, + ESPEasy::net::networkIndex_t networkIndex); + + void setNetworkTimer(unsigned long msecFromNow, + ESPEasy::net::networkIndex_t networkIndex, + NWPlugin::Function function, + int Par1 = 0, + int Par2 = 0, + int Par3 = 0, + int Par4 = 0, + int Par5 = 0); + + void process_network_timer(SchedulerTimerID timerID); + + /*********************************************************************************************\ * Rules Timer @@ -238,7 +259,7 @@ void setPluginTaskTimer(unsigned long msecFromNow, \*********************************************************************************************/ // Note: event will be moved - void schedule_plugin_task_event_timer(deviceIndex_t DeviceIndex, + void schedule_plugin_task_event_timer(taskIndex_t taskIndex, uint8_t Function, struct EventStruct&& event); @@ -253,12 +274,12 @@ void setPluginTaskTimer(unsigned long msecFromNow, // Note: the event will be moved - void schedule_controller_event_timer(protocolIndex_t ProtocolIndex, + void schedule_controller_event_timer(controllerIndex_t ControllerIndex, uint8_t Function, struct EventStruct&& event); #if FEATURE_MQTT - void schedule_mqtt_controller_event_timer(protocolIndex_t ProtocolIndex, + void schedule_mqtt_controller_event_timer(controllerIndex_t ControllerIndex, CPlugin::Function Function, const char *c_topic, const uint8_t *b_payload, diff --git a/src/src/Helpers/Scheduler_IntervalTimer.cpp b/src/src/Helpers/Scheduler_IntervalTimer.cpp index 345d3decd..2c62b1e34 100644 --- a/src/src/Helpers/Scheduler_IntervalTimer.cpp +++ b/src/src/Helpers/Scheduler_IntervalTimer.cpp @@ -313,11 +313,9 @@ void ESPEasy_Scheduler::process_interval_timer(SchedulerTimerID timerID, unsigne break; case SchedulerIntervalTimer_e::TIMER_C023_DELAY_QUEUE: - /* #ifdef USES_C023 process_c023_delay_queue(); #endif - */ break; case SchedulerIntervalTimer_e::TIMER_C024_DELAY_QUEUE: diff --git a/src/src/Helpers/Scheduler_NetworkTimer.cpp b/src/src/Helpers/Scheduler_NetworkTimer.cpp new file mode 100644 index 000000000..7bb085edf --- /dev/null +++ b/src/src/Helpers/Scheduler_NetworkTimer.cpp @@ -0,0 +1,64 @@ +#include "../Helpers/Scheduler.h" + +#include "../DataStructs/Scheduler_NWPluginTimerID.h" + +#include "../Globals/Settings.h" + +/*********************************************************************************************\ +* Network Adapter Timer (NWPLUGIN_TASKTIMER_IN) +* Can be scheduled per combo networkIndex & Par1 (20 least significant bits) +\*********************************************************************************************/ +void ESPEasy_Scheduler::setNetworkInitTimer(unsigned long msecFromNow, + ESPEasy::net::networkIndex_t networkIndex) +{ + setNetworkTimer(msecFromNow, networkIndex, NWPlugin::Function::NWPLUGIN_INIT); +} + +void ESPEasy_Scheduler::setNetworkExitTimer(unsigned long msecFromNow, + ESPEasy::net::networkIndex_t networkIndex) +{ + setNetworkTimer(msecFromNow, networkIndex, NWPlugin::Function::NWPLUGIN_EXIT); +} + +void ESPEasy_Scheduler::setNetworkTimer(unsigned long msecFromNow, + ESPEasy::net::networkIndex_t networkIndex, + NWPlugin::Function function, + int Par1, + int Par2, + int Par3, + int Par4, + int Par5) +{ + if (!validNetworkIndex(networkIndex)) { return; } + + if (!Settings.getNetworkEnabled(networkIndex)) { return; } + + const NWPluginTimerID timerID(networkIndex, Par1, function); + + systemTimerStruct timer_data; + + timer_data.fromEvent(networkIndex, Par1, Par2, Par3, Par4, Par5); + systemTimers[timerID.mixed_id] = timer_data; + setNewTimerAt(timerID, millis() + msecFromNow); +} + +void ESPEasy_Scheduler::process_network_timer(SchedulerTimerID id) +{ + auto it = systemTimers.find(id.mixed_id); + + if (it == systemTimers.end()) { return; } + + struct EventStruct TempEvent(it->second.toNetworkEvent()); + + // TD-er: Not sure if we have to keep original source for notifications. + TempEvent.Source = EventValueSource::Enum::VALUE_SOURCE_SYSTEM; + systemTimers.erase(id.mixed_id); + + { + String dummy; + const NWPluginTimerID *tmp = reinterpret_cast(&id); + const NWPlugin::Function function = tmp->getFunction(); + ESPEasy::net::NWPluginCall(function, &TempEvent, dummy); + } + +} diff --git a/src/src/Helpers/Scheduler_PluginTaskTimer.cpp b/src/src/Helpers/Scheduler_PluginTaskTimer.cpp index 13ebddad9..accb4127e 100644 --- a/src/src/Helpers/Scheduler_PluginTaskTimer.cpp +++ b/src/src/Helpers/Scheduler_PluginTaskTimer.cpp @@ -21,7 +21,7 @@ void ESPEasy_Scheduler::setPluginTaskTimer( void ESPEasy_Scheduler::setPluginTaskTimer( unsigned long msecFromNow, taskIndex_t taskIndex, - PluginFunctions_e function, + const PluginFunctions_e& function, int Par1, int Par2, int Par3, int Par4, int Par5) { // taskIndex and par1 form a unique key that can be used to restart a timer @@ -29,7 +29,7 @@ void ESPEasy_Scheduler::setPluginTaskTimer( if (!Settings.TaskDeviceEnabled[taskIndex]) { return; } - const PluginTaskTimerID timerID(taskIndex, Par1); + const PluginTaskTimerID timerID(taskIndex, Par1, function); systemTimerStruct timer_data; diff --git a/src/src/Helpers/Scheduler_PluginTimer.cpp b/src/src/Helpers/Scheduler_PluginTimer.cpp index d015fb0f3..46c675a60 100644 --- a/src/src/Helpers/Scheduler_PluginTimer.cpp +++ b/src/src/Helpers/Scheduler_PluginTimer.cpp @@ -66,7 +66,7 @@ void ESPEasy_Scheduler::process_plugin_timer(SchedulerTimerID id) { if (validDeviceIndex(deviceIndex)) { String dummy; - PluginCall(deviceIndex, PLUGIN_DEVICETIMER_IN, &TempEvent, dummy); + do_PluginCall(deviceIndex, PLUGIN_DEVICETIMER_IN, &TempEvent, dummy); } STOP_TIMER(PLUGIN_CALL_DEVICETIMER_IN); } diff --git a/src/src/Helpers/Scheduler_RulesTimer.cpp b/src/src/Helpers/Scheduler_RulesTimer.cpp index 3c3795cf4..8c568b5ad 100644 --- a/src/src/Helpers/Scheduler_RulesTimer.cpp +++ b/src/src/Helpers/Scheduler_RulesTimer.cpp @@ -86,7 +86,7 @@ bool ESPEasy_Scheduler::pause_rules_timer(unsigned long timerIndex) { auto it = systemTimers.find(timerID.mixed_id); if (it == systemTimers.end()) { - #ifndef BUILD_MINIMAL_OTA + #ifndef LIMIT_BUILD_SIZE addLog(LOG_LEVEL_INFO, F("TIMER: no timer set")); #endif return false; @@ -96,7 +96,7 @@ bool ESPEasy_Scheduler::pause_rules_timer(unsigned long timerIndex) { if (msecTimerHandler.getTimerForId(timerID.mixed_id, timer)) { if (it->second.isPaused()) { - #ifndef BUILD_MINIMAL_OTA + #ifndef LIMIT_BUILD_SIZE addLog(LOG_LEVEL_INFO, F("TIMER: already paused")); #endif } else { diff --git a/src/src/Helpers/Scheduler_SystemEventTimer.cpp b/src/src/Helpers/Scheduler_SystemEventTimer.cpp index 492e849b0..73eea0af5 100644 --- a/src/src/Helpers/Scheduler_SystemEventTimer.cpp +++ b/src/src/Helpers/Scheduler_SystemEventTimer.cpp @@ -3,12 +3,14 @@ #include "../DataStructs/Scheduler_SystemEventQueueTimerID.h" #include "../DataStructs/TimingStats.h" -#include "../Globals/CPlugins.h" +//#include "../Globals/CPlugins.h" #include "../Globals/Device.h" #include "../Globals/NPlugins.h" #include "../Globals/RTC.h" #include "../Helpers/_Plugin_init.h" +#include "../Helpers/_CPlugin_init.h" +#include "../../ESPEasy/net/Helpers/_NWPlugin_init.h" #include "../Helpers/ESPEasyRTC.h" #include "../Helpers/ESPEasy_Storage.h" #include "../Helpers/StringConverter.h" @@ -20,13 +22,13 @@ * Proper use case is calling from a callback function, since those cannot use yield() or delay() \*********************************************************************************************/ void ESPEasy_Scheduler::schedule_plugin_task_event_timer( - deviceIndex_t DeviceIndex, + taskIndex_t taskIndex, uint8_t Function, struct EventStruct&& event) { - if (validDeviceIndex(DeviceIndex)) { + if (validDeviceIndex(getDeviceIndex_from_TaskIndex(taskIndex))) { schedule_event_timer( SchedulerPluginPtrType_e::TaskPlugin, - DeviceIndex.value, + taskIndex, Function, std::move(event)); } @@ -56,7 +58,7 @@ void ESPEasy_Scheduler::schedule_mqtt_plugin_import_event_timer( // This makes sure the relatively large event will not be in memory twice. const SystemEventQueueTimerID timerID( SchedulerPluginPtrType_e::TaskPlugin, - DeviceIndex.value, + TaskIndex, static_cast(Function)); { @@ -73,13 +75,14 @@ void ESPEasy_Scheduler::schedule_mqtt_plugin_import_event_timer( #endif // if FEATURE_MQTT void ESPEasy_Scheduler::schedule_controller_event_timer( - protocolIndex_t ProtocolIndex, + controllerIndex_t ControllerIndex, uint8_t Function, struct EventStruct&& event) { - if (validProtocolIndex(ProtocolIndex)) { + if (validControllerIndex(ControllerIndex)) { + event.ControllerIndex = ControllerIndex; schedule_event_timer( SchedulerPluginPtrType_e::ControllerPlugin, - ProtocolIndex, + ControllerIndex, Function, std::move(event)); } @@ -87,13 +90,14 @@ void ESPEasy_Scheduler::schedule_controller_event_timer( #if FEATURE_MQTT void ESPEasy_Scheduler::schedule_mqtt_controller_event_timer( - protocolIndex_t ProtocolIndex, + controllerIndex_t ControllerIndex, CPlugin::Function Function, const char *c_topic, const uint8_t *b_payload, unsigned int length) { - if (validProtocolIndex(ProtocolIndex) && c_topic && b_payload) { + if (validControllerIndex(ControllerIndex) && c_topic && b_payload) { EventStruct event; + event.ControllerIndex = ControllerIndex; const size_t topic_length = strlen_P(c_topic); // This is being called from a callback function, so do not try to allocate this on the 2nd heap, but rather on the default heap. @@ -109,7 +113,7 @@ void ESPEasy_Scheduler::schedule_mqtt_controller_event_timer( // This makes sure the relatively large event will not be in memory twice. const SystemEventQueueTimerID timerID( SchedulerPluginPtrType_e::ControllerPlugin, - ProtocolIndex, + ControllerIndex, static_cast(Function)); { @@ -188,7 +192,7 @@ void ESPEasy_Scheduler::process_system_event_queue() { switch (ptr_type) { case SchedulerPluginPtrType_e::TaskPlugin: { - const deviceIndex_t deviceIndex = deviceIndex_t::toDeviceIndex(Index); + const deviceIndex_t deviceIndex = getDeviceIndex_from_TaskIndex(Index); if (validDeviceIndex(deviceIndex)) { if (((Function != PLUGIN_READ) && @@ -198,16 +202,14 @@ void ESPEasy_Scheduler::process_system_event_queue() { // FIXME TD-er: LoadTaskSettings should only be called when needed, not pre-emptive. LoadTaskSettings(ScheduledEventQueue.front().event.TaskIndex); } - PluginCall(deviceIndex, - Function, + PluginCall(Function, &ScheduledEventQueue.front().event, tmpString); } break; } case SchedulerPluginPtrType_e::ControllerPlugin: - CPluginCall(Index, - static_cast(Function), + CPluginCall(static_cast(Function), &ScheduledEventQueue.front().event, tmpString); break; diff --git a/src/src/Helpers/SerialWriteBuffer.cpp b/src/src/Helpers/SerialWriteBuffer.cpp index 9321136f6..93a8737b6 100644 --- a/src/src/Helpers/SerialWriteBuffer.cpp +++ b/src/src/Helpers/SerialWriteBuffer.cpp @@ -1,134 +1,71 @@ #include "../Helpers/SerialWriteBuffer.h" -#include "../Helpers/Memory.h" +#include "../Helpers/StringConverter.h" -void SerialWriteBuffer_t::add(const String& line) -{ - // When the buffer is too full, try to dump at least the size of what we try to add. - const bool mustPop = _buffer.size() > _maxSize; + +String SerialWriteBuffer_t::colorize(const String& str) const { +#if FEATURE_COLORIZE_CONSOLE_LOGS + const __FlashStringHelper *format = F("%s"); + + switch (_loglevel) { - #ifdef USE_SECOND_HEAP + case LOG_LEVEL_NONE: format = F("\033[31;1m%s\033[0m"); // Red + Bold or increased intensity + break; + case LOG_LEVEL_INFO: format = F("\033[36m%s\033[0m"); // Cyan + break; + case LOG_LEVEL_ERROR: format = F("\033[31;1m%s\033[0m"); // Red + Bold or increased intensity + break; +# ifndef BUILD_NO_DEBUG + case LOG_LEVEL_DEBUG: format = F("\033[32m%s\033[0m"); // Green + break; + case LOG_LEVEL_DEBUG_MORE: format = F("\033[35m%s\033[0m"); // Purple + break; + case LOG_LEVEL_DEBUG_DEV: format = F("\033[33m%s\033[0m"); // Yellow + break; +# endif // ifndef BUILD_NO_DEBUG + default: + return str; - // Do not store in 2nd heap, std::dequeue cannot handle 2nd heap well - HeapSelectIram ephemeral; - #endif // ifdef USE_SECOND_HEAP - int roomLeft = getRoomLeft(); - - auto it = line.begin(); - - while (roomLeft > 0 && it != line.end()) { - if (mustPop) { - _buffer.pop_front(); - } - _buffer.push_back(*it); - --roomLeft; - ++it; - } } + return strformat(format, str.c_str()); +#else // if FEATURE_COLORIZE_CONSOLE_LOGS + return str; +#endif // if FEATURE_COLORIZE_CONSOLE_LOGS } -void SerialWriteBuffer_t::add(const __FlashStringHelper *line) + +size_t SerialWriteBuffer_t::write_skipping(Stream& stream) { - add(String(line)); -} + size_t bytesWritten{}; -void SerialWriteBuffer_t::add(char c) -{ - #ifdef USE_SECOND_HEAP - - // Do not store in 2nd heap, std::dequeue cannot handle 2nd heap well - HeapSelectIram ephemeral; - #endif // ifdef USE_SECOND_HEAP - - if (_buffer.size() > _maxSize) { - _buffer.pop_front(); - } - _buffer.push_back(c); -} - -void SerialWriteBuffer_t::addNewline() -{ - add('\r'); - add('\n'); -} - -void SerialWriteBuffer_t::clear() -{ - _buffer.clear(); -} - -size_t SerialWriteBuffer_t::write(Stream& stream, size_t nrBytesToWrite) -{ - size_t bytesWritten = 0; - const size_t bufferSize = _buffer.size(); - - if (bufferSize == 0) { - return bytesWritten; - } - - if (nrBytesToWrite > 0) { - if (nrBytesToWrite > bufferSize) { - nrBytesToWrite = bufferSize; - } - - while (nrBytesToWrite > 0 && !_buffer.empty()) { - uint8_t tmpBuffer[16]{}; - - size_t tmpBufferUsed = 0; - - auto it = _buffer.begin(); - - bool done = false; - - for (; tmpBufferUsed < sizeof(tmpBuffer) && - !done && - it != _buffer.end();) { - tmpBuffer[tmpBufferUsed] = (uint8_t)(*it); - - if ((*it == '\n') || - (tmpBufferUsed >= nrBytesToWrite)) { - done = true; - } - ++tmpBufferUsed; - ++it; - } - - // done = false; - const size_t written = (tmpBufferUsed == 0) ? 0 : stream.write(tmpBuffer, tmpBufferUsed); - - if (written < tmpBufferUsed) { - done = true; - } - - for (size_t i = 0; i < written; ++i) { - _buffer.pop_front(); - --nrBytesToWrite; - ++bytesWritten; - } - - if (done) { - return bytesWritten; - } - } - } + // Mark with empty line we skipped the rest of the message. + bytesWritten += stream.println(F(" ...")); + bytesWritten += stream.println(); return bytesWritten; } -int SerialWriteBuffer_t::getRoomLeft() const { - #ifdef USE_SECOND_HEAP +void SerialWriteBuffer_t::prepare_prefix() +{ + // Prepare prefix + _prefix = format_msec_duration(_timestamp); - // Do not store in 2nd heap, std::dequeue cannot handle 2nd heap well - HeapSelectIram ephemeral; - #endif // ifdef USE_SECOND_HEAP - - int roomLeft = getMaxFreeBlock(); - - if (roomLeft < 1000) { - roomLeft = 0; // Do not append to buffer. - } else if (roomLeft < 4000) { - roomLeft = 128 - _buffer.size(); // 1 buffer. + if (_loglevel == LOG_LEVEL_NONE) { + _prefix += colorize(F(" : > ")); } else { - roomLeft -= 4000; // leave some free for normal use. + #ifndef LIMIT_BUILD_SIZE + _prefix += strformat(F(" : (%d) "), FreeMem()); + #else + _prefix += ' '; + #endif // ifndef LIMIT_BUILD_SIZE + { + String loglevelDisplayString = getLogLevelDisplayString(_loglevel); + + while (loglevelDisplayString.length() < LOG_LEVEL_MAX_STRING_LENGTH) { + loglevelDisplayString += ' '; + } + _prefix += colorize(loglevelDisplayString); + } + _prefix += F(" | "); } - return roomLeft; + } diff --git a/src/src/Helpers/SerialWriteBuffer.h b/src/src/Helpers/SerialWriteBuffer.h index 7e5932df4..556561570 100644 --- a/src/src/Helpers/SerialWriteBuffer.h +++ b/src/src/Helpers/SerialWriteBuffer.h @@ -3,40 +3,25 @@ #include "../../ESPEasy_common.h" -#include +#include "../Helpers/LogStreamWriter.h" -#ifndef MAX_SERIALWRITEBUFFER_SIZE -# ifdef ESP8266 -# define MAX_SERIALWRITEBUFFER_SIZE 1024 -# endif // ifdef ESP8266 -# ifdef ESP32 -# define MAX_SERIALWRITEBUFFER_SIZE 10240 -# endif // ifdef ESP32 -#endif // ifndef MAX_SERIALWRITEBUFFER_SIZE - -class SerialWriteBuffer_t { +class SerialWriteBuffer_t : public LogStreamWriter { public: - SerialWriteBuffer_t(size_t maxSize = MAX_SERIALWRITEBUFFER_SIZE) - : _maxSize(maxSize) {} + SerialWriteBuffer_t(LogDestination log_destination) : LogStreamWriter(log_destination) {} - void add(const String& line); - void add(const __FlashStringHelper *line); - void add(char c); - - void addNewline(); - - void clear(); - - size_t write(Stream& stream, - size_t nrBytesToWrite); + private: - int getRoomLeft() const; + size_t write_skipping(Stream& stream) override; + + void prepare_prefix() override; + + + + String colorize(const String& str) const; - std::deque_buffer; - size_t _maxSize = MAX_SERIALWRITEBUFFER_SIZE; }; #endif // ifndef HELPERS_SERIALWRITEBUFFER_H diff --git a/src/src/Helpers/StringConverter.cpp b/src/src/Helpers/StringConverter.cpp index 277d2cb6f..44ba64e7c 100644 --- a/src/src/Helpers/StringConverter.cpp +++ b/src/src/Helpers/StringConverter.cpp @@ -11,7 +11,7 @@ #include "../Globals/Cache.h" #include "../Globals/CRCValues.h" #include "../Globals/Device.h" -#include "../Globals/ESPEasyWiFiEvent.h" +#include "../../ESPEasy/net/Globals/ESPEasyWiFiEvent.h" #include "../Globals/ESPEasy_time.h" #include "../Globals/MQTT.h" #include "../Globals/Plugins.h" @@ -59,6 +59,15 @@ String concat(const __FlashStringHelper * str, const __FlashStringHelper *val) { return concat(str, String(val)); } +String concat(const __FlashStringHelper * str, const char* val) { + return concat(String(str), String(val)); +} + +String concat(const String & str, const char* val) +{ + return concat(str, String(val)); +} + String concat(const char& str, const String &val) { String res(str); @@ -240,150 +249,6 @@ String formatIP(const IPAddress& ip, bool includeZone) { #endif } - -/********************************************************************************************\ - Handling HEX strings - \*********************************************************************************************/ - -// Convert max. 8 hex decimals to unsigned long -unsigned long hexToUL(const String& input_c, size_t nrHexDecimals) { - const unsigned long long resULL = hexToULL(input_c, nrHexDecimals); - return static_cast(resULL & 0xFFFFFFFFull); -} - -unsigned long hexToUL(const String& input_c) { - return hexToUL(input_c, input_c.length()); -} - -unsigned long hexToUL(const String& input_c, size_t startpos, size_t nrHexDecimals) { - return hexToUL(input_c.substring(startpos, startpos + nrHexDecimals), nrHexDecimals); -} - -// Convert max. 16 hex decimals to unsigned long long (aka uint64_t) -unsigned long long hexToULL(const String& input_c, size_t nrHexDecimals) { - size_t nr_decimals = nrHexDecimals; - - if (nr_decimals > 16) { - nr_decimals = 16; - } - const size_t inputLength = input_c.length(); - - if (nr_decimals > inputLength) { - nr_decimals = inputLength; - } else if (input_c.startsWith(F("0x"))) { // strtoull handles that prefix nicely - nr_decimals += 2; - } - return strtoull(input_c.substring(0, nr_decimals).c_str(), 0, 16); -} - -unsigned long long hexToULL(const String& input_c) { - return hexToULL(input_c, input_c.length()); -} - -unsigned long long hexToULL(const String& input_c, size_t startpos, size_t nrHexDecimals) { - return hexToULL(input_c.substring(startpos, startpos + nrHexDecimals), nrHexDecimals); -} - -void appendHexChar(uint8_t data, String& string) -{ - const char *hex_chars = "0123456789abcdef"; - string += hex_chars[(data >> 4) & 0xF]; - string += hex_chars[(data) & 0xF]; -} - -String formatToHex_array(const uint8_t* data, size_t size) -{ - String res; - res.reserve(2 * size); - for (size_t i = 0; i < size; ++i) { - appendHexChar(data[i], res); - } - return res; -} - -String formatToHex_wordarray(const uint16_t* data, size_t size) -{ - String res; - res.reserve(2 * size); - for (size_t i = 0; i < size; ++i) { - appendHexChar(data[i] << 8, res); - appendHexChar(data[i] & 0xFF, res); - } - return res; -} - -String formatToHex(unsigned long value, - const __FlashStringHelper * prefix, - unsigned int minimal_hex_digits) { - return concat(prefix, formatToHex_no_prefix(value, minimal_hex_digits)); -} - -String formatToHex(unsigned long value, - const __FlashStringHelper * prefix) { - return formatToHex(value, prefix, 0); -} - -String formatToHex(unsigned long value, unsigned int minimal_hex_digits) { - return formatToHex(value, F("0x"), minimal_hex_digits); -} - -String formatToHex_no_prefix(unsigned long value, unsigned int minimal_hex_digits) { - const String fmt = strformat(F("%%0%dX"), minimal_hex_digits); - return strformat(fmt, value); -} - -String formatHumanReadable(unsigned long value, unsigned long factor) { - String result = formatHumanReadable(value, factor, 2); - - result.replace(F(".00"), EMPTY_STRING); - return result; -} - -String formatHumanReadable(unsigned long value, unsigned long factor, int NrDecimals) { - float floatValue(value); - uint8_t steps = 0; - - while (value >= factor) { - value /= factor; - ++steps; - floatValue /= float(factor); - } - String result = toString(floatValue, NrDecimals); - - switch (steps) { - case 0: break; - case 1: result += 'k'; break; - case 2: result += 'M'; break; - case 3: result += 'G'; break; - case 4: result += 'T'; break; - default: - result += '*'; - result += factor; - result += '^'; - result += steps; - break; - } - return result; -} - -String formatToHex_decimal(unsigned long value) { - return formatToHex_decimal(value, 1); -} - -String formatToHex_decimal(unsigned long value, unsigned long factor) { - String result = formatToHex(value); - - result += F(" ("); - - if (factor > 1) { - result += formatHumanReadable(value, factor); - } else { - result += value; - } - result += ')'; - return result; -} - const __FlashStringHelper * boolToString(bool value) { return value ? F("true") : F("false"); } @@ -678,59 +543,6 @@ String wrapWithQuotesIfContainsParameterSeparatorChar(const String& text) { /*********************************************************************************************\ Format an object value pair for use in JSON. \*********************************************************************************************/ -String to_json_object_value(const __FlashStringHelper * object, - const __FlashStringHelper * value, - bool wrapInQuotes) -{ - return to_json_object_value(String(object), String(value), wrapInQuotes); -} - - -String to_json_object_value(const __FlashStringHelper * object, - const String& value, - bool wrapInQuotes) -{ - return to_json_object_value(String(object), value, wrapInQuotes); -} - -String to_json_object_value(const __FlashStringHelper * object, - String&& value, - bool wrapInQuotes) -{ - return to_json_object_value(String(object), value, wrapInQuotes); -} - -String to_json_object_value(const __FlashStringHelper * object, - int value, - bool wrapInQuotes) -{ - return to_json_object_value(String(object), value, wrapInQuotes); -} - -String to_json_object_value(const String& object, - int value, - bool wrapInQuotes) -{ - if (wrapInQuotes) { - return strformat( - F("\"%s\":\"%d\""), - object.c_str(), - value); - } - - return strformat( - F("\"%s\":%d"), - object.c_str(), - value); -} - -String to_json_object_value(const String& object, const String& value, bool wrapInQuotes) { - return strformat( - F("\"%s\":%s"), - object.c_str(), - to_json_value(value, wrapInQuotes).c_str()); -} - String to_json_value(const String& value, bool wrapInQuotes) { if (value.isEmpty()) { // Empty string @@ -1709,6 +1521,10 @@ void parseStandardConversions(String& s, bool useURLencode) { #if FEATURE_STRING_VARIABLES SMART_CONV(F("%c_ts2wday%"), get_weekday_from_timestamp(static_cast(data.arg1))) #endif // if FEATURE_STRING_VARIABLES + #ifndef LIMIT_BUILD_SIZE + SMART_CONV(F("%c_d2r%"), doubleToString(radians(data.arg1))) + SMART_CONV(F("%c_r2d%"), doubleToString(degrees(data.arg1))) + #endif // ifndef LIMIT_BUILD_SIZE #undef SMART_CONV // Conversions with 2 parameters diff --git a/src/src/Helpers/StringConverter.h b/src/src/Helpers/StringConverter.h index 6f1562bb2..d2f1a43dc 100644 --- a/src/src/Helpers/StringConverter.h +++ b/src/src/Helpers/StringConverter.h @@ -24,6 +24,10 @@ unsigned int count_newlines(const String& str); String concat(const __FlashStringHelper * str, const String &val); String concat(const __FlashStringHelper * str, const __FlashStringHelper *val); + +String concat(const __FlashStringHelper * str, const char* val); +String concat(const String & str, const char* val); + String concat(const char& str, const String &val); template @@ -93,59 +97,6 @@ bool str2ip(const char *string, String formatIP(const IPAddress& ip, bool includeZone = false); -/********************************************************************************************\ - Handling HEX strings - \*********************************************************************************************/ - -// Convert max. 8 hex decimals to unsigned long -unsigned long hexToUL(const String& input_c, - size_t nrHexDecimals); - -unsigned long hexToUL(const String& input_c); - -unsigned long hexToUL(const String& input_c, - size_t startpos, - size_t nrHexDecimals); - -// Convert max. 16 hex decimals to unsigned long long -unsigned long long hexToULL(const String& input_c, - size_t nrHexDecimals); - -unsigned long long hexToULL(const String& input_c); - -unsigned long long hexToULL(const String& input_c, - size_t startpos, - size_t nrHexDecimals); - -void appendHexChar(uint8_t data, String& string); - -// Binary data to HEX -// Returned string length will be twice the size of the data array. -String formatToHex_array(const uint8_t* data, size_t size); -String formatToHex_wordarray(const uint16_t* data, size_t size); - -String formatToHex(unsigned long value, - const __FlashStringHelper * prefix, - unsigned int minimal_hex_digits); - -String formatToHex(unsigned long value, - const __FlashStringHelper * prefix); - -String formatToHex(unsigned long value, unsigned int minimal_hex_digits = 0); - -String formatToHex_no_prefix(unsigned long value, unsigned int minimal_hex_digits = 0); - -String formatHumanReadable(unsigned long value, - unsigned long factor); - -String formatHumanReadable(unsigned long value, - unsigned long factor, - int NrDecimals); - -String formatToHex_decimal(unsigned long value); - -String formatToHex_decimal(unsigned long value, - unsigned long factor); const __FlashStringHelper * boolToString(bool value); @@ -224,30 +175,6 @@ String wrapWithQuotesIfContainsParameterSeparatorChar(const String& text); /*********************************************************************************************\ Format an object value pair for use in JSON. \*********************************************************************************************/ -String to_json_object_value(const __FlashStringHelper * object, - const __FlashStringHelper * value, - bool wrapInQuotes = false); - -String to_json_object_value(const __FlashStringHelper * object, - const String& value, - bool wrapInQuotes = false); - -String to_json_object_value(const __FlashStringHelper * object, - String&& value, - bool wrapInQuotes = false); - -String to_json_object_value(const String& object, - const String& value, - bool wrapInQuotes = false); - -String to_json_object_value(const __FlashStringHelper * object, - int value, - bool wrapInQuotes = false); - -String to_json_object_value(const String& object, - int value, - bool wrapInQuotes = false); - String to_json_value(const String& value, bool wrapInQuotes = false); diff --git a/src/src/Helpers/StringConverter_Numerical.cpp b/src/src/Helpers/StringConverter_Numerical.cpp index 18e29ad7b..d7bd1b68a 100644 --- a/src/src/Helpers/StringConverter_Numerical.cpp +++ b/src/src/Helpers/StringConverter_Numerical.cpp @@ -258,3 +258,231 @@ String floatToString(const float& value, bool string2float(const String& string, float& floatvalue) { return validFloatFromString(string, floatvalue); } + + + +/********************************************************************************************\ + Handling HEX strings + \*********************************************************************************************/ + +// Convert max. 8 hex decimals to unsigned long +unsigned long hexToUL(const String& input_c, size_t nrHexDecimals) { + const unsigned long long resULL = hexToULL(input_c, nrHexDecimals); + return static_cast(resULL & 0xFFFFFFFFull); +} + +unsigned long hexToUL(const String& input_c) { + return hexToUL(input_c, input_c.length()); +} + +unsigned long hexToUL(const String& input_c, size_t startpos, size_t nrHexDecimals) { + return hexToUL(input_c.substring(startpos, startpos + nrHexDecimals), nrHexDecimals); +} + +// Convert max. 16 hex decimals to unsigned long long (aka uint64_t) +unsigned long long hexToULL(const String& input_c, size_t nrHexDecimals) { + size_t nr_decimals = nrHexDecimals; + + if (nr_decimals > 16) { + nr_decimals = 16; + } + const size_t inputLength = input_c.length(); + + if (nr_decimals > inputLength) { + nr_decimals = inputLength; + } else if (input_c.startsWith(F("0x"))) { // strtoull handles that prefix nicely + nr_decimals += 2; + } + return strtoull(input_c.substring(0, nr_decimals).c_str(), 0, 16); +} + +unsigned long long hexToULL(const String& input_c) { + return hexToULL(input_c, input_c.length()); +} + +unsigned long long hexToULL(const String& input_c, size_t startpos, size_t nrHexDecimals) { + return hexToULL(input_c.substring(startpos, startpos + nrHexDecimals), nrHexDecimals); +} + +void appendHexChar(uint8_t data, String& string) +{ + const char *hex_chars = "0123456789abcdef"; + string += hex_chars[(data >> 4) & 0xF]; + string += hex_chars[(data) & 0xF]; +} + +String formatToHex_array(const uint8_t* data, size_t size) +{ + String res; + res.reserve(2 * size); + for (size_t i = 0; i < size; ++i) { + appendHexChar(data[i], res); + } + return res; +} + +String formatULLtoHex(const uint64_t& value, + const __FlashStringHelper * prefix, + unsigned int minimal_hex_digits) +{ + return concat(prefix, formatULLtoHex_no_prefix(value, minimal_hex_digits)); +} + +String formatULLtoHex(const uint64_t& value, + const __FlashStringHelper * prefix) +{ + return formatULLtoHex(value, prefix, 0); +} + +String formatULLtoHex(const uint64_t& value, unsigned int minimal_hex_digits) +{ + return formatULLtoHex(value, F("0x"), minimal_hex_digits); +} + +String formatULLtoHex_no_prefix(const uint64_t& value, unsigned int minimal_hex_digits) +{ + const uint32_t msb_val = static_cast(value >> 32); + const uint32_t lsb_val = static_cast(value & 0xFFFFFFFF); + String res; + res.reserve(16); + res += formatToHex_no_prefix(msb_val, 8); + res += formatToHex_no_prefix(lsb_val, 8); + while (minimal_hex_digits < res.length() && res.startsWith(F("0"))) { + res = res.substring(1); + } + return res; +} + +String formatULLtoHex_decimal(const uint64_t& value) +{ + return strformat( + F("%s (%s)"), + formatULLtoHex(value).c_str(), + ull2String(value).c_str()); +} + +String formatToHex_wordarray(const uint16_t* data, size_t size) +{ + String res; + res.reserve(4 * size); + for (size_t i = 0; i < size; ++i) { + appendHexChar(data[i] >> 8, res); + appendHexChar(data[i] & 0xFF, res); + } + return res; +} + +String formatToHex(unsigned long value, + const __FlashStringHelper * prefix, + unsigned int minimal_hex_digits) { + return concat(prefix, formatToHex_no_prefix(value, minimal_hex_digits)); +} + +String formatToHex(unsigned long value, + const __FlashStringHelper * prefix) { + return formatToHex(value, prefix, 0); +} + +String formatToHex(unsigned long value, unsigned int minimal_hex_digits) { + return formatToHex(value, F("0x"), minimal_hex_digits); +} + +String formatToHex_no_prefix(unsigned long value, unsigned int minimal_hex_digits) { + const String fmt = strformat(F("%%0%dX"), minimal_hex_digits); + return strformat(fmt, value); +} + +String formatHumanReadable(uint64_t value, + uint32_t factor) { + String result = formatHumanReadable(value, factor, 2); + + result.replace(F(".00"), EMPTY_STRING); + return result; +} + +String formatHumanReadable(uint64_t value, + uint32_t factor, + int NrDecimals) { + float floatValue(value); + uint8_t steps = 0; + + while (value >= factor) { + value /= factor; + ++steps; + floatValue /= float(factor); + } + String result = toString(floatValue, NrDecimals); + + if (steps != 0) result += ' '; + + switch (steps) { + case 0: break; + case 1: result += 'k'; break; + case 2: result += 'M'; break; + case 3: result += 'G'; break; + case 4: result += 'T'; break; + default: + result += '*'; + result += factor; + result += '^'; + result += steps; + break; + } + return result; +} + +String formatToHex_decimal(unsigned long value) { + return formatToHex_decimal(value, 1); +} + +String formatToHex_decimal(unsigned long value, unsigned long factor) { + String result = formatToHex(value); + + result += F(" ("); + + if (factor > 1) { + result += formatHumanReadable(value, factor); + } else { + result += value; + } + result += ')'; + return result; +} + + +int intFromHexChar(char a) +{ + if ((a >= 'A') && (a <= 'F')) { return a - 'A' + 10; } + else if ((a >= 'a') && (a <= 'f')) { return a - 'a' + 10; } + else if ((a >= '0') && (a <= '9')) { return a - '0'; } + return -1; +} + +String stringFromHexArray(const String& arr) +{ + const size_t arr_length = arr.length(); + String res; + + res.reserve(arr_length / 2); + uint8_t decoded_c{}; + bool secondNibble = false; + + for (size_t i = 0; i < arr_length; ++i) { + const char c = arr[i]; + + if (isHexadecimalDigit(c)) { + const int hexChar = intFromHexChar(c) & 0xF; + + if (secondNibble) { + decoded_c <<= 4; + decoded_c += hexChar; + res += static_cast(decoded_c); + secondNibble = false; + } else { + decoded_c = hexChar; + secondNibble = true; + } + } + } + return res; +} diff --git a/src/src/Helpers/StringConverter_Numerical.h b/src/src/Helpers/StringConverter_Numerical.h index ec44ba4db..e6acb300f 100644 --- a/src/src/Helpers/StringConverter_Numerical.h +++ b/src/src/Helpers/StringConverter_Numerical.h @@ -52,4 +52,77 @@ String floatToString(const float& value, unsigned int decimalPlaces = 2, bool trimTrailingZeros = false); + +/********************************************************************************************\ + Handling HEX strings + \*********************************************************************************************/ + +// Convert max. 8 hex decimals to unsigned long +unsigned long hexToUL(const String& input_c, + size_t nrHexDecimals); + +unsigned long hexToUL(const String& input_c); + +unsigned long hexToUL(const String& input_c, + size_t startpos, + size_t nrHexDecimals); + +// Convert max. 16 hex decimals to unsigned long long +unsigned long long hexToULL(const String& input_c, + size_t nrHexDecimals); + +unsigned long long hexToULL(const String& input_c); + +unsigned long long hexToULL(const String& input_c, + size_t startpos, + size_t nrHexDecimals); + +void appendHexChar(uint8_t data, String& string); + +// Binary data to HEX +// Returned string length will be twice the size of the data array. +String formatToHex_array(const uint8_t* data, size_t size); +String formatToHex_wordarray(const uint16_t* data, size_t size); + +String formatULLtoHex(const uint64_t& value, + const __FlashStringHelper * prefix, + unsigned int minimal_hex_digits); + +String formatULLtoHex(const uint64_t& value, + const __FlashStringHelper * prefix); + +String formatULLtoHex(const uint64_t& value, unsigned int minimal_hex_digits = 0); + +String formatULLtoHex_no_prefix(const uint64_t& value, unsigned int minimal_hex_digits = 0); + +String formatULLtoHex_decimal(const uint64_t& value); + + +String formatToHex(unsigned long value, + const __FlashStringHelper * prefix, + unsigned int minimal_hex_digits); + +String formatToHex(unsigned long value, + const __FlashStringHelper * prefix); + +String formatToHex(unsigned long value, unsigned int minimal_hex_digits = 0); + +String formatToHex_no_prefix(unsigned long value, unsigned int minimal_hex_digits = 0); + +String formatHumanReadable(uint64_t value, + uint32_t factor); + +String formatHumanReadable(uint64_t value, + uint32_t factor, + int NrDecimals); + +String formatToHex_decimal(unsigned long value); + +String formatToHex_decimal(unsigned long value, + unsigned long factor); + +int intFromHexChar(char a); + +String stringFromHexArray(const String& arr); + #endif // ifndef HELPERS_STRINGCONVERTER_NUMERICAL_H diff --git a/src/src/Helpers/StringGenerator_GPIO.cpp b/src/src/Helpers/StringGenerator_GPIO.cpp index 4841f57ec..0b58bb23b 100644 --- a/src/src/Helpers/StringGenerator_GPIO.cpp +++ b/src/src/Helpers/StringGenerator_GPIO.cpp @@ -6,11 +6,17 @@ #include "../Helpers/StringConverter.h" #include "../../ESPEasy_common.h" +#ifdef ESP32P4 +# include + +#endif // ifdef ESP32P4 + /*********************************************************************************************\ Device GPIO name functions to share flash strings \*********************************************************************************************/ const __FlashStringHelper* formatGpioDirection(gpio_direction direction) { - switch (direction) { + switch (direction) + { case gpio_direction::gpio_input: return F("← "); case gpio_direction::gpio_output: return F("→ "); case gpio_direction::gpio_bidirectional: return F("⇄ "); @@ -32,13 +38,22 @@ String formatGpioLabel(int gpio, bool includeWarning) { return F("-"); } -String formatGpioName(const __FlashStringHelper * label, gpio_direction direction, bool optional) { - int reserveLength = 5 /* "GPIO " */ + 8 /* "⇄ " */ + strlen_P((PGM_P)label); +String formatGpioName(const __FlashStringHelper *label, gpio_direction direction, bool optional) { + return formatGpioName(String(label), direction, optional); +} + +String formatGpioName(const String & label, + gpio_direction direction, + bool optional) +{ + + int reserveLength = 5 /* "GPIO " */ + 8 /* "⇄ " */ + label.length(); if (optional) { reserveLength += 11; } String result; + if (reserve_special(result, reserveLength)) { result += F("GPIO "); result += formatGpioDirection(direction); @@ -51,57 +66,36 @@ String formatGpioName(const __FlashStringHelper * label, gpio_direction directio return result; } -String formatGpioName_input(const __FlashStringHelper * label) { - return formatGpioName(label, gpio_direction::gpio_input, false); -} +String formatGpioName_input(const __FlashStringHelper *label) { return formatGpioName(label, gpio_direction::gpio_input, false); } -String formatGpioName_output(const __FlashStringHelper * label) { - return formatGpioName(label, gpio_direction::gpio_output, false); -} +String formatGpioName_output(const __FlashStringHelper *label) { return formatGpioName(label, gpio_direction::gpio_output, false); } -String formatGpioName_bidirectional(const __FlashStringHelper * label) { +String formatGpioName_bidirectional(const __FlashStringHelper *label) { return formatGpioName(label, gpio_direction::gpio_bidirectional, false); } -String formatGpioName_input_optional(const __FlashStringHelper * label) { - return formatGpioName(label, gpio_direction::gpio_input, true); -} +String formatGpioName_input_optional(const __FlashStringHelper *label) { return formatGpioName(label, gpio_direction::gpio_input, true); } -String formatGpioName_output_optional(const __FlashStringHelper * label) { - return formatGpioName(label, gpio_direction::gpio_output, true); -} +String formatGpioName_output_optional(const __FlashStringHelper *label) { return formatGpioName(label, gpio_direction::gpio_output, true); } // RX/TX are the only signals which are crossed, so they must be labelled like this: // "GPIO <-- TX" and "GPIO --> RX" -String formatGpioName_TX(bool optional) { - return formatGpioName(F("RX"), gpio_direction::gpio_output, optional); -} +String formatGpioName_TX(bool optional) { return formatGpioName(F("RX"), gpio_direction::gpio_output, optional); } -String formatGpioName_RX(bool optional) { - return formatGpioName(F("TX"), gpio_direction::gpio_input, optional); -} +String formatGpioName_RX(bool optional) { return formatGpioName(F("TX"), gpio_direction::gpio_input, optional); } -String formatGpioName_serialTX(bool optional) -{ - return concat(F("ESP TX "), formatGpioName_TX(optional)); -} +String formatGpioName_serialTX(bool optional) { return concat(F("ESP TX "), formatGpioName_TX(optional)); } -String formatGpioName_serialRX(bool optional) -{ - return concat(F("ESP RX "), formatGpioName_RX(optional)); -} +String formatGpioName_serialRX(bool optional) { return concat(F("ESP RX "), formatGpioName_RX(optional)); } -String formatGpioName_TX_HW(bool optional) { - return formatGpioName(F("RX (HW)"), gpio_direction::gpio_output, optional); -} +String formatGpioName_TX_HW(bool optional) { return formatGpioName(F("RX (HW)"), gpio_direction::gpio_output, optional); } -String formatGpioName_RX_HW(bool optional) { - return formatGpioName(F("TX (HW)"), gpio_direction::gpio_input, optional); -} +String formatGpioName_RX_HW(bool optional) { return formatGpioName(F("TX (HW)"), gpio_direction::gpio_input, optional); } #ifdef ESP32 -#if SOC_ADC_SUPPORTED +# if SOC_ADC_SUPPORTED + String formatGpioName_ADC(int gpio_pin) { int adc, ch, t; @@ -122,9 +116,11 @@ String formatGpioName_ADC(int gpio_pin) { } return EMPTY_STRING; } -#endif -#if SOC_DAC_SUPPORTED +# endif // if SOC_ADC_SUPPORTED + +# if SOC_DAC_SUPPORTED + String formatGpioName_DAC(int gpio_pin) { int dac; @@ -133,7 +129,8 @@ String formatGpioName_DAC(int gpio_pin) { } return EMPTY_STRING; } -#endif + +# endif // if SOC_DAC_SUPPORTED #endif // ifdef ESP32 @@ -150,12 +147,13 @@ String createGPIO_label(int gpio, int pinnr, bool input, bool output, bool warni result += gpio; #ifdef ESP8266 + if (pinnr >= 0) { result += F(" (D"); result += pinnr; result += ')'; } -#endif +#endif // ifdef ESP8266 if (input != output) { result += ' '; @@ -169,78 +167,90 @@ String createGPIO_label(int gpio, int pinnr, bool input, bool output, bool warni return result; } -const __FlashStringHelper* getConflictingUse(int gpio, PinSelectPurpose purpose, bool ignorePSRAMpins) +const __FlashStringHelper* getConflictingUse_flashstr(int gpio, PinSelectPurpose purpose, bool ignorePSRAMpins) { #ifdef PIN_USB_D_MIN - if (gpio == PIN_USB_D_MIN) { return F("USB_D-"); } -#endif -#ifdef PIN_USB_D_PLUS - if (gpio == PIN_USB_D_PLUS) { return F("USB_D+"); } -#endif - if (isFlashInterfacePin_ESPEasy(gpio)) { + if (gpio == PIN_USB_D_MIN) { return F("USB_D-"); } +#endif // ifdef PIN_USB_D_MIN +#ifdef PIN_USB_D_PLUS + + if (gpio == PIN_USB_D_PLUS) { return F("USB_D+"); } +#endif // ifdef PIN_USB_D_PLUS + + if (!ignorePSRAMpins && isFlashInterfacePin_ESPEasy(gpio)) { return F("Flash"); } #ifdef ESP32 + if (!ignorePSRAMpins && isPSRAMInterfacePin(gpio)) { // PSRAM pins can be shared with SPI return F("PSRAM"); } -#endif +#endif // ifdef ESP32 - # ifdef ESP32S2 +#ifdef ESP32S2 - #elif defined(ESP32S3) +#elif defined(ESP32S3) // See Appendix A, page 71: https://www.espressif.com/sites/default/files/documentation/esp32-s3_datasheet_en.pdf - #elif defined(ESP32C6) +#elif defined(ESP32P4) + + // TODO TD-er: Must check if P4 has conflicts here + +#elif defined(ESP32C5) + + // TODO TD-er: Must check if C5 has conflicts here + +#elif defined(ESP32C6) if (gpio == 27) { // By default VDD_SPI is the power supply pin for embedded flash or external flash. It can only be used as GPIO // only when the chip is connected to an external flash, and this flash is powered by an external power supply - return F("Flash Vdd"); + return F("Flash Vdd"); } - #elif defined(ESP32C2) || defined(ESP32C3) +#elif defined(ESP32C61) + + // TODO TD-er: Must check if C61 has conflicts here + +#elif defined(ESP32C2) || defined(ESP32C3) if (gpio == 11) { // By default VDD_SPI is the power supply pin for embedded flash or external flash. It can only be used as GPIO11 // only when the chip is connected to an external flash, and this flash is powered by an external power supply - return F("Flash Vdd"); + return F("Flash Vdd"); } - # elif defined(ESP32_CLASSIC) +#elif defined(ESP32_CLASSIC) - # elif defined(ESP8266) +#elif defined(ESP8266) - # else - static_assert(false, "Implement processor architecture"); +#else // ifdef ESP32S2 + static_assert(false, "Implement processor architecture"); - # endif +#endif // ifdef ESP32S2 bool includeI2C = true; bool includeSPI = true; - #if FEATURE_SD +#if FEATURE_SD bool includeSDCard = true; - #endif - bool includeSerial = Settings.UseSerial; // Only need to check if Serial Port Console is enabled - - #if FEATURE_ETHERNET - bool includeEthernet = true; - #endif // if FEATURE_ETHERNET +#endif + bool includeSerial = Settings.UseSerial; // Only need to check if Serial Port Console is enabled bool includeStatusLed = true; - bool includeResetPin = true; + bool includeResetPin = true; - switch (purpose) { + switch (purpose) + { case PinSelectPurpose::I2C: #if FEATURE_I2C_MULTIPLE case PinSelectPurpose::I2C_2: -#if FEATURE_I2C_INTERFACE_3 +# if FEATURE_I2C_INTERFACE_3 case PinSelectPurpose::I2C_3: -#endif -#endif +# endif +#endif // if FEATURE_I2C_MULTIPLE includeI2C = false; break; case PinSelectPurpose::SPI: @@ -251,22 +261,20 @@ const __FlashStringHelper* getConflictingUse(int gpio, PinSelectPurpose purpose, case PinSelectPurpose::Serial_output: includeSerial = false; break; +#if FEATURE_ETHERNET case PinSelectPurpose::Ethernet: - #if FEATURE_ETHERNET - includeEthernet = false; - #endif // if FEATURE_ETHERNET - break; +#endif case PinSelectPurpose::Generic: case PinSelectPurpose::Generic_input: case PinSelectPurpose::Generic_output: case PinSelectPurpose::Generic_bidir: case PinSelectPurpose::DAC: break; - #if FEATURE_SD +#if FEATURE_SD case PinSelectPurpose::SD_Card: includeSDCard = false; break; - #endif +#endif // if FEATURE_SD case PinSelectPurpose::Status_led: includeStatusLed = false; break; @@ -279,37 +287,44 @@ const __FlashStringHelper* getConflictingUse(int gpio, PinSelectPurpose purpose, for (uint8_t i2cBus = 0; i2cBus < getI2CBusCount(); ++i2cBus) { if (Settings.getI2CSdaPin(i2cBus) == gpio) { - #if FEATURE_I2C_MULTIPLE - switch (i2cBus) { +#if FEATURE_I2C_MULTIPLE + + switch (i2cBus) + { case 0: return F("I2C SDA (bus 0)"); case 1: return F("I2C SDA (bus 1)"); - #if FEATURE_I2C_INTERFACE_3 +# if FEATURE_I2C_INTERFACE_3 case 2: return F("I2C SDA (bus 2)"); - #endif +# endif } - #else +#else // if FEATURE_I2C_MULTIPLE return F("I2C SDA"); - #endif +#endif // if FEATURE_I2C_MULTIPLE } + if (Settings.getI2CSclPin(i2cBus) == gpio) { - #if FEATURE_I2C_MULTIPLE - switch (i2cBus) { +#if FEATURE_I2C_MULTIPLE + + switch (i2cBus) + { case 0: return F("I2C SCL (bus 0)"); case 1: return F("I2C SCL (bus 1)"); - #if FEATURE_I2C_INTERFACE_3 +# if FEATURE_I2C_INTERFACE_3 case 2: return F("I2C SCL (bus 2)"); - #endif +# endif } - #else +#else // if FEATURE_I2C_MULTIPLE return F("I2C SCL"); - #endif +#endif // if FEATURE_I2C_MULTIPLE } } } +#ifdef ESP8266 if (includeSPI && Settings.isSPI_pin(gpio)) { return F("SPI"); } +#endif // ifdef ESP8266 if (includeStatusLed && (Settings.Pin_status_led == gpio) && (-1 != gpio)) { return F("Wifi Status LED"); @@ -318,57 +333,54 @@ const __FlashStringHelper* getConflictingUse(int gpio, PinSelectPurpose purpose, if (includeResetPin && (Settings.Pin_Reset == gpio) && (-1 != gpio)) { return F("Reset Pin"); } +#ifdef ESP8266 if (includeSerial) { - #if FEATURE_DEFINE_SERIAL_CONSOLE_PORT - if (Settings.UseSerial && - (Settings.console_serial_port == 2 // 2 == ESPEasySerialPort::serial0 - #if USES_ESPEASY_CONSOLE_FALLBACK_PORT +# if FEATURE_DEFINE_SERIAL_CONSOLE_PORT + + if (Settings.UseSerial && + (Settings.console_serial_port == 2 // 2 == ESPEasySerialPort::serial0 +# if USES_ESPEASY_CONSOLE_FALLBACK_PORT || Settings.console_serial0_fallback - #endif // if USES_ESPEASY_CONSOLE_FALLBACK_PORT +# endif // if USES_ESPEASY_CONSOLE_FALLBACK_PORT )) - #else - if (Settings.UseSerial) - #endif +# else // if FEATURE_DEFINE_SERIAL_CONSOLE_PORT + + if (Settings.UseSerial) +# endif // if FEATURE_DEFINE_SERIAL_CONSOLE_PORT { if (gpio == SOC_TX0) { return F("TX0"); } if (gpio == SOC_RX0) { return F("RX0"); } } } +#endif // ifdef ESP8266 +#if FEATURE_SD - #if FEATURE_SD - if (validGpio(gpio) && Settings.Pin_sd_cs == gpio && includeSDCard) { return F("SD-Card CS"); } - #endif // if FEATURE_SD + if (validGpio(gpio) && (Settings.Pin_sd_cs == gpio) && includeSDCard) { return F("SD-Card CS"); } +#endif // if FEATURE_SD +#ifdef ESP32P4 +# ifdef BOARD_HAS_SDIO_ESP_HOSTED - #if FEATURE_ETHERNET - if (isSPI_EthernetType(Settings.ETH_Phy_Type)) { - if (includeEthernet && Settings.isEthernetPinOptional(gpio)) { - if (Settings.ETH_Pin_mdc_cs == gpio) { return F("Eth SPI CS"); } + // TODO TD-er: Make this configurable as we can set this via WiFi.setPins + switch (gpio) + { + case BOARD_SDIO_ESP_HOSTED_CLK: return F("SDIO_CLK"); + case BOARD_SDIO_ESP_HOSTED_CMD: return F("SDIO_CMD"); + case BOARD_SDIO_ESP_HOSTED_D0: return F("SDIO_D0"); + case BOARD_SDIO_ESP_HOSTED_D1: return F("SDIO_D1"); + case BOARD_SDIO_ESP_HOSTED_D2: return F("SDIO_D2"); + case BOARD_SDIO_ESP_HOSTED_D3: return F("SDIO_D3"); + case BOARD_SDIO_ESP_HOSTED_RESET: return F("SDIO_RESET"); + } +# endif // ifdef BOARD_HAS_SDIO_ESP_HOSTED +#endif // ifdef ESP32P4 - if (Settings.ETH_Pin_mdio_irq == gpio) { return F("Eth SPI IRQ"); } - if (Settings.ETH_Pin_power_rst == gpio) { return F("Eth SPI RST"); } - } - } else { - if (Settings.isEthernetPin(gpio)) { - return F("Eth"); - } - - if (includeEthernet && Settings.isEthernetPinOptional(gpio)) { - if (isGpioUsedInETHClockMode(Settings.ETH_Clock_Mode, gpio)) { return F("Eth Clock"); } - - if (Settings.ETH_Pin_mdc_cs == gpio) { return F("Eth MDC"); } - - if (Settings.ETH_Pin_mdio_irq == gpio) { return F("Eth MDIO"); } - - if (Settings.ETH_Pin_power_rst == gpio) { return F("Eth Pwr"); } - - return F("Eth"); - } + if (isBootModePin(gpio)) { + return F("Boot Mode"); } - #endif // if FEATURE_ETHERNET if (isBootStrapPin(gpio)) { return F("Boot Strapping"); @@ -377,11 +389,128 @@ const __FlashStringHelper* getConflictingUse(int gpio, PinSelectPurpose purpose, return F(""); } +#ifdef ESP32 + +String getConflictingUse_fromPeriman(int gpio, PinSelectPurpose purpose, bool ignorePSRAMpins) +{ + String conflict; + + peripheral_bus_type_t bus_type = ESP32_BUS_TYPE_INIT; + String gpio_str, typeName; + int bus_number{}; + int bus_channel{}; + + if (getPeriman_gpio_info(gpio, bus_type, gpio_str, typeName, bus_number, bus_channel)) + { + switch (purpose) + { + case PinSelectPurpose::I2C: +# if FEATURE_I2C_MULTIPLE + case PinSelectPurpose::I2C_2: +# if FEATURE_I2C_INTERFACE_3 + case PinSelectPurpose::I2C_3: +# endif +# endif // if FEATURE_I2C_MULTIPLE + + if ((bus_type == ESP32_BUS_TYPE_I2C_MASTER_SDA) || + (bus_type == ESP32_BUS_TYPE_I2C_MASTER_SCL) || + (bus_type == ESP32_BUS_TYPE_I2C_SLAVE_SDA) || + (bus_type == ESP32_BUS_TYPE_I2C_SLAVE_SCL)) + { return conflict; } + break; + case PinSelectPurpose::SPI: + case PinSelectPurpose::SPI_MISO: + + if ((bus_type == ESP32_BUS_TYPE_SPI_MASTER_SCK) || + (bus_type == ESP32_BUS_TYPE_SPI_MASTER_MISO) || + (bus_type == ESP32_BUS_TYPE_SPI_MASTER_MOSI) || + (bus_type == ESP32_BUS_TYPE_SPI_MASTER_SS)) + { return conflict; } + break; + case PinSelectPurpose::Serial_input: + case PinSelectPurpose::Serial_output: + + if ((bus_type == ESP32_BUS_TYPE_UART_RX) || + (bus_type == ESP32_BUS_TYPE_UART_TX) || + (bus_type == ESP32_BUS_TYPE_UART_CTS) || + (bus_type == ESP32_BUS_TYPE_UART_RTS)) + { return conflict; } + break; +#if FEATURE_ETHERNET + case PinSelectPurpose::Ethernet: +# if CONFIG_ETH_USE_ESP32_EMAC + + if ( // (bus_type == ESP32_BUS_TYPE_ETHERNET_RMII) || + (bus_type == ESP32_BUS_TYPE_ETHERNET_CLK) || + (bus_type == ESP32_BUS_TYPE_ETHERNET_MCD) || + (bus_type == ESP32_BUS_TYPE_ETHERNET_MDIO) || + (bus_type == ESP32_BUS_TYPE_ETHERNET_PWR)) + { return conflict; } +# endif // if CONFIG_ETH_USE_ESP32_EMAC +# if SOC_GPSPI_SUPPORTED + + if (bus_type == ESP32_BUS_TYPE_ETHERNET_SPI) + { return conflict; } +# endif // if SOC_GPSPI_SUPPORTED + break; +#endif + case PinSelectPurpose::Generic: + case PinSelectPurpose::Generic_input: + case PinSelectPurpose::Generic_output: + case PinSelectPurpose::Generic_bidir: + case PinSelectPurpose::DAC: + break; +# if FEATURE_SD + case PinSelectPurpose::SD_Card: + break; +# endif // if FEATURE_SD + case PinSelectPurpose::Status_led: +# if SOC_LEDC_SUPPORTED + + if (bus_type == ESP32_BUS_TYPE_LEDC) { return conflict; } +# endif // if SOC_LEDC_SUPPORTED + break; + case PinSelectPurpose::Reset_pin: + break; + } + + conflict = typeName; + + if (bus_number != -1) { + conflict += '('; + conflict += bus_number; + + if (bus_channel != -1) { + conflict += ','; + conflict += bus_channel; + } + conflict += ')'; + } + } + return conflict; +} + +#endif // ifdef ESP32 + +String getConflictingUse(int gpio, PinSelectPurpose purpose, bool ignorePSRAMpins) +{ + String conflict; + + #ifdef ESP32 + conflict = getConflictingUse_fromPeriman(gpio, purpose, ignorePSRAMpins); + + if (!conflict.isEmpty()) { return conflict; } + #endif // ifdef ESP32 + return getConflictingUse_flashstr(gpio, purpose, ignorePSRAMpins); +} + String getConflictingUse_wrapped(int gpio, PinSelectPurpose purpose, bool ignorePSRAMpins) { const String conflict = getConflictingUse(gpio, purpose, ignorePSRAMpins); - if (conflict.isEmpty()) { return conflict; } + if (conflict.isEmpty()) { + return conflict; + } String res = F(" ["); res += conflict; diff --git a/src/src/Helpers/StringGenerator_GPIO.h b/src/src/Helpers/StringGenerator_GPIO.h index e09b85b34..e28515a8a 100644 --- a/src/src/Helpers/StringGenerator_GPIO.h +++ b/src/src/Helpers/StringGenerator_GPIO.h @@ -31,7 +31,9 @@ enum class PinSelectPurpose : uint8_t { #endif SPI, SPI_MISO, +#if FEATURE_ETHERNET Ethernet, +#endif Serial_input, Serial_output, DAC, @@ -56,6 +58,9 @@ String formatGpioLabel(int gpio, String formatGpioName(const __FlashStringHelper * label, gpio_direction direction, bool optional = false); +String formatGpioName(const String& label, + gpio_direction direction, + bool optional = false); String formatGpioName_input(const __FlashStringHelper * label); String formatGpioName_output(const __FlashStringHelper * label); @@ -96,7 +101,9 @@ String createGPIO_label(int gpio, bool output, bool warning); -const __FlashStringHelper * getConflictingUse(int gpio, PinSelectPurpose purpose = PinSelectPurpose::Generic, bool ignorePSRAMpin = false); +const __FlashStringHelper * getConflictingUse_flashstr(int gpio, PinSelectPurpose purpose = PinSelectPurpose::Generic, bool ignorePSRAMpin = false); + +String getConflictingUse(int gpio, PinSelectPurpose purpose = PinSelectPurpose::Generic, bool ignorePSRAMpin = false); String getConflictingUse_wrapped(int gpio, PinSelectPurpose purpose = PinSelectPurpose::Generic, bool ignorePSRAMpin = false); diff --git a/src/src/Helpers/StringGenerator_System.cpp b/src/src/Helpers/StringGenerator_System.cpp index df5d9a368..d2d1bf7f1 100644 --- a/src/src/Helpers/StringGenerator_System.cpp +++ b/src/src/Helpers/StringGenerator_System.cpp @@ -19,14 +19,16 @@ #include #elif defined(ESP32C3) #include + #elif defined(ESP32C5) + #include #elif defined(ESP32C6) #include + #elif defined(ESP32C61) + #include + #elif defined(ESP32P4) + #include # elif defined(ESP32_CLASSIC) - #if ESP_IDF_VERSION_MAJOR > 3 - #include - #else - #include - #endif + #include # else static_assert(false, "Implement processor architecture"); @@ -181,6 +183,32 @@ const __FlashStringHelper * getResetReasonString_f(uint8_t icore, bool& isDEEPSL case POWER_GLITCH_RESET : return F("power glitch reset digital core and rtc module"); } + #elif defined(ESP32C5) + + // See tools\sdk\esp32\include\esp_rom\include\esp32c6\rom\rtc.h + switch (rtc_get_reset_reason(icore)) { + case NO_MEAN : break; + case POWERON_RESET : return F("Vbat power on reset"); + case RTC_SW_SYS_RESET : return F("Software reset digital core (hp system)"); + case DEEPSLEEP_RESET : isDEEPSLEEP_RESET = true; break; + //case DEEPSLEEP_RESET : return F("Deep Sleep reset digital core (hp system)"); +// case SDIO_RESET : return F("Reset by SLC module, reset digital core (hp system)"); + case TG0WDT_SYS_RESET : return F("Timer Group0 Watch dog reset digital core (hp system)"); + case TG1WDT_SYS_RESET : return F("Timer Group1 Watch dog reset digital core (hp system)"); + case RTCWDT_SYS_RESET : return F("RTC Watch dog Reset digital core (hp system)"); + case TG0WDT_CPU_RESET : return F("Time Group0 reset CPU"); + case RTC_SW_CPU_RESET : return F("Software reset CPU"); + case RTCWDT_CPU_RESET : return F("RTC Watch dog Reset CPU"); + case RTCWDT_BROWN_OUT_RESET : return F("Reset when the vdd voltage is not stable"); + case RTCWDT_RTC_RESET : return F("RTC Watch dog reset digital core and rtc module"); + case TG1WDT_CPU_RESET : return F("Time Group1 reset CPU"); + case SUPER_WDT_RESET : return F("super watchdog reset digital core and rtc module"); + case EFUSE_RESET : return F("efuse reset digital core (hp system)"); + case USB_UART_CHIP_RESET : return F("usb uart reset digital core (hp system)"); + case USB_JTAG_CHIP_RESET : return F("usb jtag reset digital core (hp system)"); + case JTAG_RESET : return F("jtag reset CPU"); + } + #elif defined(ESP32C6) // See tools\sdk\esp32\include\esp_rom\include\esp32c6\rom\rtc.h @@ -207,6 +235,55 @@ const __FlashStringHelper * getResetReasonString_f(uint8_t icore, bool& isDEEPSL case JTAG_RESET : return F("jtag reset CPU"); } + #elif defined(ESP32C61) + + // See tools\sdk\esp32\include\esp_rom\include\esp32c61\rom\rtc.h + switch (rtc_get_reset_reason(icore)) { + case NO_MEAN : break; + case POWERON_RESET : return F("Vbat power on reset"); + case RTC_SW_SYS_RESET : return F("Software reset digital core (hp system)"); + case DEEPSLEEP_RESET : isDEEPSLEEP_RESET = true; break; + //case DEEPSLEEP_RESET : return F("Deep Sleep reset digital core (hp system)"); + case TG0WDT_SYS_RESET : return F("Timer Group0 Watch dog reset digital core (hp system)"); + case TG1WDT_SYS_RESET : return F("Timer Group1 Watch dog reset digital core (hp system)"); + case RTCWDT_SYS_RESET : return F("RTC Watch dog Reset digital core (hp system)"); + case TG0WDT_CPU_RESET : return F("Time Group0 reset CPU"); + case RTC_SW_CPU_RESET : return F("Software reset CPU"); + case RTCWDT_CPU_RESET : return F("RTC Watch dog Reset CPU"); + case RTCWDT_BROWN_OUT_RESET : return F("Reset when the vdd voltage is not stable"); + case RTCWDT_RTC_RESET : return F("RTC Watch dog reset digital core and rtc module"); + case TG1WDT_CPU_RESET : return F("Time Group1 reset CPU"); + case SUPER_WDT_RESET : return F("super watchdog reset digital core and rtc module"); + case EFUSE_RESET : return F("efuse reset digital core (hp system)"); + case USB_UART_CHIP_RESET : return F("usb uart reset digital core (hp system)"); + case USB_JTAG_CHIP_RESET : return F("usb jtag reset digital core (hp system)"); + case JTAG_RESET : return F("jtag reset CPU"); + } + + #elif defined(ESP32P4) + + // See tools\sdk\esp32\include\esp_rom\include\esp32p4\rom\rtc.h + switch (rtc_get_reset_reason(icore)) { + case NO_MEAN : break; + case POWERON_RESET : return F("Vbat power on reset"); + case SW_SYS_RESET : return F("Software reset digital core"); + case PMU_SYS_PWR_DOWN_RESET : return F("PMU HP system power down reset"); + case HP_SYS_HP_WDT_RESET : return F("HP system reset from HP watchdog"); + case HP_SYS_LP_WDT_RESET : return F("HP system reset from LP watchdog"); + case HP_CORE_HP_WDT_RESET : return F("HP core reset from HP watchdog"); + case SW_CPU_RESET : return F("software reset cpu"); + case HP_CORE_LP_WDT_RESET : return F("HP core reset from LP watchdog"); + case BROWN_OUT_RESET : return F("Reset when the vdd voltage is not stable"); + case CHIP_LP_WDT_RESET : return F("LP watchdog chip reset"); + case SUPER_WDT_RESET : return F("super watchdog reset"); + case GLITCH_RTC_RESET : return F("glitch reset"); + case EFUSE_CRC_ERR_RESET : return F("efuse ecc error reset"); + case CHIP_USB_JTAG_RESET : return F("HP usb jtag chip reset"); + case CHIP_USB_UART_RESET : return F("HP usb uart chip reset"); + case JTAG_RESET : return F("jtag reset"); + case CPU_LOCKUP_RESET : return F("cpu lockup reset"); + } + # elif defined(ESP32_CLASSIC) diff --git a/src/src/Helpers/StringGenerator_WiFi.cpp b/src/src/Helpers/StringGenerator_WiFi.cpp index 7d49df982..7a4f08d49 100644 --- a/src/src/Helpers/StringGenerator_WiFi.cpp +++ b/src/src/Helpers/StringGenerator_WiFi.cpp @@ -2,7 +2,9 @@ #include "../Helpers/StringConverter.h" -#include "../Globals/ESPEasyWiFiEvent.h" +#include "../../ESPEasy/net/Globals/ESPEasyWiFiEvent.h" +#include "../../ESPEasy/net/_NWPlugin_Helper.h" +#include "../../ESPEasy/net/NWPluginStructs/NW001_data_struct_WiFi_STA.h" #ifdef ESP8266 #include @@ -11,12 +13,23 @@ const __FlashStringHelper * WiFi_encryptionType(uint8_t encryptionType) { switch (encryptionType) { #ifdef ESP32 - case WIFI_AUTH_OPEN: return F("open"); - case WIFI_AUTH_WEP: return F("WEP"); - case WIFI_AUTH_WPA_PSK: return F("WPA/PSK"); - case WIFI_AUTH_WPA2_PSK: return F("WPA2/PSK"); - case WIFI_AUTH_WPA_WPA2_PSK: return F("WPA/WPA2/PSK"); - case WIFI_AUTH_WPA2_ENTERPRISE: return F("WPA2 Enterprise"); + case WIFI_AUTH_OPEN: return F("open"); + case WIFI_AUTH_WEP: return F("WEP"); + case WIFI_AUTH_WPA_PSK: return F("WPA PSK"); + case WIFI_AUTH_WPA2_PSK: return F("WPA2 PSK"); + case WIFI_AUTH_WPA_WPA2_PSK: return F("WPA/WPA2 PSK"); + case WIFI_AUTH_WPA2_ENTERPRISE: return F("WPA2-Enterprise"); + case WIFI_AUTH_WPA3_EXT_PSK: // This authentication mode will yield same result as WIFI_AUTH_WPA3_PSK and not recommended to be used. It will be deprecated in future: please use WIFI_AUTH_WPA3_PSK instead. + case WIFI_AUTH_WPA3_EXT_PSK_MIXED_MODE: // This authentication mode will yield same result as WIFI_AUTH_WPA3_PSK and not recommended to be used. It will be deprecated in future: please use WIFI_AUTH_WPA3_PSK instead. + case WIFI_AUTH_WPA3_PSK: return F("WPA3 PSK"); + case WIFI_AUTH_WPA2_WPA3_PSK: return F("WPA2/WPA3 PSK"); + case WIFI_AUTH_WAPI_PSK: return F("WAPI PSK"); + case WIFI_AUTH_OWE: return F("OWE"); + case WIFI_AUTH_WPA3_ENT_192: return F("WPA3 ENT SUITE B 192 BIT"); + case WIFI_AUTH_DPP: return F("DPP"); + case WIFI_AUTH_WPA3_ENTERPRISE: return F("WPA3-Enterprise Only Mode"); + case WIFI_AUTH_WPA2_WPA3_ENTERPRISE: return F("WPA3-Enterprise Transition Mode"); + case WIFI_AUTH_WPA_ENTERPRISE: return F("WPA-Enterprise"); #else // ifdef ESP32 case ENC_TYPE_WEP: return F("WEP"); case ENC_TYPE_TKIP: return F("WPA/PSK"); @@ -119,13 +132,33 @@ const __FlashStringHelper * getLastDisconnectReason(WiFiDisconnectReason reason) } } -String getLastDisconnectReason() { +WiFiDisconnectReason getWiFi_disconnectReason() +{ + ESPEasy::net::wifi::NW001_data_struct_WiFi_STA *NW_data = + static_cast(ESPEasy::net::getNWPluginData(NETWORK_INDEX_WIFI_STA)); + if (NW_data) { + return NW_data->getWiFi_disconnectReason(); + } + return WIFI_DISCONNECT_REASON_UNSPECIFIED; +} + +String getWiFi_disconnectReason_str() { #ifndef LIMIT_BUILD_SIZE - String reason = wrap_braces(String(WiFiEventData.lastDisconnectReason)); - reason += ' '; - reason += getLastDisconnectReason(WiFiEventData.lastDisconnectReason); - return reason; + const WiFiDisconnectReason reason = getWiFi_disconnectReason(); + return strformat( + F("(%d) %s"), + reason, + FsP(getLastDisconnectReason(reason))); #else - return wrap_braces(String(WiFiEventData.lastDisconnectReason)); + return wrap_braces(String(getWiFi_disconnectReason())); #endif } + +const __FlashStringHelper* getWiFi_encryptionType() { + ESPEasy::net::wifi::NW001_data_struct_WiFi_STA *NW_data = + static_cast(ESPEasy::net::getNWPluginData(NETWORK_INDEX_WIFI_STA)); + if (NW_data) { + return NW_data->getWiFi_encryptionType(); + } + return F("-"); +} \ No newline at end of file diff --git a/src/src/Helpers/StringGenerator_WiFi.h b/src/src/Helpers/StringGenerator_WiFi.h index d08e57b98..0f953b5dc 100644 --- a/src/src/Helpers/StringGenerator_WiFi.h +++ b/src/src/Helpers/StringGenerator_WiFi.h @@ -3,6 +3,8 @@ #include "../../ESPEasy_common.h" +#include "../../ESPEasy/net/wifi/WiFiDisconnectReason.h" + const __FlashStringHelper * WiFi_encryptionType(uint8_t encryptionType); #ifdef ESP8266 @@ -15,6 +17,10 @@ const __FlashStringHelper * SDKwifiStatusToString(uint8_t sdk_wifistatus); String ArduinoWifiStatusToString(uint8_t arduino_corelib_wifistatus); -String getLastDisconnectReason(); +WiFiDisconnectReason getWiFi_disconnectReason(); + +String getWiFi_disconnectReason_str(); + +const __FlashStringHelper* getWiFi_encryptionType(); #endif \ No newline at end of file diff --git a/src/src/Helpers/StringParser.cpp b/src/src/Helpers/StringParser.cpp index bd7ebe2ad..515d1f23d 100644 --- a/src/src/Helpers/StringParser.cpp +++ b/src/src/Helpers/StringParser.cpp @@ -16,6 +16,7 @@ #include "../Helpers/_CPlugin_init.h" #include "../Helpers/ESPEasy_math.h" #include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/ESPEasy_UnitOfMeasure.h" #include "../Helpers/Misc.h" #include "../Helpers/Numerical.h" #include "../Helpers/StringConverter.h" @@ -319,7 +320,7 @@ String parseTemplate_padded(String& tmpString, uint8_t minimal_lineSize, bool us } } #if FEATURE_STRING_VARIABLES - if (!isHandled) { + if (!isHandled && Settings.TaskDeviceEnabled[taskIndex]) { String value; const String valName = parseString(valueName, 1); String derived = getCustomStringVar(strformat(F(TASK_VALUE_DERIVED_PREFIX_TEMPLATE), deviceName.c_str(), valName.c_str())); @@ -333,6 +334,7 @@ String parseTemplate_padded(String& tmpString, uint8_t minimal_lineSize, bool us } } } + #endif // if FEATURE_STRING_VARIABLES #if FEATURE_TASKVALUE_ATTRIBUTES if (!isHandled && valueName.indexOf('.') > -1) { // TaskValue specific attributes @@ -376,6 +378,7 @@ String parseTemplate_padded(String& tmpString, uint8_t minimal_lineSize, bool us } #endif // if FEATURE_TASKVALUE_ATTRIBUTES + #if FEATURE_STRING_VARIABLES if (!isHandled && valueName.indexOf('.') > -1) { String value; const String fullValueName = parseString(valueName, 1); diff --git a/src/src/Helpers/StringProvider.cpp b/src/src/Helpers/StringProvider.cpp index c9f6bdeca..64af7cd89 100644 --- a/src/src/Helpers/StringProvider.cpp +++ b/src/src/Helpers/StringProvider.cpp @@ -8,34 +8,35 @@ #include "../CustomBuild/CompiletimeDefines.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyWifi.h" +#include "../../ESPEasy/net/ESPEasyNetwork.h" +#include "../../ESPEasy/net/Helpers/NWAccessControl.h" +#include "../../ESPEasy/net/wifi/ESPEasyWifi.h" + #if FEATURE_ETHERNET -#include "../ESPEasyCore/ESPEasyEth.h" +# include "../../ESPEasy/net/eth/ESPEasyEth.h" #endif #include "../Globals/Device.h" #include "../Globals/ESPEasy_Console.h" #include "../Globals/ESPEasy_Scheduler.h" #include "../Globals/ESPEasy_time.h" -#include "../Globals/ESPEasyWiFiEvent.h" +#include "../../ESPEasy/net/Globals/ESPEasyWiFiEvent.h" -#if FEATURE_ETHERNET -#include "../Globals/ESPEasyEthEvent.h" -#endif - -#include "../Globals/NetworkState.h" +#include "../../ESPEasy/net/Globals/NetworkState.h" +#include "../Globals/RTC.h" #include "../Globals/SecuritySettings.h" #include "../Globals/Settings.h" -#include "../Globals/WiFi_AP_Candidates.h" +#include "../../ESPEasy/net/Globals/WiFi_AP_Candidates.h" #include "../Helpers/Convert.h" #include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/ESPEasy_UnitOfMeasure.h" #include "../Helpers/Hardware_device_info.h" #include "../Helpers/Hardware_temperature_sensor.h" #include "../Helpers/Memory.h" #include "../Helpers/Misc.h" #include "../Helpers/Networking.h" +#include "../Helpers/OTA.h" #include "../Helpers/Scheduler.h" #include "../Helpers/StringConverter.h" #include "../Helpers/StringGenerator_System.h" @@ -43,623 +44,1268 @@ #include "../WebServer/JSON.h" #include "../WebServer/AccessControl.h" +#include "../WebServer/Markup.h" #ifdef ESP32 -#include +# include #endif -String getInternalLabel(LabelType::Enum label, char replaceSpace) { - return to_internal_string(getLabel(label), replaceSpace); -} +#ifndef LIMIT_BUILD_SIZE +# define KV_SETUNIT(S) kv.setUnit(S) +# define KV_SETID(S) kv.setID(S) +#else // ifndef LIMIT_BUILD_SIZE +# define KV_SETUNIT(S) +# define KV_SETID(S) +#endif // ifndef LIMIT_BUILD_SIZE -const __FlashStringHelper * getLabel(LabelType::Enum label) { +KeyValueStruct getKeyValue(LabelType::Enum label, bool extendedValue) +{ switch (label) { - case LabelType::UNIT_NR: return F("Unit Number"); + case LabelType::UNIT_NR: + { + return KeyValueStruct(F("Unit Number"), Settings.Unit); + } #if FEATURE_ZEROFILLED_UNITNUMBER - case LabelType::UNIT_NR_0: return F("Unit Number 0-filled"); - #endif // FEATURE_ZEROFILLED_UNITNUMBER - case LabelType::UNIT_NAME: return F("Unit Name"); - case LabelType::HOST_NAME: return F("Hostname"); - - case LabelType::LOCAL_TIME: return F("Local Time"); - case LabelType::TIME_SOURCE: return F("Time Source"); - case LabelType::TIME_WANDER: return F("Time Wander"); - #if FEATURE_EXT_RTC - case LabelType::EXT_RTC_UTC_TIME: return F("UTC time stored in RTC chip"); - #endif - case LabelType::UPTIME: return F("Uptime"); - case LabelType::LOAD_PCT: return F("Load"); - case LabelType::LOOP_COUNT: return F("Load LC"); - case LabelType::CPU_ECO_MODE: return F("CPU Eco Mode"); -#if FEATURE_SET_WIFI_TX_PWR - case LabelType::WIFI_TX_MAX_PWR: return F("Max WiFi TX Power"); - case LabelType::WIFI_CUR_TX_PWR: return F("Current WiFi TX Power"); - case LabelType::WIFI_SENS_MARGIN: return F("WiFi Sensitivity Margin"); - case LabelType::WIFI_SEND_AT_MAX_TX_PWR:return F("Send With Max TX Power"); -#endif - case LabelType::WIFI_NR_EXTRA_SCANS: return F("Extra WiFi scan loops"); - case LabelType::WIFI_USE_LAST_CONN_FROM_RTC: return F("Use Last Connected AP from RTC"); - - case LabelType::FREE_MEM: return F("Free RAM"); - case LabelType::FREE_STACK: return F("Free Stack"); -#ifdef USE_SECOND_HEAP - case LabelType::FREE_HEAP_IRAM: return F("Free 2nd Heap"); -#endif - -#if defined(CORE_POST_2_5_0) || defined(ESP32) - #ifndef LIMIT_BUILD_SIZE - case LabelType::HEAP_MAX_FREE_BLOCK: return F("Heap Max Free Block"); - #endif -#endif // if defined(CORE_POST_2_5_0) || defined(ESP32) -#if defined(CORE_POST_2_5_0) - #ifndef LIMIT_BUILD_SIZE - case LabelType::HEAP_FRAGMENTATION: return F("Heap Fragmentation"); - #endif -#endif // if defined(CORE_POST_2_5_0) - -#ifdef ESP32 - case LabelType::HEAP_SIZE: return F("Heap Size"); - case LabelType::HEAP_MIN_FREE: return F("Heap Min Free"); - #ifdef BOARD_HAS_PSRAM - case LabelType::PSRAM_SIZE: return F("PSRAM Size"); - case LabelType::PSRAM_FREE: return F("PSRAM Free"); - case LabelType::PSRAM_MIN_FREE: return F("PSRAM Min Free"); - case LabelType::PSRAM_MAX_FREE_BLOCK: return F("PSRAM Max Free Block"); - #endif // BOARD_HAS_PSRAM -#endif // ifdef ESP32 - - case LabelType::JSON_BOOL_QUOTES: return F("JSON bool output without quotes"); -#if FEATURE_TIMING_STATS - case LabelType::ENABLE_TIMING_STATISTICS: return F("Collect Timing Statistics"); -#endif - case LabelType::ENABLE_RULES_CACHING: return F("Enable Rules Cache"); - case LabelType::ENABLE_SERIAL_PORT_CONSOLE: return F("Enable Serial Port Console"); - case LabelType::CONSOLE_SERIAL_PORT: return F("Console Serial Port"); -#if USES_ESPEASY_CONSOLE_FALLBACK_PORT - case LabelType::CONSOLE_FALLBACK_TO_SERIAL0: return F("Fallback to Serial 0"); - case LabelType::CONSOLE_FALLBACK_PORT: return F("Console Fallback Port"); -#endif - -// case LabelType::ENABLE_RULES_EVENT_REORDER: return F("Optimize Rules Cache Event Order"); // TD-er: Disabled for now - case LabelType::TASKVALUESET_ALL_PLUGINS: return F("Allow TaskValueSet on all plugins"); - case LabelType::ALLOW_OTA_UNLIMITED: return F("Allow OTA without size-check"); -#if FEATURE_CLEAR_I2C_STUCK - case LabelType::ENABLE_CLEAR_HUNG_I2C_BUS: return F("Try clear I2C bus when stuck"); -#endif - #if FEATURE_I2C_DEVICE_CHECK - case LabelType::ENABLE_I2C_DEVICE_CHECK: return F("Check I2C devices when enabled"); - #endif // if FEATURE_I2C_DEVICE_CHECK -#ifndef BUILD_NO_RAM_TRACKER - case LabelType::ENABLE_RAM_TRACKING: return F("Enable RAM Tracker"); -#endif -#if FEATURE_AUTO_DARK_MODE - case LabelType::ENABLE_AUTO_DARK_MODE: return F("Web light/dark mode"); -#endif // FEATURE_AUTO_DARK_MODE -#if FEATURE_RULES_EASY_COLOR_CODE - case LabelType::DISABLE_RULES_AUTOCOMPLETE: return F("Disable Rules auto-completion"); -#endif // if FEATURE_RULES_EASY_COLOR_CODE -#if FEATURE_TARSTREAM_SUPPORT - case LabelType::DISABLE_SAVE_CONFIG_AS_TAR: return F("Disable Save Config as .tar"); -#endif // if FEATURE_TARSTREAM_SUPPORT - #if FEATURE_TASKVALUE_UNIT_OF_MEASURE - case LabelType::SHOW_UOM_ON_DEVICES_PAGE: return F("Show Unit of Measure"); - #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE - - case LabelType::BOOT_TYPE: return F("Last Boot Cause"); - case LabelType::BOOT_COUNT: return F("Boot Count"); - case LabelType::DEEP_SLEEP_ALTERNATIVE_CALL: return F("Deep Sleep Alternative"); - case LabelType::RESET_REASON: return F("Reset Reason"); - case LabelType::LAST_TASK_BEFORE_REBOOT: return F("Last Action before Reboot"); - case LabelType::SW_WD_COUNT: return F("SW WD count"); - - - case LabelType::WIFI_CONNECTION: return F("WiFi Connection"); - case LabelType::WIFI_RSSI: return F("RSSI"); - case LabelType::IP_CONFIG: return F("IP Config"); -#if FEATURE_USE_IPV6 - case LabelType::IP6_LOCAL: return F("IPv6 link local"); - case LabelType::IP6_GLOBAL: return F("IPv6 global"); -// case LabelType::IP6_ALL_ADDRESSES: return F("IPv6 all addresses"); -#endif - case LabelType::IP_CONFIG_STATIC: return F("Static"); - case LabelType::IP_CONFIG_DYNAMIC: return F("DHCP"); - case LabelType::IP_ADDRESS: return F("IP Address"); - case LabelType::IP_SUBNET: return F("IP Subnet"); - case LabelType::IP_ADDRESS_SUBNET: return F("IP / Subnet"); - case LabelType::GATEWAY: return F("Gateway"); - case LabelType::CLIENT_IP: return F("Client IP"); - #if FEATURE_MDNS - case LabelType::M_DNS: return F("mDNS"); - #endif // if FEATURE_MDNS - case LabelType::DNS: return F("DNS"); - case LabelType::DNS_1: return F("DNS 1"); - case LabelType::DNS_2: return F("DNS 2"); - case LabelType::ALLOWED_IP_RANGE: return F("Allowed IP Range"); - case LabelType::STA_MAC: return F("STA MAC"); - case LabelType::AP_MAC: return F("AP MAC"); - case LabelType::SSID: return F("SSID"); - case LabelType::BSSID: return F("BSSID"); - case LabelType::CHANNEL: return F("Channel"); - case LabelType::ENCRYPTION_TYPE_STA: return F("Encryption Type"); - case LabelType::CONNECTED: return F("Connected"); - case LabelType::CONNECTED_MSEC: return F("Connected msec"); - case LabelType::LAST_DISCONNECT_REASON: return F("Last Disconnect Reason"); - case LabelType::LAST_DISC_REASON_STR: return F("Last Disconnect Reason str"); - case LabelType::NUMBER_RECONNECTS: return F("Number Reconnects"); - case LabelType::WIFI_STORED_SSID1: return F("Configured SSID1"); - case LabelType::WIFI_STORED_SSID2: return F("Configured SSID2"); - - - case LabelType::FORCE_WIFI_BG: return F("Force WiFi B/G"); - case LabelType::RESTART_WIFI_LOST_CONN: return F("Restart WiFi Lost Conn"); - case LabelType::FORCE_WIFI_NOSLEEP: return F("Force WiFi No Sleep"); - case LabelType::PERIODICAL_GRAT_ARP: return F("Periodical send Gratuitous ARP"); - case LabelType::CONNECTION_FAIL_THRESH: return F("Connection Failure Threshold"); -#ifndef ESP32 - case LabelType::WAIT_WIFI_CONNECT: return F("Extra Wait WiFi Connect"); -#endif - case LabelType::CONNECT_HIDDEN_SSID: return F("Include Hidden SSID"); -#ifdef ESP32 - case LabelType::WIFI_PASSIVE_SCAN: return F("Passive WiFi Scan"); -#endif - case LabelType::HIDDEN_SSID_SLOW_CONNECT: return F("Hidden SSID Slow Connect"); - case LabelType::SDK_WIFI_AUTORECONNECT: return F("Enable SDK WiFi Auto Reconnect"); -#if FEATURE_USE_IPV6 - case LabelType::ENABLE_IPV6: return F("Enable IPv6"); -#endif - - - case LabelType::BUILD_DESC: return F("Build"); - case LabelType::GIT_BUILD: return F("Git Build"); - case LabelType::SYSTEM_LIBRARIES: return F("System Libraries"); -#ifdef ESP32 - case LabelType::ESP_IDF_SDK_VERSION: return F("ESP-IDF Version"); -#endif - case LabelType::PLUGIN_COUNT: return F("Plugin Count"); - case LabelType::PLUGIN_DESCRIPTION: return F("Plugin Description"); - case LabelType::BUILD_TIME: return F("Build Time"); - case LabelType::BINARY_FILENAME: return F("Binary Filename"); - case LabelType::BUILD_PLATFORM: return F("Build Platform"); - case LabelType::GIT_HEAD: return F("Git HEAD"); - #ifdef CONFIGURATION_CODE - case LabelType::CONFIGURATION_CODE_LBL: return F("Configuration code"); - #endif // ifdef CONFIGURATION_CODE - - case LabelType::I2C_BUS_STATE: return F("I2C Bus State"); - case LabelType::I2C_BUS_CLEARED_COUNT: return F("I2C bus cleared count"); - - case LabelType::SYSLOG_LOG_LEVEL: return F("Syslog Log Level"); - case LabelType::SERIAL_LOG_LEVEL: return F("Serial Log Level"); - case LabelType::WEB_LOG_LEVEL: return F("Web Log Level"); - #if FEATURE_SD - case LabelType::SD_LOG_LEVEL: return F("SD Log Level"); - #endif // if FEATURE_SD - - case LabelType::ESP_CHIP_ID: return F("ESP Chip ID"); - case LabelType::ESP_CHIP_FREQ: return F("ESP Chip Frequency"); -#ifdef ESP32 - case LabelType::ESP_CHIP_XTAL_FREQ: return F("ESP Crystal Frequency"); - case LabelType::ESP_CHIP_APB_FREQ: return F("ESP APB Frequency"); -#endif - case LabelType::ESP_CHIP_MODEL: return F("ESP Chip Model"); - case LabelType::ESP_CHIP_REVISION: return F("ESP Chip Revision"); - case LabelType::ESP_CHIP_CORES: return F("ESP Chip Cores"); - - case LabelType::BOARD_NAME: return F("ESP Board Name"); - - case LabelType::FLASH_CHIP_ID: return F("Flash Chip ID"); - case LabelType::FLASH_CHIP_VENDOR: return F("Flash Chip Vendor"); - case LabelType::FLASH_CHIP_MODEL: return F("Flash Chip Model"); - case LabelType::FLASH_CHIP_REAL_SIZE: return F("Flash Chip Real Size"); - case LabelType::FLASH_CHIP_SPEED: return F("Flash Chip Speed"); - case LabelType::FLASH_IDE_SIZE: return F("Flash IDE Size"); - case LabelType::FLASH_IDE_SPEED: return F("Flash IDE Speed"); - case LabelType::FLASH_IDE_MODE: return F("Flash IDE Mode"); - case LabelType::FLASH_WRITE_COUNT: return F("Flash Writes"); - case LabelType::SKETCH_SIZE: return F("Sketch Size"); - case LabelType::SKETCH_FREE: return F("Sketch Free"); - #ifdef USE_LITTLEFS - case LabelType::FS_SIZE: return F("Little FS Size"); - case LabelType::FS_FREE: return F("Little FS Free"); - #else // ifdef USE_LITTLEFS - case LabelType::FS_SIZE: return F("SPIFFS Size"); - case LabelType::FS_FREE: return F("SPIFFS Free"); - #endif // ifdef USE_LITTLEFS - case LabelType::MAX_OTA_SKETCH_SIZE: return F("Max. OTA Sketch Size"); - case LabelType::OTA_2STEP: return F("OTA 2-step Needed"); - case LabelType::OTA_POSSIBLE: return F("OTA possible"); - #if FEATURE_INTERNAL_TEMPERATURE - case LabelType::INTERNAL_TEMPERATURE: return F("Internal Temperature"); - #endif // if FEATURE_INTERNAL_TEMPERATURE -#if FEATURE_ETHERNET - case LabelType::ETH_IP_ADDRESS: return F("Eth IP Address"); - case LabelType::ETH_IP_SUBNET: return F("Eth IP Subnet"); - case LabelType::ETH_IP_ADDRESS_SUBNET: return F("Eth IP / Subnet"); - case LabelType::ETH_IP_GATEWAY: return F("Eth Gateway"); - case LabelType::ETH_IP_DNS: return F("Eth DNS"); -#if FEATURE_USE_IPV6 - case LabelType::ETH_IP6_LOCAL: return F("Eth IPv6 link local"); -#endif - case LabelType::ETH_MAC: return F("Eth MAC"); - case LabelType::ETH_DUPLEX: return F("Eth Mode"); - case LabelType::ETH_SPEED: return F("Eth Speed"); - case LabelType::ETH_STATE: return F("Eth State"); - case LabelType::ETH_SPEED_STATE: return F("Eth Speed State"); - case LabelType::ETH_CONNECTED: return F("Eth connected"); - case LabelType::ETH_CHIP: return F("Eth chip"); -#endif // if FEATURE_ETHERNET -# if FEATURE_ETHERNET || defined(USES_ESPEASY_NOW) - case LabelType::ETH_WIFI_MODE: return F("Network Type"); -#endif - case LabelType::SUNRISE: return F("Sunrise"); - case LabelType::SUNSET: return F("Sunset"); - case LabelType::SUNRISE_S: return F("Sunrise sec."); - case LabelType::SUNSET_S: return F("Sunset sec."); - case LabelType::SUNRISE_M: return F("Sunrise min."); - case LabelType::SUNSET_M: return F("Sunset min."); - case LabelType::ISNTP: return F("Use NTP"); - case LabelType::UPTIME_MS: return F("Uptime (ms)"); - case LabelType::TIMEZONE_OFFSET: return F("Timezone Offset"); - case LabelType::LATITUDE: return F("Latitude"); - case LabelType::LONGITUDE: return F("Longitude"); - - case LabelType::MAX_LABEL: - break; - - } - return F("MissingString"); -} - -String getValue(LabelType::Enum label) { - int retval = INT_MAX; - switch (label) - { - case LabelType::UNIT_NR: retval = Settings.Unit; break; - #if FEATURE_ZEROFILLED_UNITNUMBER - case LabelType::UNIT_NR_0: + case LabelType::UNIT_NR_0: { // Fixed 3-digit unitnumber - return formatIntLeadingZeroes(Settings.Unit, 3); + return KeyValueStruct(F("Unit Number 0-filled"), formatIntLeadingZeroes(Settings.Unit, 3)); } #endif // FEATURE_ZEROFILLED_UNITNUMBER - case LabelType::UNIT_NAME: return Settings.getName(); // Only return the set name, no appended unit. - case LabelType::HOST_NAME: return NetworkGetHostname(); - - - case LabelType::LOCAL_TIME: return node_time.getDateTimeString('-', ':', ' '); - case LabelType::TIME_SOURCE: + case LabelType::UNIT_NAME: { - String timeSource_str = toString(node_time.getTimeSource()); - if (((node_time.getTimeSource() == timeSource_t::ESPEASY_p2p_UDP) || - (node_time.getTimeSource() == timeSource_t::ESP_now_peer)) && - (node_time.timeSource_p2p_unit != 0)) - { - return strformat(F("%s (%u)"), timeSource_str.c_str(), node_time.timeSource_p2p_unit); - } - return timeSource_str; + // Only return the set name, no appended unit. + return KeyValueStruct(F("Unit Name"), Settings.getName()); } - case LabelType::TIME_WANDER: return String(node_time.timeWander, 3); + case LabelType::HOST_NAME: + { + return KeyValueStruct(F("Hostname"), ESPEasy::net::NetworkGetHostname()); + } + + case LabelType::LOCAL_TIME: + + if (node_time.systemTimePresent()) + { + return KeyValueStruct(F("Local Time"), node_time.getDateTimeString('-', ':', ' ')); + } else if (extendedValue) { + return KeyValueStruct(F("Local Time"), F("No system time source")); + } + break; + case LabelType::TIME_SOURCE: + + if (node_time.systemTimePresent()) + { + String timeSource_str = toString(node_time.getTimeSource()); + + if (((node_time.getTimeSource() == timeSource_t::ESPEASY_p2p_UDP) || + (node_time.getTimeSource() == timeSource_t::ESP_now_peer)) && + (node_time.timeSource_p2p_unit != 0)) + { + timeSource_str = strformat(F("%s (%u)"), timeSource_str.c_str(), node_time.timeSource_p2p_unit); + } + + return KeyValueStruct(F("Time Source"), timeSource_str); + } + break; + case LabelType::TIME_WANDER: + + if (node_time.systemTimePresent()) + { + KeyValueStruct kv(F("Time Wander"), node_time.timeWander, 3); +#if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_ppm); +#endif + return kv; + } + break; #if FEATURE_EXT_RTC case LabelType::EXT_RTC_UTC_TIME: { - if (Settings.ExtTimeSource() != ExtTimeSource_e::None) { - // Try to read the stored time in the ext. time source to allow to check if it is working properly. - uint32_t unixtime; - if (node_time.ExtRTC_get(unixtime)) { - struct tm RTC_time; - breakTime(unixtime, RTC_time); - return formatDateTimeString(RTC_time); - } else { - return F("Not Set"); - } + if (Settings.ExtTimeSource() == ExtTimeSource_e::None) { break; } + String rtcTime = F("Not Set"); + + // Try to read the stored time in the ext. time source to allow to check if it is working properly. + uint32_t unixtime; + + if (node_time.ExtRTC_get(unixtime)) { + struct tm RTC_time; + breakTime(unixtime, RTC_time); + rtcTime = formatDateTimeString(RTC_time); } - return String('-'); + return KeyValueStruct(F("UTC time stored in RTC chip"), rtcTime); + } + #endif // if FEATURE_EXT_RTC + case LabelType::UPTIME: + { + if (extendedValue) { + return KeyValueStruct(F("Uptime"), minutesToDayHourMinute(getUptimeMinutes())); + + } else { + return KeyValueStruct(F("Uptime"), getUptimeMinutes()); + + } + } + case LabelType::LOAD_PCT: + + if (wdcounter > 0) + { + if (extendedValue) { + return KeyValueStruct(F("Load"), strformat( + F("%.2f [%%] (LC=%d)"), + getCPUload(), + getLoopCountPerSec())); + } + KeyValueStruct kv(F("Load"), getCPUload(), 2); +#if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_percent); +#endif + return kv; + } + break; + case LabelType::LOOP_COUNT: + { + if (extendedValue) { break; } + return KeyValueStruct(F("Load LC"), getLoopCountPerSec()); + } + case LabelType::CPU_ECO_MODE: + { + return KeyValueStruct(F("CPU Eco Mode"), Settings.EcoPowerMode()); } - #endif - case LabelType::UPTIME: retval = getUptimeMinutes(); break; - case LabelType::LOAD_PCT: return toString(getCPUload(), 2); - case LabelType::LOOP_COUNT: retval = getLoopCountPerSec(); break; - case LabelType::CPU_ECO_MODE: return jsonBool(Settings.EcoPowerMode()); #if FEATURE_SET_WIFI_TX_PWR - case LabelType::WIFI_TX_MAX_PWR: return toString(Settings.getWiFi_TX_power(), 2); - case LabelType::WIFI_CUR_TX_PWR: return toString(WiFiEventData.wifi_TX_pwr, 2); - case LabelType::WIFI_SENS_MARGIN: retval = Settings.WiFi_sensitivity_margin; break; - case LabelType::WIFI_SEND_AT_MAX_TX_PWR:return jsonBool(Settings.UseMaxTXpowerForSending()); -#endif - case LabelType::WIFI_NR_EXTRA_SCANS: retval = Settings.NumberExtraWiFiScans; break; - case LabelType::WIFI_USE_LAST_CONN_FROM_RTC: return jsonBool(Settings.UseLastWiFiFromRTC()); - - case LabelType::FREE_MEM: retval = FreeMem(); break; - case LabelType::FREE_STACK: retval = getCurrentFreeStack(); break; - -#ifdef USE_SECOND_HEAP - case LabelType::FREE_HEAP_IRAM: retval = FreeMem2ndHeap(); break; -#endif - -#if defined(CORE_POST_2_5_0) - #ifndef LIMIT_BUILD_SIZE - case LabelType::HEAP_MAX_FREE_BLOCK: retval = ESP.getMaxFreeBlockSize(); break; - #endif -#endif // if defined(CORE_POST_2_5_0) -#if defined(ESP32) - #ifndef LIMIT_BUILD_SIZE - case LabelType::HEAP_MAX_FREE_BLOCK: retval = ESP.getMaxAllocHeap(); break; - #endif -#endif // if defined(ESP32) -#if defined(CORE_POST_2_5_0) - #ifndef LIMIT_BUILD_SIZE - case LabelType::HEAP_FRAGMENTATION: retval = ESP.getHeapFragmentation(); break; - #endif -#endif // if defined(CORE_POST_2_5_0) + case LabelType::WIFI_TX_MAX_PWR: + { + KeyValueStruct kv(F("Max WiFi TX Power"), Settings.getWiFi_TX_power(), 2); +# if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_dBm); +# endif + return kv; + } + case LabelType::WIFI_CUR_TX_PWR: + if (ESPEasy::net::wifi::WiFiConnected()) + { + KeyValueStruct kv(F("Current WiFi TX Power"), ESPEasy::net::wifi::GetWiFiTXpower(), 2); +# if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_dBm); +# endif + return kv; + } + break; + case LabelType::WIFI_SENS_MARGIN: + { + KeyValueStruct kv(F("WiFi Sensitivity Margin"), Settings.WiFi_sensitivity_margin); +# if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_dB); +# endif + return kv; + } + case LabelType::WIFI_SEND_AT_MAX_TX_PWR: + { + return KeyValueStruct(F("Send With Max TX Power"), Settings.UseMaxTXpowerForSending()); + } +#endif // if FEATURE_SET_WIFI_TX_PWR + case LabelType::WIFI_AP_CHANNEL: + { + return KeyValueStruct(F("Wifi AP channel"), Settings.WiFiAP_channel); + } + case LabelType::WIFI_ENABLE_CAPTIVE_PORTAL: + { + return KeyValueStruct(F("Force /setup in AP-Mode"), Settings.ApCaptivePortal()); + } + case LabelType::WIFI_START_AP_NO_CREDENTIALS: + { + return KeyValueStruct(F("Start AP on No Credentials"), Settings.StartAPfallback_NoCredentials()); + } + case LabelType::WIFI_START_AP_ON_CONNECT_FAIL: + { + return KeyValueStruct(F("Start AP on Connect Fail"), !Settings.DoNotStartAPfallback_ConnectFail()); + } + case LabelType::WIFI_START_AP_ON_NW002_INIT: + { + return KeyValueStruct(F("Auto Start AP"), Settings.StartAP_on_NW002_init()); + } + case LabelType::WIFI_NR_RECONNECT_ATTEMPTS: + { + return KeyValueStruct(F("Connect Retry Attempts"), Settings.ConnectFailRetryCount); + } + case LabelType::WIFI_MAX_UPTIME_AUTO_START_AP: + { + KeyValueStruct kv(F("Max. Uptime to Start AP"), Settings.APfallback_autostart_max_uptime_m()); +# if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_min); +# endif + return kv; + } + case LabelType::WIFI_AP_MINIMAL_ON_TIME: + { + KeyValueStruct kv(F("AP Minimal 'on' Time"), Settings.APfallback_minimal_on_time_sec()); +# if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_sec); +# endif + return kv; + } #ifdef ESP32 - case LabelType::HEAP_SIZE: retval = ESP.getHeapSize(); break; - case LabelType::HEAP_MIN_FREE: retval = ESP.getMinFreeHeap(); break; - #ifdef BOARD_HAS_PSRAM - case LabelType::PSRAM_SIZE: retval = UsePSRAM() ? ESP.getPsramSize() : 0; break; - case LabelType::PSRAM_FREE: retval = UsePSRAM() ? ESP.getFreePsram() : 0; break; - case LabelType::PSRAM_MIN_FREE: retval = UsePSRAM() ? ESP.getMinFreePsram() : 0; break; - case LabelType::PSRAM_MAX_FREE_BLOCK: retval = UsePSRAM() ? ESP.getMaxAllocPsram() : 0; break; - #endif // BOARD_HAS_PSRAM + case LabelType::WIFI_AP_ENABLE_NAPT: + { + return KeyValueStruct(F("Enable NAPT"), Settings.WiFi_AP_enable_NAPT()); + } +#endif + + case LabelType::WIFI_USE_LAST_CONN_FROM_RTC: + { + return KeyValueStruct(F("Use Last Connected AP from RTC"), Settings.UseLastWiFiFromRTC()); + } + + case LabelType::FREE_MEM: + { +#ifndef BUILD_NO_RAM_TRACKER + + if (extendedValue) { + return KeyValueStruct(F("Free RAM"), + strformat( + F("%d [B] (%d - %s)"), + FreeMem(), + lowestRAM, + lowestRAMfunction.c_str())); + } +#endif // ifndef BUILD_NO_RAM_TRACKER + + KeyValueStruct kv(F("Free RAM"), FreeMem()); +#if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_Byte); +#endif + return kv; + } + case LabelType::FREE_STACK: + { +#ifndef BUILD_NO_RAM_TRACKER + + if (extendedValue) { + return KeyValueStruct(F("Free Stack"), + strformat( + F("%d [B] (%d - %s)"), + getCurrentFreeStack(), + lowestFreeStack, + lowestFreeStackfunction.c_str())); + } +#endif // ifndef BUILD_NO_RAM_TRACKER + KeyValueStruct kv(F("Free Stack"), getCurrentFreeStack()); +#if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_Byte); +#endif + + return kv; + } +#ifdef USE_SECOND_HEAP + case LabelType::FREE_HEAP_IRAM: + { + KeyValueStruct kv(F("Free 2nd Heap"), FreeMem2ndHeap()); +# if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_Byte); +# endif + return kv; + } +#endif // ifdef USE_SECOND_HEAP + +#if defined(CORE_POST_2_5_0) || defined(ESP32) + # ifndef LIMIT_BUILD_SIZE + case LabelType::HEAP_MAX_FREE_BLOCK: + { + KeyValueStruct kv(F("Heap Max Free Block"), +# ifdef ESP32 + ESP.getMaxAllocHeap() +# else + ESP.getMaxFreeBlockSize() +# endif // ifdef ESP32 + ); +# if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_Byte); +# endif + return kv; + } + # endif // ifndef LIMIT_BUILD_SIZE +#endif // if defined(CORE_POST_2_5_0) || defined(ESP32) +#if defined(CORE_POST_2_5_0) + # ifndef LIMIT_BUILD_SIZE + case LabelType::HEAP_FRAGMENTATION: + { + KeyValueStruct kv(F("Heap Fragmentation"), ESP.getHeapFragmentation()); +# if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_percent); +# endif + return kv; + } + # endif // ifndef LIMIT_BUILD_SIZE +#endif // if defined(CORE_POST_2_5_0) + +#ifdef ESP32 + case LabelType::HEAP_SIZE: + { + KeyValueStruct kv(F("Heap Size"), ESP.getHeapSize()); +# if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_Byte); +# endif + return kv; + } + case LabelType::HEAP_MIN_FREE: + { + KeyValueStruct kv(F("Heap Min Free"), ESP.getMinFreeHeap()); +# if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_Byte); +# endif + return kv; + } + # ifdef BOARD_HAS_PSRAM + case LabelType::PSRAM_SIZE: + { + if (!UsePSRAM()) { break; } + KeyValueStruct kv(F("PSRAM Size"), ESP.getPsramSize()); +# if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_Byte); +# endif + return kv; + } + case LabelType::PSRAM_FREE: + { + if (!UsePSRAM()) { break; } + KeyValueStruct kv(F("PSRAM Free"), ESP.getFreePsram()); +# if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_Byte); +# endif + return kv; + } + case LabelType::PSRAM_MIN_FREE: + { + if (!UsePSRAM()) { break; } + KeyValueStruct kv(F("PSRAM Min Free"), ESP.getMinFreePsram()); +# if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_Byte); +# endif + return kv; + } + case LabelType::PSRAM_MAX_FREE_BLOCK: + { + if (!UsePSRAM()) { break; } + KeyValueStruct kv(F("PSRAM Max Free Block"), ESP.getMaxAllocPsram()); +# if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_Byte); +# endif + return kv; + } + # endif // BOARD_HAS_PSRAM #endif // ifdef ESP32 - - case LabelType::JSON_BOOL_QUOTES: return jsonBool(Settings.JSONBoolWithoutQuotes()); + case LabelType::JSON_BOOL_QUOTES: + { + return KeyValueStruct(F("JSON bool output without quotes"), Settings.JSONBoolWithoutQuotes()); + } #if FEATURE_TIMING_STATS - case LabelType::ENABLE_TIMING_STATISTICS: return jsonBool(Settings.EnableTimingStats()); -#endif - case LabelType::ENABLE_RULES_CACHING: return jsonBool(Settings.EnableRulesCaching()); - case LabelType::ENABLE_SERIAL_PORT_CONSOLE: return jsonBool(Settings.UseSerial); - case LabelType::CONSOLE_SERIAL_PORT: return ESPEasy_Console.getPortDescription(); - + case LabelType::ENABLE_TIMING_STATISTICS: + { + return KeyValueStruct(F("Collect Timing Statistics"), Settings.EnableTimingStats()); + } +#endif // if FEATURE_TIMING_STATS + case LabelType::ENABLE_RULES_CACHING: + { + return KeyValueStruct(F("Enable Rules Cache"), Settings.EnableRulesCaching()); + } + case LabelType::ENABLE_SERIAL_PORT_CONSOLE: + { + return KeyValueStruct(F("Enable Serial Port Console"), !!Settings.UseSerial); + } + case LabelType::CONSOLE_SERIAL_PORT: + { + return KeyValueStruct(F("Console Serial Port"), ESPEasy_Console.getPortDescription()); + } #if USES_ESPEASY_CONSOLE_FALLBACK_PORT - case LabelType::CONSOLE_FALLBACK_TO_SERIAL0: return jsonBool(Settings.console_serial0_fallback); - case LabelType::CONSOLE_FALLBACK_PORT: return ESPEasy_Console.getFallbackPortDescription(); -#endif + case LabelType::CONSOLE_FALLBACK_TO_SERIAL0: + { + return KeyValueStruct(F("Fallback to Serial 0"), Settings.console_serial0_fallback); + } + case LabelType::CONSOLE_FALLBACK_PORT: + { + return KeyValueStruct(F("Console Fallback Port"), ESPEasy_Console.getFallbackPortDescription()); + } +#endif // if USES_ESPEASY_CONSOLE_FALLBACK_PORT -// case LabelType::ENABLE_RULES_EVENT_REORDER: return jsonBool(Settings.EnableRulesEventReorder()); // TD-er: Disabled for now - case LabelType::TASKVALUESET_ALL_PLUGINS: return jsonBool(Settings.AllowTaskValueSetAllPlugins()); - case LabelType::ALLOW_OTA_UNLIMITED: return jsonBool(Settings.AllowOTAUnlimited()); + // case LabelType::ENABLE_RULES_EVENT_REORDER: { + // return KeyValueStruct( F("Optimize Rules Cache Event Order"), Settings.EnableRulesEventReorder() ); + // } // TD-er: Disabled for now + case LabelType::TASKVALUESET_ALL_PLUGINS: + { + return KeyValueStruct(F("Allow TaskValueSet on all plugins"), Settings.AllowTaskValueSetAllPlugins()); + } + case LabelType::ALLOW_OTA_UNLIMITED: + { + return KeyValueStruct(F("Allow OTA without size-check"), Settings.AllowOTAUnlimited()); + } #if FEATURE_CLEAR_I2C_STUCK - case LabelType::ENABLE_CLEAR_HUNG_I2C_BUS: return jsonBool(Settings.EnableClearHangingI2Cbus()); -#endif -#if FEATURE_I2C_DEVICE_CHECK - case LabelType::ENABLE_I2C_DEVICE_CHECK: return jsonBool(Settings.CheckI2Cdevice()); -#endif // if FEATURE_I2C_DEVICE_CHECK + case LabelType::ENABLE_CLEAR_HUNG_I2C_BUS: + { + return KeyValueStruct(F("Try clear I2C bus when stuck"), Settings.EnableClearHangingI2Cbus()); + } +#endif // if FEATURE_CLEAR_I2C_STUCK + #if FEATURE_I2C_DEVICE_CHECK + case LabelType::ENABLE_I2C_DEVICE_CHECK: + { + return KeyValueStruct(F("Check I2C devices when enabled"), Settings.CheckI2Cdevice()); + } + #endif // if FEATURE_I2C_DEVICE_CHECK #ifndef BUILD_NO_RAM_TRACKER - case LabelType::ENABLE_RAM_TRACKING: return jsonBool(Settings.EnableRAMTracking()); -#endif + case LabelType::ENABLE_RAM_TRACKING: + { + return KeyValueStruct(F("Enable RAM Tracker"), Settings.EnableRAMTracking()); + } +#endif // ifndef BUILD_NO_RAM_TRACKER #if FEATURE_AUTO_DARK_MODE - case LabelType::ENABLE_AUTO_DARK_MODE: return toString(Settings.getCssMode()); + case LabelType::ENABLE_AUTO_DARK_MODE: + { + return KeyValueStruct(F("Web light/dark mode"), Settings.getCssMode()); + } #endif // FEATURE_AUTO_DARK_MODE #if FEATURE_RULES_EASY_COLOR_CODE - case LabelType::DISABLE_RULES_AUTOCOMPLETE: return jsonBool(Settings.DisableRulesCodeCompletion()); + case LabelType::DISABLE_RULES_AUTOCOMPLETE: + { + return KeyValueStruct(F("Disable Rules auto-completion"), Settings.DisableRulesCodeCompletion()); + } #endif // if FEATURE_RULES_EASY_COLOR_CODE #if FEATURE_TARSTREAM_SUPPORT - case LabelType::DISABLE_SAVE_CONFIG_AS_TAR: return jsonBool(Settings.DisableSaveConfigAsTar()); -#endif // if FEATURE_TARSTREAM_SUPPORT - #if FEATURE_TASKVALUE_UNIT_OF_MEASURE - case LabelType::SHOW_UOM_ON_DEVICES_PAGE: return jsonBool(Settings.ShowUnitOfMeasureOnDevicesPage()); - #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE - - case LabelType::BOOT_TYPE: return getLastBootCauseString(); - case LabelType::BOOT_COUNT: break; - case LabelType::DEEP_SLEEP_ALTERNATIVE_CALL: return jsonBool(Settings.UseAlternativeDeepSleep()); - case LabelType::RESET_REASON: return getResetReasonString(); - case LabelType::LAST_TASK_BEFORE_REBOOT: return ESPEasy_Scheduler::decodeSchedulerId(lastMixedSchedulerId_beforereboot); - case LabelType::SW_WD_COUNT: retval = sw_watchdog_callback_count; break; - - case LabelType::WIFI_CONNECTION: break; - case LabelType::WIFI_RSSI: retval = WiFi.RSSI(); break; - case LabelType::IP_CONFIG: return useStaticIP() - ? getLabel(LabelType::IP_CONFIG_STATIC) - : getLabel(LabelType::IP_CONFIG_DYNAMIC); - case LabelType::IP_CONFIG_STATIC: break; - case LabelType::IP_CONFIG_DYNAMIC: break; - case LabelType::IP_ADDRESS: return formatIP(NetworkLocalIP()); - case LabelType::IP_SUBNET: return formatIP(NetworkSubnetMask()); - case LabelType::IP_ADDRESS_SUBNET: return strformat(F("%s / %s"), getValue(LabelType::IP_ADDRESS).c_str(), getValue(LabelType::IP_SUBNET).c_str()); - case LabelType::GATEWAY: return formatIP(NetworkGatewayIP()); -#if FEATURE_USE_IPV6 - case LabelType::IP6_LOCAL: return formatIP(NetworkLocalIP6(), true); - case LabelType::IP6_GLOBAL: return formatIP(NetworkGlobalIP6()); -#if FEATURE_ETHERNET - case LabelType::ETH_IP6_LOCAL: return formatIP(NetworkLocalIP6(), true); -#endif -/* - case LabelType::IP6_ALL_ADDRESSES: + case LabelType::DISABLE_SAVE_CONFIG_AS_TAR: { - IP6Addresses_t addresses = NetworkAllIPv6(); - String res; - for (auto it = addresses.begin(); it != addresses.end(); ++it) - { - if (!res.isEmpty()) { - res += F("
"); - } - res += it->toString(); - } - return res; + return KeyValueStruct(F("Disable Save Config as .tar"), Settings.DisableSaveConfigAsTar()); } -*/ -#endif - case LabelType::CLIENT_IP: return formatIP(web_server.client().remoteIP(), true); - #if FEATURE_INTERNAL_TEMPERATURE - case LabelType::INTERNAL_TEMPERATURE: return toString(getInternalTemperature()); - #endif // if FEATURE_INTERNAL_TEMPERATURE +#endif // if FEATURE_TARSTREAM_SUPPORT +#if FEATURE_TASKVALUE_UNIT_OF_MEASURE + case LabelType::SHOW_UOM_ON_DEVICES_PAGE: + { + return KeyValueStruct(F("Show Unit of Measure"), Settings.ShowUnitOfMeasureOnDevicesPage()); + } + #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE + #if FEATURE_MQTT_CONNECT_BACKGROUND + case LabelType::MQTT_CONNECT_IN_BACKGROUND: + { + return KeyValueStruct(F("MQTT Connect in background"), Settings.MQTTConnectInBackground()); + } + #endif // if FEATURE_MQTT_CONNECT_BACKGROUND - #if FEATURE_MDNS - case LabelType::M_DNS: return NetworkGetHostname() + F(".local"); - #endif // if FEATURE_MDNS - case LabelType::DNS: return strformat(F("%s / %s"), getValue(LabelType::DNS_1).c_str(), getValue(LabelType::DNS_2).c_str()); - case LabelType::DNS_1: return formatIP(NetworkDnsIP(0)); - case LabelType::DNS_2: return formatIP(NetworkDnsIP(1)); - case LabelType::ALLOWED_IP_RANGE: return describeAllowedIPrange(); - case LabelType::STA_MAC: return WifiSTAmacAddress().toString(); - case LabelType::AP_MAC: return WifiSoftAPmacAddress().toString(); - case LabelType::SSID: return WiFi.SSID(); - case LabelType::BSSID: return WiFi.BSSIDstr(); - case LabelType::CHANNEL: retval = WiFi.channel(); break; - case LabelType::ENCRYPTION_TYPE_STA: return // WiFi_AP_Candidates.getCurrent().encryption_type(); - WiFi_encryptionType(WiFiEventData.auth_mode); - case LabelType::CONNECTED: - #if FEATURE_ETHERNET - if(active_network_medium == NetworkMedium_t::Ethernet) { - return format_msec_duration(EthEventData.lastConnectMoment.millisPassedSince()); +#if CONFIG_SOC_WIFI_SUPPORT_5G + case LabelType::WIFI_BAND_MODE: + { + return KeyValueStruct(F("WiFi Band Mode"), ESPEasy::net::wifi::getWifiBandModeString(Settings.WiFi_band_mode())); + } +#endif // if CONFIG_SOC_WIFI_SUPPORT_5G + + case LabelType::BOOT_TYPE: + { + if (extendedValue) { + KeyValueStruct kv( + F("Boot"), + concat( + getLastBootCauseString(), + strformat(F(" (%d)"), RTC.bootCounter))); + return kv; } - #endif // if FEATURE_ETHERNET - return format_msec_duration(WiFiEventData.lastConnectMoment.millisPassedSince()); + return KeyValueStruct(F("Last Boot Cause"), getLastBootCauseString()); + } + case LabelType::BOOT_COUNT: + { + return KeyValueStruct(F("Boot Count"), RTC.bootCounter); + } + case LabelType::DEEP_SLEEP_ALTERNATIVE_CALL: + { + return KeyValueStruct(F("Deep Sleep Alternative"), Settings.UseAlternativeDeepSleep()); + } + case LabelType::RESET_REASON: + { + return KeyValueStruct(F("Reset Reason"), getResetReasonString()); + } + case LabelType::LAST_TASK_BEFORE_REBOOT: + { + return KeyValueStruct(F("Last Action before Reboot"), ESPEasy_Scheduler::decodeSchedulerId(lastMixedSchedulerId_beforereboot)); + } + case LabelType::SW_WD_COUNT: + { + return KeyValueStruct(F("SW WD count"), sw_watchdog_callback_count); + } - // Use only the nr of seconds to fit it in an int32, plus append '000' to have msec format again. - case LabelType::CONNECTED_MSEC: - #if FEATURE_ETHERNET - if(active_network_medium == NetworkMedium_t::Ethernet) { - return String(static_cast(EthEventData.lastConnectMoment.millisPassedSince() / 1000ll)) + F("000"); + case LabelType::WIFI_CONNECTION: + { + return KeyValueStruct(F("WiFi Connection") /*, value*/); + } + case LabelType::WIFI_RSSI: + + if (ESPEasy::net::wifi::WiFiConnected()) + { + if (extendedValue) { + return KeyValueStruct(F("RSSI"), strformat( + F("%d [dBm] (%s)"), + WiFi.RSSI(), + WiFi.SSID().c_str())); + } + KeyValueStruct kv(F("RSSI"), WiFi.RSSI()); +#if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_dBm); +#endif + return kv; } - #endif // if FEATURE_ETHERNET - return String(static_cast(WiFiEventData.lastConnectMoment.millisPassedSince() / 1000ll)) + F("000"); - case LabelType::LAST_DISCONNECT_REASON: return String(WiFiEventData.lastDisconnectReason); - case LabelType::LAST_DISC_REASON_STR: return getLastDisconnectReason(); - case LabelType::NUMBER_RECONNECTS: retval = WiFiEventData.wifi_reconnects; break; - case LabelType::WIFI_STORED_SSID1: return String(SecuritySettings.WifiSSID); - case LabelType::WIFI_STORED_SSID2: return String(SecuritySettings.WifiSSID2); - - - case LabelType::FORCE_WIFI_BG: return jsonBool(Settings.ForceWiFi_bg_mode()); - case LabelType::RESTART_WIFI_LOST_CONN: return jsonBool(Settings.WiFiRestart_connection_lost()); - case LabelType::FORCE_WIFI_NOSLEEP: return jsonBool(Settings.WifiNoneSleep()); - case LabelType::PERIODICAL_GRAT_ARP: return jsonBool(Settings.gratuitousARP()); - case LabelType::CONNECTION_FAIL_THRESH: retval = Settings.ConnectionFailuresThreshold; break; -#ifndef ESP32 - case LabelType::WAIT_WIFI_CONNECT: return jsonBool(Settings.WaitWiFiConnect()); -#endif - case LabelType::CONNECT_HIDDEN_SSID: return jsonBool(Settings.IncludeHiddenSSID()); -#ifdef ESP32 - case LabelType::WIFI_PASSIVE_SCAN: return jsonBool(Settings.PassiveWiFiScan()); -#endif - case LabelType::HIDDEN_SSID_SLOW_CONNECT: return jsonBool(Settings.HiddenSSID_SlowConnectPerBSSID()); - case LabelType::SDK_WIFI_AUTORECONNECT: return jsonBool(Settings.SDK_WiFi_autoreconnect()); + break; + case LabelType::IP_CONFIG: + { + KeyValueStruct kv(F("IP Config"), useStaticIP() ? F("static") : F("DHCP")); + KV_SETID(F("dhcp")); + return kv; + } #if FEATURE_USE_IPV6 - case LabelType::ENABLE_IPV6: return jsonBool(Settings.EnableIPv6()); -#endif + case LabelType::IP6_LOCAL: + + if (Settings.EnableIPv6()) { + auto ip = ESPEasy::net::NetworkLocalIP6(); + + if (ip != IN6ADDR_ANY) { + return KeyValueStruct(F("IPv6 link local"), formatIP(ip, true)); + } + } + break; + case LabelType::IP6_GLOBAL: + + if (Settings.EnableIPv6()) { + auto ip = ESPEasy::net::NetworkGlobalIP6(); + + if (ip != IN6ADDR_ANY) { + return KeyValueStruct(F("IPv6 global"), formatIP(ip)); + } + } + break; + + // case LabelType::IP6_ALL_ADDRESSES: { + // KeyValueStruct kv(F("IPv6 all addresses")); + // IP6Addresses_t addresses = NetworkAllIPv6(); + // for (auto it = addresses.begin(); it != addresses.end(); ++it) + // { + // kv.appendValue(it->toString()); + // } + // return kv; + // } +#endif // if FEATURE_USE_IPV6 + case LabelType::IP_ADDRESS: + { + KeyValueStruct kv(F("IP Address"), formatIP(ESPEasy::net::NetworkLocalIP())); + KV_SETID(F("ip")); + return kv; + } + case LabelType::IP_SUBNET: + { + KeyValueStruct kv(F("IP Subnet"), formatIP(ESPEasy::net::NetworkSubnetMask())); + KV_SETID(F("subnet")); + return kv; + } + case LabelType::IP_ADDRESS_SUBNET: + { + KeyValueStruct kv(F("IP / Subnet")); + kv.appendValue(getValue(LabelType::IP_ADDRESS)); + kv.appendValue(getValue(LabelType::IP_SUBNET)); + return kv; + } + case LabelType::GATEWAY: + { + KeyValueStruct kv(F("Gateway"), formatIP(ESPEasy::net::NetworkGatewayIP())); + KV_SETID(F("gw")); + return kv; + } + case LabelType::CLIENT_IP: + { + return KeyValueStruct(F("Client IP"), formatIP(web_server.client().remoteIP(), true)); + } + #if FEATURE_MDNS + case LabelType::M_DNS: + { + const String url = NetworkGetHostname() + F(".local"); + + if (extendedValue) { + return KeyValueStruct(F("mDNS"), + strformat( + F("%s"), + url.c_str(), + url.c_str())); + } + return KeyValueStruct(F("mDNS"), url); + } + #endif // if FEATURE_MDNS + case LabelType::DNS: + { + if (!extendedValue) { break; } + KeyValueStruct kv(F("DNS")); + kv.appendValue(getValue(LabelType::DNS_1)); + kv.appendValue(getValue(LabelType::DNS_2)); + #ifdef ESP32 + kv.appendValue(formatIP(ESPEasy::net::NetworkDnsIP(2))); + #endif + + return kv; + } + case LabelType::DNS_1: + { + if (extendedValue) { break; } + KeyValueStruct kv(F("DNS 1"), formatIP(ESPEasy::net::NetworkDnsIP(0))); + KV_SETID(F("dns1")); + return kv; + } + case LabelType::DNS_2: + { + if (extendedValue) { break; } + KeyValueStruct kv(F("DNS 2"), formatIP(ESPEasy::net::NetworkDnsIP(1))); + KV_SETID(F("dns2")); + return kv; + } + case LabelType::ALLOWED_IP_RANGE: + { + KeyValueStruct kv(F("Allowed IP Range"), ESPEasy::net::describeAllowedIPrange()); + KV_SETID(F("allowed_range")); + return kv; + } + case LabelType::STA_MAC: + { + return KeyValueStruct(F("STA MAC"), ESPEasy::net::WifiSTAmacAddress().toString()); + } + case LabelType::AP_MAC: + { + return KeyValueStruct(F("AP MAC"), ESPEasy::net::WifiSoftAPmacAddress().toString()); + } + case LabelType::SSID: + { + return KeyValueStruct(F("SSID"), WiFi.SSID()); + } + case LabelType::BSSID: + { + return KeyValueStruct(F("BSSID"), WiFi.BSSIDstr()); + } + case LabelType::CHANNEL: + { + return KeyValueStruct(F("Channel"), WiFi.channel()); + } + case LabelType::ENCRYPTION_TYPE_STA: + { + KeyValueStruct kv(F("Encryption Type"), getWiFi_encryptionType()); + KV_SETID(F("encryption")); + return kv; + } + case LabelType::CONNECTED: + { + return KeyValueStruct(F("Connected"), format_msec_duration(ESPEasy::net::NetworkConnectDuration_ms())); + } + case LabelType::CONNECTED_MSEC: + { + return KeyValueStruct(F("Connected msec"), ESPEasy::net::NetworkConnectDuration_ms()); + } + case LabelType::LAST_DISCONNECT_REASON: + { + return KeyValueStruct(F("Last Disconnect Reason"), getWiFi_disconnectReason()); + } + case LabelType::LAST_DISC_REASON_STR: + { + return KeyValueStruct(F("Last Disconnect Reason str"), getWiFi_disconnectReason_str()); + } + case LabelType::NUMBER_RECONNECTS: + { + return KeyValueStruct(F("Number Reconnects"), ESPEasy::net::NetworkConnectCount()); + } + case LabelType::WIFI_STORED_SSID1: + { + return KeyValueStruct(F("Configured SSID1"), SecuritySettings.WifiSSID); + } + case LabelType::WIFI_STORED_SSID2: + { + return KeyValueStruct(F("Configured SSID2"), SecuritySettings.WifiSSID2); + } - case LabelType::BUILD_DESC: return getSystemBuildString(); + case LabelType::FORCE_WIFI_BG: + { + return KeyValueStruct(F("Force WiFi B/G"), Settings.ForceWiFi_bg_mode()); + } + case LabelType::RESTART_WIFI_LOST_CONN: + { + return KeyValueStruct(F("Restart WiFi Lost Conn"), Settings.WiFiRestart_connection_lost()); + } + case LabelType::FORCE_WIFI_NOSLEEP: + { + return KeyValueStruct(F("Force WiFi No Sleep"), Settings.WifiNoneSleep()); + } + case LabelType::PERIODICAL_GRAT_ARP: + { + return KeyValueStruct(F("Periodical send Gratuitous ARP"), Settings.gratuitousARP()); + } + case LabelType::CONNECTION_FAIL_THRESH: + { + return KeyValueStruct(F("Connection Failure Threshold"), Settings.ConnectionFailuresThreshold); + } +#ifndef ESP32 + case LabelType::WAIT_WIFI_CONNECT: + { + return KeyValueStruct(F("Extra Wait WiFi Connect"), Settings.WaitWiFiConnect()); + } +#endif // ifndef ESP32 + case LabelType::CONNECT_HIDDEN_SSID: + { + return KeyValueStruct(F("Include Hidden SSID"), Settings.IncludeHiddenSSID()); + } +#ifdef ESP32 + case LabelType::WIFI_PASSIVE_SCAN: + { + return KeyValueStruct(F("Passive WiFi Scan"), Settings.PassiveWiFiScan()); + } +#endif // ifdef ESP32 + case LabelType::HIDDEN_SSID_SLOW_CONNECT: + { + return KeyValueStruct(F("Hidden SSID Slow Connect"), Settings.HiddenSSID_SlowConnectPerBSSID()); + } + case LabelType::SDK_WIFI_AUTORECONNECT: + { + return KeyValueStruct(F("Enable SDK WiFi Auto Reconnect"), Settings.SDK_WiFi_autoreconnect()); + } +#if FEATURE_USE_IPV6 + case LabelType::ENABLE_IPV6: + { + return KeyValueStruct(F("Enable IPv6"), Settings.EnableIPv6()); + } +#endif // if FEATURE_USE_IPV6 + + + case LabelType::BUILD_DESC: + { + String descr = getSystemBuildString(); + + if (extendedValue) { + descr += ' '; + descr += F(BUILD_NOTES); + } + return KeyValueStruct(F("Build"), descr); + } + case LabelType::BUILD_ORIGIN: + { + return KeyValueStruct(F("Build Origin"), get_build_origin()); + } case LabelType::GIT_BUILD: { - const String res(F(BUILD_GIT)); + String res(F(BUILD_GIT)); - if (!res.isEmpty()) { return res; } - return get_git_head(); + if (res.isEmpty()) { res = get_git_head(); } + return KeyValueStruct(F("Git Build"), res); + } + case LabelType::SYSTEM_LIBRARIES: + { + return KeyValueStruct(F("System Libraries"), getSystemLibraryString()); } - case LabelType::SYSTEM_LIBRARIES: return getSystemLibraryString(); #ifdef ESP32 - case LabelType::ESP_IDF_SDK_VERSION: return strformat( - F("%d.%d.%d"), - ESP_IDF_VERSION_MAJOR, - ESP_IDF_VERSION_MINOR, - ESP_IDF_VERSION_PATCH); -#endif - - case LabelType::PLUGIN_COUNT: retval = getDeviceCount() + 1; break; - case LabelType::PLUGIN_DESCRIPTION: return getPluginDescriptionString(); - case LabelType::BUILD_TIME: return String(get_build_date()) + ' ' + get_build_time(); - case LabelType::BINARY_FILENAME: return get_binary_filename(); - case LabelType::BUILD_PLATFORM: return get_build_platform(); - case LabelType::GIT_HEAD: return get_git_head(); + case LabelType::ESP_IDF_SDK_VERSION: + { + return KeyValueStruct(F("ESP-IDF Version"), strformat( + F("%d.%d.%d"), + ESP_IDF_VERSION_MAJOR, + ESP_IDF_VERSION_MINOR, + ESP_IDF_VERSION_PATCH)); + } +#endif // ifdef ESP32 + case LabelType::PLUGIN_COUNT: + { + return KeyValueStruct(F("Plugin Count"), getDeviceCount() + 1); + } + case LabelType::PLUGIN_DESCRIPTION: + { + return KeyValueStruct(F("Plugin Description"), getPluginDescriptionString()); + } + case LabelType::BUILD_TIME: + { + return KeyValueStruct(F("Build Time"), String(get_build_date()) + ' ' + get_build_time()); + } + case LabelType::BINARY_FILENAME: + { + return KeyValueStruct(F("Binary Filename"), get_binary_filename()); + } + case LabelType::BUILD_PLATFORM: + { + return KeyValueStruct(F("Build Platform"), get_build_platform()); + } + case LabelType::GIT_HEAD: + { + return KeyValueStruct(F("Git HEAD"), get_git_head()); + } #ifdef CONFIGURATION_CODE - case LabelType::CONFIGURATION_CODE_LBL: return getConfigurationCode(); + case LabelType::CONFIGURATION_CODE_LBL: + { + return KeyValueStruct(F("Configuration code"), getConfigurationCode()); + } #endif // ifdef CONFIGURATION_CODE - case LabelType::I2C_BUS_STATE: return toString(I2C_state); - case LabelType::I2C_BUS_CLEARED_COUNT: retval = I2C_bus_cleared_count; break; - case LabelType::SYSLOG_LOG_LEVEL: return getLogLevelDisplayString(Settings.SyslogLevel); - case LabelType::SERIAL_LOG_LEVEL: return getLogLevelDisplayString(getSerialLogLevel()); - case LabelType::WEB_LOG_LEVEL: return getLogLevelDisplayString(getWebLogLevel()); +#if FEATURE_CLEAR_I2C_STUCK + case LabelType::I2C_BUS_STATE: + { + return KeyValueStruct(F("I2C Bus State"), toString(I2C_state)); + } + case LabelType::I2C_BUS_CLEARED_COUNT: + { + return KeyValueStruct(F("I2C bus cleared count"), I2C_bus_cleared_count); + } +#endif // if FEATURE_CLEAR_I2C_STUCK +#if FEATURE_SYSLOG + case LabelType::SYSLOG_LOG_LEVEL: + { + return KeyValueStruct(F("Syslog Log Level"), getLogLevelDisplayString(Settings.SyslogLevel)); + } +#endif + case LabelType::SERIAL_LOG_LEVEL: + { + return KeyValueStruct(F("Serial Log Level"), getLogLevelDisplayString(getSerialLogLevel())); + } +# ifdef WEBSERVER_LOG + case LabelType::WEB_LOG_LEVEL: + { + return KeyValueStruct(F("Web Log Level"), getLogLevelDisplayString(getWebLogLevel())); + } +#endif #if FEATURE_SD - case LabelType::SD_LOG_LEVEL: return getLogLevelDisplayString(Settings.SDLogLevel); + case LabelType::SD_LOG_LEVEL: + { + return KeyValueStruct(F("SD Log Level"), getLogLevelDisplayString(Settings.SDLogLevel)); + } #endif // if FEATURE_SD - case LabelType::ESP_CHIP_ID: return formatToHex(getChipId(), 6); - case LabelType::ESP_CHIP_FREQ: retval = ESP.getCpuFreqMHz(); break; -#ifdef ESP32 - case LabelType::ESP_CHIP_XTAL_FREQ: retval = getXtalFrequencyMHz(); break; - case LabelType::ESP_CHIP_APB_FREQ: retval = rtc_clk_apb_freq_get() / 1000000; break; - //getApbFrequency() / 1000000; break; + case LabelType::ESP_CHIP_ID: + { + return KeyValueStruct(F("ESP Chip ID"), formatToHex(getChipId(), 6)); + } + case LabelType::ESP_CHIP_FREQ: + { + KeyValueStruct kv(F("ESP Chip Frequency"), ESP.getCpuFreqMHz()); +#if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_MHz); #endif - case LabelType::ESP_CHIP_MODEL: return getChipModel(); - case LabelType::ESP_CHIP_REVISION: return getChipRevision(); - case LabelType::ESP_CHIP_CORES: retval = getChipCores(); break; - case LabelType::BOARD_NAME: return get_board_name(); - case LabelType::FLASH_CHIP_ID: return formatToHex(getFlashChipId(), 6); - case LabelType::FLASH_CHIP_VENDOR: return formatToHex(getFlashChipId() & 0xFF, 2); - case LabelType::FLASH_CHIP_MODEL: + return kv; + } +#ifdef ESP32 + case LabelType::ESP_CHIP_XTAL_FREQ: + { + KeyValueStruct kv(F("ESP Crystal Frequency"), getXtalFrequencyMHz()); +# if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_MHz); +# endif + return kv; + } + case LabelType::ESP_CHIP_APB_FREQ: + { + KeyValueStruct kv(F("ESP APB Frequency"), rtc_clk_apb_freq_get() / 1000000); +# if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_MHz); +# endif + return kv; + } +#endif // ifdef ESP32 + case LabelType::ESP_CHIP_MODEL: + { + return KeyValueStruct(F("ESP Chip Model"), getChipModel()); + } + case LabelType::ESP_CHIP_REVISION: + { + return KeyValueStruct(F("ESP Chip Revision"), getChipRevision()); + } + case LabelType::ESP_CHIP_CORES: + { + return KeyValueStruct(F("ESP Chip Cores"), getChipCores()); + } + + case LabelType::BOARD_NAME: + { + return KeyValueStruct(F("ESP Board Name"), get_board_name()); + } + + case LabelType::FLASH_CHIP_ID: + { + auto flashChipId = getFlashChipId(); + + if (flashChipId == 0) { break; } + return KeyValueStruct(F("Flash Chip ID"), formatToHex(flashChipId, 6)); + } + case LabelType::FLASH_CHIP_VENDOR: { const uint32_t flashChipId = getFlashChipId(); - const uint32_t flashDevice = (flashChipId & 0xFF00) | ((flashChipId >> 16) & 0xFF); - return formatToHex(flashDevice, 4); + + if (flashChipId == 0) { break; } + String id = formatToHex(flashChipId & 0xFF, 2); + + if (extendedValue && flashChipVendorPuya()) { + id += concat(F(" (PUYA"), puyaSupport() ? F(", supported") : F(HTML_SYMBOL_WARNING)) + ')'; + } + + return KeyValueStruct(F("Flash Chip Vendor"), id); } - case LabelType::FLASH_CHIP_REAL_SIZE: retval = getFlashRealSizeInBytes(); break; - case LabelType::FLASH_CHIP_SPEED: retval = getFlashChipSpeed() / 1000000; break; - case LabelType::FLASH_IDE_SIZE: break; - case LabelType::FLASH_IDE_SPEED: break; - case LabelType::FLASH_IDE_MODE: return getFlashChipMode(); - case LabelType::FLASH_WRITE_COUNT: break; - case LabelType::SKETCH_SIZE: break; - case LabelType::SKETCH_FREE: break; - case LabelType::FS_SIZE: retval = SpiffsTotalBytes(); break; - case LabelType::FS_FREE: retval = SpiffsFreeSpace(); break; - case LabelType::MAX_OTA_SKETCH_SIZE: break; - case LabelType::OTA_2STEP: break; - case LabelType::OTA_POSSIBLE: break; -#if FEATURE_ETHERNET - case LabelType::ETH_IP_ADDRESS: return formatIP(NetworkLocalIP()); - case LabelType::ETH_IP_SUBNET: return formatIP(NetworkSubnetMask()); - case LabelType::ETH_IP_ADDRESS_SUBNET: return strformat( - F("%s / %s"), - getValue(LabelType::ETH_IP_ADDRESS).c_str(), - getValue(LabelType::ETH_IP_SUBNET).c_str()); - case LabelType::ETH_IP_GATEWAY: return formatIP(NetworkGatewayIP()); - case LabelType::ETH_IP_DNS: return formatIP(NetworkDnsIP(0)); - case LabelType::ETH_MAC: return NetworkMacAddress().toString(); - case LabelType::ETH_DUPLEX: return EthLinkUp() ? (EthFullDuplex() ? F("Full Duplex") : F("Half Duplex")) : F("Link Down"); - case LabelType::ETH_SPEED: return EthLinkUp() ? getEthSpeed() : F("Link Down"); - case LabelType::ETH_STATE: return EthLinkUp() ? F("Link Up") : F("Link Down"); - case LabelType::ETH_SPEED_STATE: return EthLinkUp() ? getEthLinkSpeedState() : F("Link Down"); - case LabelType::ETH_CONNECTED: return ETHConnected() ? F("CONNECTED") : F("DISCONNECTED"); // 0=disconnected, 1=connected - case LabelType::ETH_CHIP: return toString(Settings.ETH_Phy_Type); -#endif // if FEATURE_ETHERNET -# if FEATURE_ETHERNET || defined(USES_ESPEASY_NOW) - case LabelType::ETH_WIFI_MODE: return toString(active_network_medium); + case LabelType::FLASH_CHIP_MODEL: + { + #ifndef LIMIT_BUILD_SIZE + const uint32_t flashChipId = getFlashChipId(); + const uint32_t flashDevice = (flashChipId & 0xFF00) | ((flashChipId >> 16) & 0xFF); + + String model(formatToHex(flashDevice, 4)); + # ifdef ESP32 + + if (extendedValue && getChipFeatures().embeddedFlash) { + model += F(" (Embedded)"); + } + # endif // ifdef ESP32 + return KeyValueStruct(F("Flash Chip Model"), model); + #else // ifndef LIMIT_BUILD_SIZE + return KeyValueStruct(F("Flash Chip Model"), getFlashChipId()); + #endif // ifndef LIMIT_BUILD_SIZE + } + case LabelType::FLASH_CHIP_REAL_SIZE: + { + KeyValueStruct kv(F("Flash Chip Real Size"), getFlashRealSizeInBytes() >> 10); +#if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_kB); #endif - case LabelType::SUNRISE: return node_time.getSunriseTimeString(':'); - case LabelType::SUNSET: return node_time.getSunsetTimeString(':'); - case LabelType::SUNRISE_S: retval = node_time.sunRise.tm_hour * 3600 + node_time.sunRise.tm_min * 60 + node_time.sunRise.tm_sec; break; - case LabelType::SUNSET_S: retval = node_time.sunSet.tm_hour * 3600 + node_time.sunSet.tm_min * 60 + node_time.sunSet.tm_sec; break; - case LabelType::SUNRISE_M: retval = node_time.sunRise.tm_hour * 60 + node_time.sunRise.tm_min; break; - case LabelType::SUNSET_M: retval = node_time.sunSet.tm_hour * 60 + node_time.sunSet.tm_min; break; - case LabelType::ISNTP: return jsonBool(Settings.UseNTP()); - case LabelType::UPTIME_MS: return ull2String(getMicros64() / 1000); - case LabelType::TIMEZONE_OFFSET: retval = Settings.TimeZone; break; - case LabelType::LATITUDE: return toString(Settings.Latitude, 6); - case LabelType::LONGITUDE: return toString(Settings.Longitude, 6); + return kv; + } + case LabelType::FLASH_CHIP_SPEED: + { + KeyValueStruct kv(F("Flash Chip Speed"), getFlashChipSpeed() / 1000000); +#if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_MHz); +#endif + return kv; + } + case LabelType::FLASH_IDE_SIZE: + { + KeyValueStruct kv(F("Flash IDE Size"), ESP.getFlashChipSize() >> 10); +#if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_kB); +#endif + return kv; + } + case LabelType::FLASH_IDE_SPEED: + { + KeyValueStruct kv(F("Flash IDE Speed"), getFlashChipSpeed() / 1000000); +#if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_MHz); +#endif + return kv; + } + case LabelType::FLASH_IDE_MODE: + { + KeyValueStruct kv(F("Flash IDE Mode"), getFlashChipMode()); + KV_SETID(F("mode")); + return kv; + } + case LabelType::FLASH_WRITE_COUNT: + { + if (extendedValue) { + KeyValueStruct kv( + F("Flash Writes"), + strformat( + F("%d daily / %d cold boot"), + RTC.flashDayCounter, + static_cast(RTC.flashCounter))); + return kv; + } + return KeyValueStruct(F("Flash Writes"), RTC.flashCounter); + } + case LabelType::SKETCH_SIZE: + { + String str; + + if (extendedValue) { + uint32_t maxSketchSize; + bool use2step; + OTA_possible(maxSketchSize, use2step); + str += strformat( + F("%d [kB] (%d kB not used)"), + (getSketchSize() >> 10), + (maxSketchSize - getSketchSize()) >> 10); + } else { + str = (getSketchSize() >> 10); + } + + KeyValueStruct kv(F("Sketch Size"), str); + KV_SETID(F("sketch_size")); + + if (!extendedValue) { +#if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_kB); +#endif + } + return kv; + } + case LabelType::SKETCH_FREE: + { + KeyValueStruct kv(F("Sketch Free"), getFreeSketchSpace() >> 10); + KV_SETID(F("sketch_free")); +#if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_kB); +#endif + return kv; + } + case LabelType::FS_SIZE: + { + String size; + + if (extendedValue) { + size = strformat( + F("%d [kB] (%d kB free)"), + SpiffsTotalBytes() / 1024, + SpiffsFreeSpace() / 1024); + } + else { + size = (SpiffsTotalBytes() >> 10); + } + + KeyValueStruct kv( + #ifdef USE_LITTLEFS + F("Little FS Size"), + #else + F("SPIFFS Size"), + #endif // ifdef USE_LITTLEFS + SpiffsTotalBytes() >> 10); + KV_SETID(F("fs_size")); +#if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_kB); +#endif + + if (!extendedValue) { +#if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_kB); +#endif + } + return kv; + } + case LabelType::FS_FREE: + { + KeyValueStruct kv( + #ifdef USE_LITTLEFS + F("Little FS Free"), + #else + F("SPIFFS Free"), + #endif // ifdef USE_LITTLEFS + SpiffsFreeSpace() >> 10); + KV_SETID(F("fs_free")); +#if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_kB); +#endif + return kv; + } + case LabelType::MAX_OTA_SKETCH_SIZE: + { + uint32_t maxSketchSize; + bool use2step; + OTA_possible(maxSketchSize, use2step); + + return KeyValueStruct(F("Max. OTA Sketch Size"), strformat( + F("%d [kB] (%d bytes)"), + maxSketchSize / 1024, + maxSketchSize)); + } +#ifdef ESP8266 + case LabelType::OTA_2STEP: + { + uint32_t maxSketchSize; + bool use2step; + OTA_possible(maxSketchSize, use2step); + + return KeyValueStruct(F("OTA 2-step Needed"), use2step); + } + case LabelType::OTA_POSSIBLE: + { + uint32_t maxSketchSize; + bool use2step; + # if defined(ESP8266) + bool otaEnabled = + # endif // if defined(ESP8266) + OTA_possible(maxSketchSize, use2step); + return KeyValueStruct(F("OTA possible"), otaEnabled); + } +#endif // ifdef ESP8266 + #if FEATURE_INTERNAL_TEMPERATURE + case LabelType::INTERNAL_TEMPERATURE: + { + KeyValueStruct kv(F("Internal Temperature"), getInternalTemperature(), 1); +# if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_degC); +# endif + return kv; + } + #endif // if FEATURE_INTERNAL_TEMPERATURE +#if FEATURE_ETHERNET + case LabelType::ETH_MAC: + { + return KeyValueStruct(F("Eth MAC"), ESPEasy::net::NetworkMacAddress().toString()); + } + case LabelType::ETH_DUPLEX: + { + KeyValueStruct kv(F("Eth Mode"), ESPEasy::net::EthLinkUp() ? + (ESPEasy::net::EthFullDuplex() ? F("Full Duplex") : F("Half Duplex")) : F("Link Down")); + KV_SETID(F("ethduplex")); + return kv; + } + case LabelType::ETH_SPEED: + { + KeyValueStruct kv(F("Eth Speed"), getEthSpeed()); + KV_SETID(F("ethspeed")); +# if FEATURE_TASKVALUE_UNIT_OF_MEASURE + KV_SETUNIT(UOM_Mbps); +# endif + return kv; + } + case LabelType::ETH_STATE: + { + KeyValueStruct kv(F("Eth State"), ESPEasy::net::EthLinkUp() ? F("Link Up") : F("Link Down")); + KV_SETID(F("ethstate")); + return kv; + } + case LabelType::ETH_SPEED_STATE: + { + if (active_network_medium != ESPEasy::net::NetworkMedium_t::Ethernet) { break; } + KeyValueStruct kv(F("Eth Speed State"), getEthLinkSpeedState()); + KV_SETID(F("ethspeedstate")); + return kv; + } + case LabelType::ETH_CONNECTED: + { + KeyValueStruct kv(F("Eth connected"), ESPEasy::net::eth::ETHConnected() ? F("CONNECTED") : F("DISCONNECTED")); + KV_SETID(F("ethconnected")); + return kv; + } + case LabelType::ETH_CHIP: + { + // FIXME TD-er: Might no longer be needed? Otherwise need to query ETH Network interface, not settings. + KeyValueStruct kv(F("Eth chip"), toString(Settings.ETH_Phy_Type)); + KV_SETID(F("ethchip")); + return kv; + } +#endif // if FEATURE_ETHERNET +#if FEATURE_ETHERNET || defined(USES_ESPEASY_NOW) + case LabelType::ETH_WIFI_MODE: + { + KeyValueStruct kv(F("Network Type"), toString(active_network_medium)); + KV_SETID(F("ethwifimode")); + return kv; + } +#endif // if FEATURE_ETHERNET || defined(USES_ESPEASY_NOW) + case LabelType::SUNRISE: + { + return KeyValueStruct(F("Sunrise"), node_time.getSunriseTimeString(':')); + } + case LabelType::SUNSET: + { + return KeyValueStruct(F("Sunset"), node_time.getSunsetTimeString(':')); + } + case LabelType::SUNRISE_S: + { + return KeyValueStruct(F("Sunrise sec."), node_time.sunRise.tm_hour * 3600 + node_time.sunRise.tm_min * 60 + + node_time.sunRise.tm_sec); + } + case LabelType::SUNSET_S: + { + return KeyValueStruct(F("Sunset sec."), node_time.sunSet.tm_hour * 3600 + node_time.sunSet.tm_min * 60 + node_time.sunSet.tm_sec); + } + case LabelType::SUNRISE_M: + { + return KeyValueStruct(F("Sunrise min."), node_time.sunRise.tm_hour * 60 + node_time.sunRise.tm_min); + } + case LabelType::SUNSET_M: + { + return KeyValueStruct(F("Sunset min."), node_time.sunSet.tm_hour * 60 + node_time.sunSet.tm_min); + } + case LabelType::ISNTP: + { + return KeyValueStruct(F("Use NTP"), Settings.UseNTP()); + } + case LabelType::UPTIME_MS: + { + return KeyValueStruct(F("Uptime (ms)"), getMicros64() / 1000); + } + case LabelType::TIMEZONE_OFFSET: + { + return KeyValueStruct(F("Timezone Offset"), Settings.TimeZone); + } + case LabelType::LATITUDE: + { + return KeyValueStruct(F("Latitude"), Settings.Latitude, 6); + } + case LabelType::LONGITUDE: + { + return KeyValueStruct(F("Longitude"), Settings.Longitude, 6); + } case LabelType::MAX_LABEL: break; } - if (retval != INT_MAX) return String(retval); - return F("MissingString"); + return KeyValueStruct(); +} + +String getInternalLabel(const KeyValueStruct& kv, + char replaceSpace) +{ + String res = kv.getID(); + if (replaceSpace != '\0') res.replace(" ", String(replaceSpace)); + + return res; +} + + +String getInternalLabel(LabelType::Enum label, char replaceSpace) { + return getInternalLabel(getKeyValue(label), replaceSpace); +} + +String getLabel(const KeyValueStruct& kv) +{ + if (kv._key.isEmpty()) { + return F("MissingString"); + } + return kv._key.toString(); +} + +String getLabel(LabelType::Enum label) { + return getLabel(getKeyValue(label)); +} + + +String getValue(const KeyValueStruct& kv) +{ + if (kv._values.size() && kv._values[0].isSet()) { return kv._values[0].toString(); } + return EMPTY_STRING; +} + +int64_t getValue_int(const KeyValueStruct& kv) +{ + if (kv._values.size() && kv._values[0].isSet()) { + return kv._values[0].toInt(); + } + return 0; +} + +double getValue_float(const KeyValueStruct& kv) +{ + if (kv._values.size() && kv._values[0].isSet()) { + return kv._values[0].toFloat(); + } + return 0; +} + +String getValue(LabelType::Enum label) { + return getValue(getKeyValue(label)); } #if FEATURE_ETHERNET + String getEthSpeed() { - return strformat(F("%d [Mbps]"), EthLinkSpeed()); + if (ESPEasy::net::EthLinkUp()) { + return String(ESPEasy::net::EthLinkSpeed()); + } + return getValue(LabelType::ETH_STATE); } String getEthLinkSpeedState() { - if (EthLinkUp()) { - return strformat(F("%s %s %s"), - getValue(LabelType::ETH_STATE).c_str(), - getValue(LabelType::ETH_DUPLEX).c_str(), - getEthSpeed().c_str()); + if (ESPEasy::net::EthLinkUp()) { + return strformat(F("%s %s %s"), + getValue(LabelType::ETH_STATE).c_str(), + getValue(LabelType::ETH_DUPLEX).c_str(), + getEthSpeed().c_str()); } return getValue(LabelType::ETH_STATE); } @@ -686,34 +1332,35 @@ String getFormNote(LabelType::Enum label) // Otherwise lots of calls to String() constructor are included. const __FlashStringHelper *flash_str = F(""); - switch (label) { + switch (label) + { #ifndef MINIMAL_OTA case LabelType::CONNECT_HIDDEN_SSID: flash_str = F("Must be checked to connect to a hidden SSID"); break; -#ifdef ESP32 +# ifdef ESP32 case LabelType::WIFI_PASSIVE_SCAN: flash_str = F("Passive scan listens for WiFi beacons, Active scan probes for AP. Passive scan is typically faster."); break; -#endif // ifdef ESP32 +# endif // ifdef ESP32 case LabelType::HIDDEN_SSID_SLOW_CONNECT: flash_str = F("Required for some AP brands like Mikrotik to connect to hidden SSID"); break; -#if FEATURE_USE_IPV6 +# if FEATURE_USE_IPV6 case LabelType::ENABLE_IPV6: flash_str = F("Toggling IPv6 requires reboot"); break; -#endif // if FEATURE_USE_IPV6 -#ifndef NO_HTTP_UPDATER +# endif // if FEATURE_USE_IPV6 +# ifndef NO_HTTP_UPDATER case LabelType::ALLOW_OTA_UNLIMITED: flash_str = F("When enabled, OTA updating can overwrite the filesystem and settings!
Requires reboot to activate"); break; -#endif // ifndef NO_HTTP_UPDATER -#if FEATURE_RULES_EASY_COLOR_CODE +# endif // ifndef NO_HTTP_UPDATER +# if FEATURE_RULES_EASY_COLOR_CODE case LabelType::DISABLE_RULES_AUTOCOMPLETE: flash_str = F("Also disables Rules syntax highlighting!"); break; -#endif // if FEATURE_RULES_EASY_COLOR_CODE +# endif // if FEATURE_RULES_EASY_COLOR_CODE case LabelType::FORCE_WIFI_NOSLEEP: flash_str = F("Change WiFi sleep settings requires reboot to activate"); @@ -723,23 +1370,46 @@ String getFormNote(LabelType::Enum label) flash_str = F("Node may miss receiving packets with Eco mode enabled"); break; - case LabelType::WIFI_NR_EXTRA_SCANS: - flash_str = F("Number of extra times to scan all channels to have higher chance of finding the desired AP"); + case LabelType::WIFI_AP_CHANNEL: + flash_str = F("WiFi channel to be used when only WiFi AP is active"); break; -#ifndef ESP32 + case LabelType::WIFI_ENABLE_CAPTIVE_PORTAL: + flash_str = F("When set, user will be redirected to /setup or root page when connecting to this AP. /setup can still be called when disabled."); + break; + + case LabelType::WIFI_NR_RECONNECT_ATTEMPTS: + flash_str = F("Number of retry attempts before counting as 'failed'"); + break; + case LabelType::WIFI_MAX_UPTIME_AUTO_START_AP: + flash_str = F("Only start AP automatically when system uptime is less than set minutes (0 = allow always)"); + break; + case LabelType::WIFI_AP_MINIMAL_ON_TIME: + flash_str = F("Minimal time to have the AP actively waiting for a user to connect"); + break; +# ifndef ESP32 case LabelType::WAIT_WIFI_CONNECT: flash_str = F("Wait for 1000 msec right after connecting to WiFi.
May improve success on some APs like Fritz!Box"); break; -#endif +# endif // ifndef ESP32 +#ifdef ESP32 + case LabelType::WIFI_AP_ENABLE_NAPT: + flash_str = F("NAPT will have no effect when 'force /setup' is enabled"); + break; #endif + case LabelType::WIFI_START_AP_ON_NW002_INIT: + flash_str = F("Always start AP mode when this network interface is enabled"); + break; + +#endif // ifndef MINIMAL_OTA #if FEATURE_SET_WIFI_TX_PWR case LabelType::WIFI_TX_MAX_PWR: case LabelType::WIFI_SENS_MARGIN: { float maxTXpwr; - float sensitivity = GetRSSIthreshold(maxTXpwr); + float sensitivity = ESPEasy::net::wifi::GetRSSIthreshold(maxTXpwr); + if (LabelType::WIFI_TX_MAX_PWR == label) { return strformat( F("Current max: %.2f dBm"), maxTXpwr); @@ -757,87 +1427,12 @@ String getFormNote(LabelType::Enum label) return flash_str; } - +#if FEATURE_TASKVALUE_UNIT_OF_MEASURE String getFormUnit(LabelType::Enum label) { - const __FlashStringHelper *flash_str = F(""); + auto kv = getKeyValue(label); - switch (label) { -#if FEATURE_SET_WIFI_TX_PWR - case LabelType::WIFI_TX_MAX_PWR: - case LabelType::WIFI_CUR_TX_PWR: - case LabelType::WIFI_RSSI: - flash_str = F("dBm"); - break; - case LabelType::WIFI_SENS_MARGIN: - flash_str = F("dB"); - break; -#endif - case LabelType::TIME_WANDER: - flash_str = F("ppm"); - break; -#ifdef ESP32 - case LabelType::HEAP_SIZE: - case LabelType::HEAP_MIN_FREE: - #ifdef BOARD_HAS_PSRAM - case LabelType::PSRAM_SIZE: - case LabelType::PSRAM_FREE: - case LabelType::PSRAM_MIN_FREE: - case LabelType::PSRAM_MAX_FREE_BLOCK: - #endif // BOARD_HAS_PSRAM -#endif // ifdef ESP32 - case LabelType::FREE_MEM: - case LabelType::FREE_STACK: -#ifdef USE_SECOND_HEAP - case LabelType::FREE_HEAP_IRAM: -#endif -#if defined(CORE_POST_2_5_0) || defined(ESP32) - #ifndef LIMIT_BUILD_SIZE - case LabelType::HEAP_MAX_FREE_BLOCK: - #endif -#endif // if defined(CORE_POST_2_5_0) || defined(ESP32) + return kv.getUnit(); +} - flash_str = F("byte"); - break; - case LabelType::FLASH_CHIP_REAL_SIZE: - case LabelType::FLASH_IDE_SIZE: - flash_str = F("kB"); - break; -/* - case LabelType::UPTIME: - flash_str = F("min"); - break; -*/ - case LabelType::LOAD_PCT: -#if defined(CORE_POST_2_5_0) - #ifndef LIMIT_BUILD_SIZE - case LabelType::HEAP_FRAGMENTATION: - #endif -#endif // if defined(CORE_POST_2_5_0) - - flash_str = F("%"); - break; - - case LabelType::ESP_CHIP_FREQ: -#ifdef ESP32 - case LabelType::ESP_CHIP_XTAL_FREQ: - case LabelType::ESP_CHIP_APB_FREQ: -#endif - case LabelType::FLASH_CHIP_SPEED: - case LabelType::FLASH_IDE_SPEED: - flash_str = F("MHz"); - break; -#if FEATURE_INTERNAL_TEMPERATURE - case LabelType::INTERNAL_TEMPERATURE: - flash_str = F("°C"); - break; -#endif // if FEATURE_INTERNAL_TEMPERATURE - - - - default: - return EMPTY_STRING; - } - - return flash_str; -} \ No newline at end of file +#endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE diff --git a/src/src/Helpers/StringProvider.h b/src/src/Helpers/StringProvider.h index f15d3bc81..6f89c5199 100644 --- a/src/src/Helpers/StringProvider.h +++ b/src/src/Helpers/StringProvider.h @@ -3,6 +3,10 @@ #include "../../ESPEasy_common.h" +#include "../DataStructs/KeyValueStruct.h" + +struct KeyValueStruct; + struct LabelType { enum Enum : uint8_t { UNIT_NR, @@ -28,7 +32,17 @@ struct LabelType { WIFI_SENS_MARGIN, // Margin in dB on top of sensitivity WIFI_SEND_AT_MAX_TX_PWR, #endif - WIFI_NR_EXTRA_SCANS, + WIFI_AP_CHANNEL, + WIFI_ENABLE_CAPTIVE_PORTAL, + WIFI_START_AP_NO_CREDENTIALS, + WIFI_START_AP_ON_CONNECT_FAIL, + WIFI_START_AP_ON_NW002_INIT, + WIFI_NR_RECONNECT_ATTEMPTS, + WIFI_MAX_UPTIME_AUTO_START_AP, + WIFI_AP_MINIMAL_ON_TIME, +#ifdef ESP32 + WIFI_AP_ENABLE_NAPT, +#endif WIFI_USE_LAST_CONN_FROM_RTC, FREE_MEM, // 9876 @@ -93,6 +107,12 @@ struct LabelType { #if FEATURE_TASKVALUE_UNIT_OF_MEASURE SHOW_UOM_ON_DEVICES_PAGE, #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE +#if CONFIG_SOC_WIFI_SUPPORT_5G + WIFI_BAND_MODE, +#endif + #if FEATURE_MQTT_CONNECT_BACKGROUND + MQTT_CONNECT_IN_BACKGROUND, + #endif // if FEATURE_MQTT_CONNECT_BACKGROUND BOOT_TYPE, // Cold boot BOOT_COUNT, // 0 @@ -104,8 +124,6 @@ struct LabelType { WIFI_CONNECTION, // 802.11G WIFI_RSSI, // -67 IP_CONFIG, // DHCP - IP_CONFIG_STATIC, - IP_CONFIG_DYNAMIC, IP_ADDRESS, // 192.168.1.123 IP_SUBNET, // 255.255.255.0 IP_ADDRESS_SUBNET, // 192.168.1.123 / 255.255.255.0 @@ -158,6 +176,7 @@ struct LabelType { #endif BUILD_DESC, + BUILD_ORIGIN, GIT_BUILD, SYSTEM_LIBRARIES, #ifdef ESP32 @@ -173,13 +192,17 @@ struct LabelType { CONFIGURATION_CODE_LBL, #endif // ifdef CONFIGURATION_CODE - +#if FEATURE_CLEAR_I2C_STUCK I2C_BUS_STATE, I2C_BUS_CLEARED_COUNT, - +#endif +#if FEATURE_SYSLOG SYSLOG_LOG_LEVEL, +#endif SERIAL_LOG_LEVEL, +# ifdef WEBSERVER_LOG WEB_LOG_LEVEL, +#endif #if FEATURE_SD SD_LOG_LEVEL, #endif // if FEATURE_SD @@ -209,20 +232,14 @@ struct LabelType { FS_SIZE, FS_FREE, MAX_OTA_SKETCH_SIZE, +#ifdef ESP8266 OTA_2STEP, OTA_POSSIBLE, +#endif #if FEATURE_INTERNAL_TEMPERATURE INTERNAL_TEMPERATURE, #endif // if FEATURE_INTERNAL_TEMPERATURE #if FEATURE_ETHERNET - ETH_IP_ADDRESS, - ETH_IP_SUBNET, - ETH_IP_ADDRESS_SUBNET, - ETH_IP_GATEWAY, - ETH_IP_DNS, -#if FEATURE_USE_IPV6 - ETH_IP6_LOCAL, -#endif ETH_MAC, ETH_DUPLEX, ETH_SPEED, @@ -258,14 +275,25 @@ String getEthSpeed(); String getEthLinkSpeedState(); #endif // if FEATURE_ETHERNET +String getInternalLabel(const KeyValueStruct& kv, + char replaceSpace = '_'); String getInternalLabel(LabelType::Enum label, char replaceSpace = '_'); -const __FlashStringHelper * getLabel(LabelType::Enum label); + +String getLabel(const KeyValueStruct& kv); +String getLabel(LabelType::Enum label); + +String getValue(const KeyValueStruct& kv); +int64_t getValue_int(const KeyValueStruct& kv); +double getValue_float(const KeyValueStruct& kv); String getValue(LabelType::Enum label); String getExtendedValue(LabelType::Enum label); String getFormNote(LabelType::Enum label); +#if FEATURE_TASKVALUE_UNIT_OF_MEASURE String getFormUnit(LabelType::Enum label); +#endif +KeyValueStruct getKeyValue(LabelType::Enum label, bool extendedValue = false); #endif // STRING_PROVIDER_TYPES_H diff --git a/src/src/Helpers/SyslogWriter.cpp b/src/src/Helpers/SyslogWriter.cpp new file mode 100644 index 000000000..def302801 --- /dev/null +++ b/src/src/Helpers/SyslogWriter.cpp @@ -0,0 +1,139 @@ +#include "../Helpers/SyslogWriter.h" + +#if FEATURE_SYSLOG + + +#include "../../ESPEasy/net/ESPEasyNetwork.h" +#include "../../ESPEasy/net/Globals/NetworkState.h" +#include "../Globals/ESPEasy_time.h" +#include "../Globals/Settings.h" +#include "../Helpers/ESPEasy_time_calc.h" +#include "../Helpers/Networking.h" +#include "../Helpers/StringConverter.h" + +#define MAX_LENGTH_SYSLOG_MESSAGE 1000 + +bool SyslogWriter::process() +{ + if ((Settings.SyslogLevel == 0) || (getNrMessages() == 0)) { + return false; + } + + bool somethingWritten{}; + + if ((Settings.Syslog_IP[0] != 0) && ESPEasy::net::NetworkConnected()) + { + WiFiUDP syslogUDP; + + if (!beginWiFiUDP_randomPort(syslogUDP)) { + return false; + } + + const IPAddress syslogIP(Settings.Syslog_IP[0], Settings.Syslog_IP[1], Settings.Syslog_IP[2], Settings.Syslog_IP[3]); + const uint32_t start = millis(); + bool done = false; + + // Only try to process syslog messages for a limited amount of time to keep ESPEasy responsive + while (!done && timePassedSince(start) < 200) { + FeedSW_watchdog(); + + if (syslogUDP.beginPacket(syslogIP, Settings.SyslogPort) == 0) { + // Could not create socket + return somethingWritten; + } + + if (write_single_item(syslogUDP, MAX_LENGTH_SYSLOG_MESSAGE) != 0) { + somethingWritten = true; + } else { done = true; } + + syslogUDP.endPacket(); + FeedSW_watchdog(); + delay(0); + } + } + return somethingWritten; +} + +void SyslogWriter::prepare_prefix() +{ + unsigned int prio = Settings.SyslogFacility * 8; + + if (_loglevel == LOG_LEVEL_ERROR) { + prio += 3; // syslog error + } + else if (_loglevel == LOG_LEVEL_INFO) { + prio += 5; // syslog notice + } + else { + prio += 7; + } + + // An RFC3164 compliant message must be formated like: + // "[TimeStamp ]Hostname TaskName: Message" + + // See: https://www.rfc-editor.org/rfc/rfc3164 + + /* + The TIMESTAMP field is the local time and is in the format of + "Mmm dd hh:mm:ss" (without the quote marks) where: + + Mmm is the English language abbreviation for the month of the + year with the first character in uppercase and the other two + characters in lowercase. The following are the only acceptable + values: + + Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec + + dd is the day of the month. If the day of the month is less + than 10, then it MUST be represented as a space and then the + number. For example, the 7th day of August would be + represented as "Aug 7", with two spaces between the "g" and + the "7". + + + + Lonvick Informational [Page 10] + + RFC 3164 The BSD syslog Protocol August 2001 + + + hh:mm:ss is the local time. The hour (hh) is represented in a + 24-hour format. Valid entries are between 00 and 23, + inclusive. The minute (mm) and second (ss) entries are between + 00 and 59 inclusive. + + A single space character MUST follow the TIMESTAMP field. + */ + + // Using Settings.Name as the Hostname (Hostname must NOT contain space) + + String formattedTimestamp; + + if (statusNTPInitialized) { + // Calculate the local time retroactively from the log timestamps + struct tm ts; + uint32_t unix_time_frac{}; + const uint32_t local_timestamp = node_time.systemMicros_to_Localtime( + node_time.internalTimestamp_to_systemMicros(_timestamp), + unix_time_frac); + breakTime(local_timestamp, ts); + + // A single space character MUST follow the TIMESTAMP field. + formattedTimestamp = strformat( + F("%s %2d %02d:%02d:%02d "), + ESPEasy_time::month_str(ts.tm_mon).c_str(), + ts.tm_mday, + ts.tm_hour, ts.tm_min, ts.tm_sec); + } + + String hostname(ESPEasy::net::NetworkCreateRFCCompliantHostname(true)); + hostname.replace(' ', '_'); + hostname.trim(); + + _prefix = strformat( + F("<%d>%s%s EspEasy: "), + prio, + formattedTimestamp.c_str(), + hostname.c_str()); +} +#endif \ No newline at end of file diff --git a/src/src/Helpers/SyslogWriter.h b/src/src/Helpers/SyslogWriter.h new file mode 100644 index 000000000..27514f197 --- /dev/null +++ b/src/src/Helpers/SyslogWriter.h @@ -0,0 +1,27 @@ +#pragma once + +#include "../../ESPEasy_common.h" + +#if FEATURE_SYSLOG + +#include "../Helpers/LogStreamWriter.h" + + +/*********************************************************************************************\ + Syslog client +\*********************************************************************************************/ + +class SyslogWriter : public LogStreamWriter { +public: + + SyslogWriter(LogDestination log_destination) : LogStreamWriter(log_destination) {} + + virtual bool process() override; + +private: + + void prepare_prefix() override; + +}; + +#endif \ No newline at end of file diff --git a/src/src/Helpers/SystemVariables.cpp b/src/src/Helpers/SystemVariables.cpp index fbc4242b1..d00be13f4 100644 --- a/src/src/Helpers/SystemVariables.cpp +++ b/src/src/Helpers/SystemVariables.cpp @@ -10,15 +10,16 @@ #include "../DataStructs/TimingStats.h" #include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../../ESPEasy/net/ESPEasyNetwork.h" +#include "../../ESPEasy/net/wifi/ESPEasyWifi.h" #include "../Globals/CRCValues.h" #include "../Globals/ESPEasy_time.h" -#include "../Globals/ESPEasyWiFiEvent.h" +#include "../../ESPEasy/net/Globals/ESPEasyWiFiEvent.h" #if FEATURE_MQTT # include "../Globals/MQTT.h" #endif // if FEATURE_MQTT -#include "../Globals/NetworkState.h" +#include "../../ESPEasy/net/Globals/NetworkState.h" #include "../Globals/RulesCalculate.h" #include "../Globals/RuntimeData.h" #include "../Globals/Settings.h" @@ -31,12 +32,23 @@ #include "../Helpers/StringConverter.h" #include "../Helpers/StringProvider.h" +#ifndef LIMIT_BUILD_SIZE +#include +#include "../Helpers/StringConverter_Numerical.h" +#endif // ifndef LIMIT_BUILD_SIZE + +#ifdef USES_NW005 +# include +#endif + #if defined(ESP8266) # include + #define WIFI_CONNECTED WiFi.isConnected() #endif // if defined(ESP8266) #if defined(ESP32) # include + #define WIFI_CONNECTED WiFi.STA.connected() #endif // if defined(ESP32) @@ -112,6 +124,10 @@ LabelType::Enum SystemVariables2LabelType(SystemVariables::Enum enumval) { case SystemVariables::ETHSPEEDSTATE: label = LabelType::ETH_SPEED_STATE; break; #endif // if FEATURE_ETHERNET case SystemVariables::LCLTIME: label = LabelType::LOCAL_TIME; break; + #if FEATURE_LAT_LONG_VAR_CMD + case SystemVariables::LATITUDE: label = LabelType::LATITUDE; break; + case SystemVariables::LONGITUDE: label = LabelType::LONGITUDE; break; + #endif // if FEATURE_LAT_LONG_VAR_CMD case SystemVariables::MAC: label = LabelType::STA_MAC; break; case SystemVariables::RSSI: label = LabelType::WIFI_RSSI; break; case SystemVariables::SUNRISE_S: label = LabelType::SUNRISE_S; break; @@ -160,9 +176,9 @@ String SystemVariables::getSystemVariable(SystemVariables::Enum enumval) { switch (enumval) { case BOOT_CAUSE: intvalue = lastBootCause; break; // Integer value to be used in rules - case BSSID: return (WiFiEventData.WiFiDisconnected()) ? MAC_address().toString() : WiFi.BSSIDstr(); + case BSSID: return (!WIFI_CONNECTED) ? MAC_address().toString() : WiFi.BSSIDstr(); case CR: return String('\r'); - case IP4: intvalue = static_cast(NetworkLocalIP()[3]); break; // 4th IP octet + case IP4: intvalue = static_cast(ESPEasy::net::NetworkLocalIP()[3]); break; // 4th IP octet case ISVAR_DOUBLE: intvalue = #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE 1; @@ -190,13 +206,39 @@ String SystemVariables::getSystemVariable(SystemVariables::Enum enumval) { 0; break; case ISNTP: intvalue = statusNTPInitialized ? 1 : 0; break; - case ISWIFI: intvalue = WiFiEventData.wifiStatus; break; // 0=disconnected, 1=connected, 2=got ip, 4=services + case ISWIFIAP: intvalue = ESPEasy::net::wifi::WifiIsAP(WiFi.getMode()) ? 1 : 0; break; + case ISWIFI: intvalue = WIFI_CONNECTED ? 1 : 0; break; +#ifdef USES_NW005 + case ISPPP: intvalue = PPP.connected() ? 1 : 0; break; +#endif + + // WiFiEventData.wifiStatus; break; // 0=disconnected, 1=connected, 2=got ip, 4=services // initialized case LCLTIME_AM: return node_time.getDateTimeString_ampm('-', ':', ' '); case LF: return String('\n'); case MAC_INT: intvalue = getChipId(); break; // Last 24 bit of MAC address as integer, to be used in rules. + #ifndef LIMIT_BUILD_SIZE + case S_PI: { + constexpr ESPEASY_RULES_FLOAT_TYPE _pi = M_PI; + #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + return doubleToString(_pi, maxNrDecimals_fpType(_pi)); + #else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + return toString(_pi, maxNrDecimals_fpType(_pi)); + #endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + } + case S_E: { + constexpr ESPEASY_RULES_FLOAT_TYPE _e = M_E; + #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + return doubleToString(_e, maxNrDecimals_fpType(_e)); + #else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + return toString(_e, maxNrDecimals_fpType(_e)); + #endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + } + #endif // ifndef LIMIT_BUILD_SIZE case SPACE: return String(' '); - case SSID: return (WiFiEventData.WiFiDisconnected()) ? String(F("--")) : WiFi.SSID(); + case SSID: return (!WIFI_CONNECTED) ? String(F("--")) : WiFi.SSID(); + + case SYSBUILD_DATE: return get_build_date(); case SYSBUILD_TIME: return get_build_time(); case SYSDAY: intvalue = node_time.day(); break; @@ -248,7 +290,7 @@ String SystemVariables::getSystemVariable(SystemVariables::Enum enumval) { #else // if FEATURE_ADC_VCC case VCC: intvalue = -1; break; #endif // if FEATURE_ADC_VCC - case WI_CH: intvalue = (WiFiEventData.WiFiDisconnected()) ? 0 : WiFi.channel(); break; + case WI_CH: intvalue = !WIFI_CONNECTED ? 0 : WiFi.channel(); break; default: // Already handled above. @@ -536,6 +578,10 @@ SystemVariables::Enum SystemVariables::startIndex_beginWith(const char* begincha case 'd': return Enum::DNS; #if FEATURE_ETHERNET case 'e': return Enum::ETHCONNECTED; +#else // if FEATURE_ETHERNET +#ifndef LIMIT_BUILD_SIZE + case 'e': return Enum::S_E; +#endif // ifndef LIMIT_BUILD_SIZE #endif // if FEATURE_ETHERNET case 'f': return Enum::FLASH_CHIP_MODEL; case 'g': return Enum::GATEWAY; @@ -547,6 +593,9 @@ SystemVariables::Enum SystemVariables::startIndex_beginWith(const char* begincha case 'l': return Enum::LCLTIME; case 'm': return Enum::SUNRISE_M; case 'n': return Enum::S_LF; +#ifndef LIMIT_BUILD_SIZE + case 'p': return Enum::S_PI; +#endif // ifndef LIMIT_BUILD_SIZE case 'r': return Enum::S_CR; case 's': return Enum::SPACE; case 'u': return Enum::UNIT_sysvar; @@ -574,6 +623,9 @@ const __FlashStringHelper * SystemVariables::toFlashString(SystemVariables::Enum case Enum::DNS: return F("dns"); case Enum::DNS_1: return F("dns1"); case Enum::DNS_2: return F("dns2"); + #ifndef LIMIT_BUILD_SIZE + case Enum::S_E: return F("e"); + #endif // ifndef LIMIT_BUILD_SIZE #if FEATURE_ETHERNET case Enum::ETHCONNECTED: return F("ethconnected"); case Enum::ETHDUPLEX: return F("ethduplex"); @@ -604,15 +656,26 @@ const __FlashStringHelper * SystemVariables::toFlashString(SystemVariables::Enum case Enum::ISMQTT: return F("ismqtt"); case Enum::ISMQTTIMP: return F("ismqttimp"); case Enum::ISNTP: return F("isntp"); + case Enum::ISWIFIAP: return F("iswifiap"); +#ifdef USES_NW005 + case Enum::ISPPP: return F("isppp"); +#endif case Enum::ISWIFI: return F("iswifi"); case Enum::LCLTIME: return F("lcltime"); case Enum::LCLTIME_AM: return F("lcltime_am"); case Enum::LF: return F("LF"); + #if FEATURE_LAT_LONG_VAR_CMD + case Enum::LATITUDE: return F("latitude"); + case Enum::LONGITUDE: return F("longitude"); + #endif // if FEATURE_LAT_LONG_VAR_CMD case Enum::SUNRISE_M: return F("m_sunrise"); case Enum::SUNSET_M: return F("m_sunset"); case Enum::MAC: return F("mac"); case Enum::MAC_INT: return F("mac_int"); case Enum::S_LF: return F("N"); + #ifndef LIMIT_BUILD_SIZE + case Enum::S_PI: return F("pi"); + #endif // ifndef LIMIT_BUILD_SIZE case Enum::S_CR: return F("R"); case Enum::RSSI: return F("rssi"); case Enum::SPACE: return F("SP"); diff --git a/src/src/Helpers/SystemVariables.h b/src/src/Helpers/SystemVariables.h index 0b1c0df50..031de2e32 100644 --- a/src/src/Helpers/SystemVariables.h +++ b/src/src/Helpers/SystemVariables.h @@ -29,6 +29,9 @@ public: ETHSTATE, ETHWIFIMODE, #endif // if FEATURE_ETHERNET + #ifndef LIMIT_BUILD_SIZE + S_E, + #endif // ifndef LIMIT_BUILD_SIZE FLASH_CHIP_MODEL, FLASH_CHIP_VENDOR, @@ -51,14 +54,25 @@ public: ISMQTT, ISMQTTIMP, ISNTP, +#ifdef USES_NW005 + ISPPP, +#endif + ISWIFIAP, ISWIFI, LCLTIME, LCLTIME_AM, LF, + #if FEATURE_LAT_LONG_VAR_CMD + LATITUDE, + LONGITUDE, + #endif // if FEATURE_LAT_LONG_VAR_CMD SUNRISE_M, SUNSET_M, MAC, MAC_INT, + #ifndef LIMIT_BUILD_SIZE + S_PI, + #endif // ifndef LIMIT_BUILD_SIZE S_LF, S_CR, RSSI, diff --git a/src/src/Helpers/TarStream.cpp b/src/src/Helpers/TarStream.cpp index 0e1b16084..82cd26537 100644 --- a/src/src/Helpers/TarStream.cpp +++ b/src/src/Helpers/TarStream.cpp @@ -44,7 +44,7 @@ TarStream::~TarStream() { size_t TarStream::write(uint8_t ch) { // TODO implement - addLogMove(LOG_LEVEL_ERROR, F("TarStream: write(ch) NOT IMPLEMENTED YET.")); + addLog(LOG_LEVEL_ERROR, F("TarStream: write(ch) NOT IMPLEMENTED YET.")); return 1u; } @@ -305,7 +305,7 @@ size_t TarStream::write(const uint8_t *buf, break; } - if (bufOffset < size) { // We got leftover bytes + if (bufOffset < size && _streamState != TarStreamState_e::Error) { // We got leftover bytes stayInLoop = true; } } diff --git a/src/src/Helpers/WebServer_commandHelper.cpp b/src/src/Helpers/WebServer_commandHelper.cpp index de9706246..3a1c0a5e7 100644 --- a/src/src/Helpers/WebServer_commandHelper.cpp +++ b/src/src/Helpers/WebServer_commandHelper.cpp @@ -14,8 +14,9 @@ HandledWebCommand_result handle_command_from_web(EventValueSource::Enum source, if (!clientIPallowed()) { return HandledWebCommand_result::IP_not_allowed; } webrequest.trim(); if (webrequest.isEmpty()) { return HandledWebCommand_result::NoCommand; } - +#ifndef BUILD_NO_DEBUG addLogMove(LOG_LEVEL_INFO, concat(F("HTTP: "), webrequest)); +#endif webrequest = parseTemplate(webrequest); #ifndef BUILD_NO_DEBUG addLogMove(LOG_LEVEL_DEBUG, concat(F("HTTP after parseTemplate: "), webrequest)); diff --git a/src/src/Helpers/_CPlugin_DomoticzHelper.cpp b/src/src/Helpers/_CPlugin_DomoticzHelper.cpp index 3413f6506..cd4a3a76d 100644 --- a/src/src/Helpers/_CPlugin_DomoticzHelper.cpp +++ b/src/src/Helpers/_CPlugin_DomoticzHelper.cpp @@ -6,12 +6,13 @@ # include "../DataTypes/TaskIndex.h" # include "../ESPEasyCore/ESPEasy_Log.h" -# include "../ESPEasyCore/ESPEasyWifi.h" +#include "../../ESPEasy/net/wifi/ESPEasyWifi.h" # include "../Globals/Cache.h" # include "../Helpers/Convert.h" # include "../Helpers/StringConverter.h" +# include "../Helpers/KeyValueWriter_JSON.h" # include "../../ESPEasy-Globals.h" @@ -42,7 +43,7 @@ int humStatDomoticz(struct EventStruct *event, uint8_t rel_index) { } int mapRSSItoDomoticz() { - return GetRSSI_quality(); + return ESPEasy::net::wifi::GetRSSI_quality(); } int mapVccToDomoticz() { @@ -214,42 +215,35 @@ bool deserializeDomoticzJson(const String& json, String serializeDomoticzJson(struct EventStruct *event) { - String json; + PrintToString json; { - json += '{'; - json += to_json_object_value(F("idx"), static_cast(event->idx)); - json += ','; - json += to_json_object_value(F("RSSI"), mapRSSItoDomoticz()); + KeyValueWriter_JSON writer(true, &json); + writer.write({ F("idx"), static_cast(event->idx) }); + writer.write({ F("RSSI"), mapRSSItoDomoticz() }); # if FEATURE_ADC_VCC - json += ','; - json += to_json_object_value(F("Battery"), mapVccToDomoticz()); + writer.write({ F("Battery"), mapVccToDomoticz() }); # endif // if FEATURE_ADC_VCC const Sensor_VType sensorType = event->getSensorType(); if (sensorType == Sensor_VType::SENSOR_TYPE_SWITCH || sensorType == Sensor_VType::SENSOR_TYPE_DIMMER) { - json += ','; - json += to_json_object_value(F("command"), F("switchlight")); - json += ','; - + writer.write({ F("command"), F("switchlight") }); + const bool value_zero = essentiallyZero(UserVar[event->BaseVarIndex]); if (sensorType == Sensor_VType::SENSOR_TYPE_DIMMER && !value_zero) { - json += to_json_object_value(F("Set%20Level"), toString(UserVar[event->BaseVarIndex], 2)); + writer.write({ F("Set%20Level"), UserVar[event->BaseVarIndex], 2 }); } else { - json += to_json_object_value(F("switchcmd"), value_zero ? F("Off") : F("On")); + writer.write({ F("switchcmd"), value_zero ? F("Off") : F("On") }); } } else { - json += ','; - json += to_json_object_value(F("nvalue"), 0); - json += ','; - json += to_json_object_value(F("svalue"), formatDomoticzSensorType(event), true); + writer.write({ F("nvalue"), F("0") }); + writer.write({ F("svalue"), formatDomoticzSensorType(event) }); } - json += '}'; } - return json; + return json.getMove(); } # endif // ifdef USES_C002 diff --git a/src/src/Helpers/_CPlugin_Helper.cpp b/src/src/Helpers/_CPlugin_Helper.cpp index 347ff2e57..24d45152e 100644 --- a/src/src/Helpers/_CPlugin_Helper.cpp +++ b/src/src/Helpers/_CPlugin_Helper.cpp @@ -13,24 +13,23 @@ #include "../ESPEasyCore/ESPEasy_backgroundtasks.h" #include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/ESPEasyEth.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyWifi.h" +#include "../../ESPEasy/net/eth/ESPEasyEth.h" +#include "../../ESPEasy/net/ESPEasyNetwork.h" +#include "../../ESPEasy/net/wifi/ESPEasyWifi.h" #include "../Globals/Settings.h" #include "../Globals/SecuritySettings.h" -#include "../Globals/ESPEasyWiFiEvent.h" +#include "../../ESPEasy/net/Globals/ESPEasyWiFiEvent.h" #include "../Helpers/ESPEasy_time_calc.h" #include "../Helpers/Misc.h" -#include "../Helpers/Network.h" +#include "../Helpers/NetworkStatusLED.h" #include "../Helpers/Networking.h" #include "../Helpers/StringConverter.h" #include #include - bool safeReadStringUntil(Stream & input, String & str, char terminator, @@ -45,7 +44,8 @@ bool safeReadStringUntil(Stream & input, // FIXME TD-er: Should this also de-allocate internal buffer? str.clear(); - do { + do + { // read character if (input.available()) { c = input.read(); @@ -83,6 +83,7 @@ bool safeReadStringUntil(Stream & input, } #ifndef BUILD_NO_DEBUG + void log_connecting_to(const __FlashStringHelper *prefix, cpluginID_t cpluginID, ControllerSettingsStruct& ControllerSettings) { if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { addLogMove(LOG_LEVEL_DEBUG, @@ -95,43 +96,42 @@ void log_connecting_to(const __FlashStringHelper *prefix, cpluginID_t cpluginID, #endif // ifndef BUILD_NO_DEBUG -void log_connecting_fail(const __FlashStringHelper *prefix, cpluginID_t cpluginID) { - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - addLogMove(LOG_LEVEL_ERROR, - strformat(F("%s%s connection failed (%d/%d)"), - prefix, - get_formatted_Controller_number(cpluginID).c_str(), - WiFiEventData.connectionFailures, - Settings.ConnectionFailuresThreshold)); - } -} - bool count_connection_results(bool success, const __FlashStringHelper *prefix, cpluginID_t cpluginID, uint64_t statisticsTimerStart) { #if FEATURE_TIMING_STATS const protocolIndex_t protocolIndex = getProtocolIndex_from_CPluginID(cpluginID); #endif + auto data = ESPEasy::net::getDefaultRoute_NWPluginData_static_runtime(); if (!success) { - ++WiFiEventData.connectionFailures; - log_connecting_fail(prefix, cpluginID); + if (data) { + data->markConnectionFailed(cpluginID); + addLogMove(LOG_LEVEL_ERROR, + strformat(F("%s%s connection failed (%d/%d)"), + FsP(prefix), + get_formatted_Controller_number(cpluginID).c_str(), + data->getConnectionFailures(), + Settings.ConnectionFailuresThreshold)); + } + STOP_TIMER_CONTROLLER(protocolIndex, CPlugin::Function::CPLUGIN_CONNECT_FAIL); return false; } - WiFiEventData.connectDurations[cpluginID] = usecPassedSince(statisticsTimerStart) / 1000ul; + + if (data) { + data->markConnectionSuccess(cpluginID, usecPassedSince(statisticsTimerStart) / 1000ul); + } + STOP_TIMER_CONTROLLER(protocolIndex, CPlugin::Function::CPLUGIN_CONNECT_SUCCESS); statusLED(true); - if (WiFiEventData.connectionFailures > 0) { - --WiFiEventData.connectionFailures; - } return true; } bool try_connect_host(cpluginID_t cpluginID, WiFiUDP& client, ControllerSettingsStruct& ControllerSettings) { const uint64_t statisticsTimerStart(getMicros64()); // START_TIMER; - if (!NetworkConnected()) { + if (!ESPEasy::net::NetworkConnected()) { client.stop(); return false; } @@ -140,9 +140,7 @@ bool try_connect_host(cpluginID_t cpluginID, WiFiUDP& client, ControllerSettings // For example because the server does not give an acknowledgement. // This way, we always need the set amount of timeout to handle the request. // Thus we should not make the timeout dynamic here if set to ignore ack. - const uint32_t timeout = ControllerSettings.MustCheckReply - ? WiFiEventData.getSuggestedTimeout(cpluginID, ControllerSettings.ClientTimeout) - : ControllerSettings.ClientTimeout; + const uint32_t timeout = ControllerSettings.getSuggestedTimeout(cpluginID); client.setTimeout(timeout); // in msec as it should be! delay(0); @@ -165,6 +163,7 @@ bool try_connect_host(cpluginID_t cpluginID, WiFiUDP& client, ControllerSettings } #if FEATURE_HTTP_CLIENT + bool try_connect_host(cpluginID_t cpluginID, WiFiClient& client, ControllerSettingsStruct& ControllerSettings) { return try_connect_host(cpluginID, client, ControllerSettings, F("HTTP : ")); } @@ -175,7 +174,7 @@ bool try_connect_host(cpluginID_t cpluginID, const __FlashStringHelper *loglabel) { const uint64_t statisticsTimerStart(getMicros64()); // START_TIMER; - if (!NetworkConnected()) { + if (!ESPEasy::net::NetworkConnected()) { client.stop(); return false; } @@ -187,9 +186,7 @@ bool try_connect_host(cpluginID_t cpluginID, // For example because the server does not give an acknowledgement. // This way, we always need the set amount of timeout to handle the request. // Thus we should not make the timeout dynamic here if set to ignore ack. - const uint32_t timeout = ControllerSettings.MustCheckReply - ? WiFiEventData.getSuggestedTimeout(cpluginID, ControllerSettings.ClientTimeout) - : ControllerSettings.ClientTimeout; + const uint32_t timeout = ControllerSettings.getSuggestedTimeout(cpluginID); # ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS @@ -239,12 +236,10 @@ String send_via_http(int cpluginID, // For example because the server does not give an acknowledgement. // This way, we always need the set amount of timeout to handle the request. // Thus we should not make the timeout dynamic here if set to ignore ack. - const uint32_t timeout = ControllerSettings.MustCheckReply - ? WiFiEventData.getSuggestedTimeout(cpluginID, ControllerSettings.ClientTimeout) - : ControllerSettings.ClientTimeout; + const uint32_t timeout = ControllerSettings.getSuggestedTimeout(cpluginID); const uint64_t statisticsTimerStart(getMicros64()); - const String result = send_via_http( + const String result = send_via_http( get_formatted_Controller_number(cpluginID), timeout, getControllerUser(controller_idx, ControllerSettings), @@ -256,7 +251,11 @@ String send_via_http(int cpluginID, header, postStr, httpCode, - ControllerSettings.MustCheckReply); + ControllerSettings.MustCheckReply + #if FEATURE_HTTP_TLS + , ControllerSettings.TLStype() + #endif // if FEATURE_HTTP_TLS + ); // FIXME TD-er: Shouldn't this be: success = (httpCode >= 100) && (httpCode < 300) // or is reachability of the host the important factor here? @@ -273,7 +272,6 @@ String send_via_http(int cpluginID, #endif // FEATURE_HTTP_CLIENT - String getControllerUser(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings, bool doParseTemplate) { if (!validControllerIndex(controller_idx)) { return EMPTY_STRING; } @@ -281,6 +279,7 @@ String getControllerUser(controllerIndex_t controller_idx, const ControllerSetti String res; if (ControllerSettings.useExtendedCredentials()) { + // FIXME TD-er: Must add checkbox to save credentials in separate file res = ExtendedControllerCredentials.getControllerUser(controller_idx); } else { res = String(SecuritySettings.ControllerUser[controller_idx]); @@ -298,6 +297,7 @@ String getControllerPass(controllerIndex_t controller_idx, const ControllerSetti if (!validControllerIndex(controller_idx)) { return EMPTY_STRING; } if (ControllerSettings.useExtendedCredentials()) { + // FIXME TD-er: Must add checkbox to save credentials in separate file return ExtendedControllerCredentials.getControllerPass(controller_idx); } String res(SecuritySettings.ControllerPassword[controller_idx]); @@ -311,6 +311,7 @@ void setControllerUser(controllerIndex_t controller_idx, const ControllerSetting if (!validControllerIndex(controller_idx)) { return; } if (ControllerSettings.useExtendedCredentials()) { + // FIXME TD-er: Must add checkbox to save credentials in separate file ExtendedControllerCredentials.setControllerUser(controller_idx, value); } else { safe_strncpy(SecuritySettings.ControllerUser[controller_idx], value, sizeof(SecuritySettings.ControllerUser[0])); @@ -322,6 +323,7 @@ void setControllerPass(controllerIndex_t controller_idx, const ControllerSetting if (!validControllerIndex(controller_idx)) { return; } if (ControllerSettings.useExtendedCredentials()) { + // FIXME TD-er: Must add checkbox to save credentials in separate file ExtendedControllerCredentials.setControllerPass(controller_idx, value); } else { safe_strncpy(SecuritySettings.ControllerPassword[controller_idx], value, sizeof(SecuritySettings.ControllerPassword[0])); diff --git a/src/src/Helpers/_CPlugin_Helper.h b/src/src/Helpers/_CPlugin_Helper.h index 0b08d4f01..07ab6c270 100644 --- a/src/src/Helpers/_CPlugin_Helper.h +++ b/src/src/Helpers/_CPlugin_Helper.h @@ -8,13 +8,13 @@ #include "../ControllerQueue/DelayQueueElements.h" // Also forward declaring the do_process_cNNN_delay_queue #include "../DataStructs/ControllerSettingsStruct.h" #include "../ESPEasyCore/Controller.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../../ESPEasy/net/ESPEasyNetwork.h" #include "../Globals/CPlugins.h" #include "../Globals/ESPEasy_Scheduler.h" #include "../Globals/Services.h" #include "../Helpers/_CPlugin_init.h" #include "../Helpers/Misc.h" -#include "../Helpers/Network.h" +#include "../Helpers/NetworkStatusLED.h" #include "../Helpers/Networking.h" #include "../Helpers/Numerical.h" #include "../Helpers/StringConverter.h" @@ -36,8 +36,6 @@ bool safeReadStringUntil(Stream & input, void log_connecting_to(const __FlashStringHelper * prefix, cpluginID_t cpluginID, ControllerSettingsStruct& ControllerSettings); #endif // ifndef BUILD_NO_DEBUG -void log_connecting_fail(const __FlashStringHelper * prefix, cpluginID_t cpluginID); - bool count_connection_results(bool success, const __FlashStringHelper * prefix, cpluginID_t cpluginID, uint64_t statisticsTimerStart); #if FEATURE_HTTP_CLIENT diff --git a/src/src/Helpers/_CPlugin_Helper_LoRa.cpp b/src/src/Helpers/_CPlugin_Helper_LoRa.cpp new file mode 100644 index 000000000..5a0c7ff8c --- /dev/null +++ b/src/src/Helpers/_CPlugin_Helper_LoRa.cpp @@ -0,0 +1,227 @@ +#include "../Helpers/_CPlugin_Helper_LoRa.h" + +#if defined(USES_C018) || defined(USES_C023) + +# include "../DataTypes/FormSelectorOptions.h" +# include "../Helpers/StringConverter.h" +# include "../WebServer/HTML_wrappers.h" + +/************************************************* +* LoRaWAN Join Method +*************************************************/ +const __FlashStringHelper * LoRa_Helper::toString(LoRaWAN_JoinMethod joinMethod) +{ + switch (joinMethod) + { + case LoRaWAN_JoinMethod::OTAA: return F("OTAA"); + case LoRaWAN_JoinMethod::ABP: return F("ABP"); + } + return F(""); +} + +void LoRa_Helper::addLoRaWAN_JoinMethod_FormSelector( + const __FlashStringHelper *label, + const __FlashStringHelper *id, + LoRaWAN_JoinMethod selectedIndex) +{ + const __FlashStringHelper *options[] = { + toString(LoRaWAN_JoinMethod::OTAA), + toString(LoRaWAN_JoinMethod::ABP) + }; + constexpr int values[] { + static_cast(LoRaWAN_JoinMethod::OTAA), + static_cast(LoRaWAN_JoinMethod::ABP) + }; + FormSelectorOptions selector(NR_ELEMENTS(options), options, values); + + // Script to toggle OTAA/ABP fields visibility when changing selection. + selector.onChangeCall = F("joinChanged(this)"); + + selector.addFormSelector(label, id, static_cast(selectedIndex)); + html_add_script( + strformat( + F("document.getElementById('%s').onchange();"), + FsP(id)), + false); +} + +void LoRa_Helper::add_joinChanged_script_element_line(const String& id, LoRaWAN_JoinMethod joinMethod) +{ + addHtml(F("document.getElementById('tr_")); + addHtml(id); + addHtml(F("').style.display = style")); + addHtml(toString(joinMethod)); + addHtml(';'); +} + +/************************************************* +* LoRaWAN Class +*************************************************/ +void LoRa_Helper::addLoRaWANclass_FormSelector( + const __FlashStringHelper *label, + const __FlashStringHelper *id, + LoRaWANclass_e selectedIndex) +{ + const __FlashStringHelper *options[] = { F("A"), F("C") }; + constexpr int values[] { + 0, + 2 + }; + const FormSelectorOptions selector(NR_ELEMENTS(options), options, values); + + selector.addFormSelector(label, id, static_cast(selectedIndex)); +} + +/************************************************* +* LoRaWAN Downlink Event Format +*************************************************/ +void LoRa_Helper::addEventFormatStructure_FormSelector( + const __FlashStringHelper *label, + const __FlashStringHelper *id, + DownlinkEventFormat_e selectedIndex) +{ + const __FlashStringHelper *options[] = { + F("PortNr in EventPar"), + F("PortNr as 1st EventValue"), + F("PortNr both EventPar & 1st EventValue") + }; + constexpr int values[] { + static_cast(DownlinkEventFormat_e::PortNr_in_eventPar), + static_cast(DownlinkEventFormat_e::PortNr_as_first_eventvalue), + static_cast(DownlinkEventFormat_e::PortNr_both_eventPar_eventvalue) + }; + const FormSelectorOptions selector(NR_ELEMENTS(options), options, values); + + selector.addFormSelector(label, id, static_cast(selectedIndex)); +} + +/************************************************* +* LoRaWAN DataRate +*************************************************/ +const __FlashStringHelper * LoRa_Helper::toString(LoRaWAN_DR dr) +{ + switch (dr) + { + case LoRaWAN_DR::SF12_BW125: return F("SF12 BW125"); + case LoRaWAN_DR::SF11_BW125: return F("SF11 BW125"); + case LoRaWAN_DR::SF10_BW125: return F("SF10 BW125"); + case LoRaWAN_DR::SF9_BW125: return F("SF9 BW125"); + case LoRaWAN_DR::SF8_BW125: return F("SF8 BW125"); + case LoRaWAN_DR::SF7_BW125: return F("SF7 BW125"); + case LoRaWAN_DR::SF7_BW250: return F("SF7 BW250"); + case LoRaWAN_DR::FSK: return F("FSK"); + case LoRaWAN_DR::ADR: return F("Adaptive Data Rate (ADR)"); + } + return F(""); +} + +uint8_t LoRa_Helper::getSF(LoRaWAN_DR dr) +{ + switch (dr) + { + case LoRaWAN_DR::SF12_BW125: return 12; + case LoRaWAN_DR::SF11_BW125: return 11; + case LoRaWAN_DR::SF10_BW125: return 10; + case LoRaWAN_DR::SF9_BW125: return 9; + case LoRaWAN_DR::SF8_BW125: return 8; + case LoRaWAN_DR::SF7_BW125: return 7; + case LoRaWAN_DR::SF7_BW250: return 7; + case LoRaWAN_DR::FSK: return 0; // TODO TD-er: What is the SF equivalent here? + case LoRaWAN_DR::ADR: return 0; + } + return 0; +} + +uint16_t LoRa_Helper::getBW(LoRaWAN_DR dr) +{ + if (dr == LoRaWAN_DR::SF7_BW250) { return 250; } + + if (dr == LoRaWAN_DR::FSK) { return 250; } // TODO TD-er: Is this correct? + return 125; +} + +void LoRa_Helper::addLoRaWAN_DR_FormSelector( + const __FlashStringHelper *label, + const __FlashStringHelper *id, + LoRaWAN_DR selectedIndex) +{ + const __FlashStringHelper *options[] = { + toString(LoRaWAN_DR::ADR), + toString(LoRaWAN_DR::SF7_BW250), + toString(LoRaWAN_DR::SF7_BW125), + toString(LoRaWAN_DR::SF8_BW125), + toString(LoRaWAN_DR::SF9_BW125), + toString(LoRaWAN_DR::SF10_BW125), + toString(LoRaWAN_DR::SF11_BW125), + toString(LoRaWAN_DR::SF12_BW125) + }; + constexpr int values[] { + static_cast(LoRaWAN_DR::ADR), + static_cast(LoRaWAN_DR::SF7_BW250), + static_cast(LoRaWAN_DR::SF7_BW125), + static_cast(LoRaWAN_DR::SF8_BW125), + static_cast(LoRaWAN_DR::SF9_BW125), + static_cast(LoRaWAN_DR::SF10_BW125), + static_cast(LoRaWAN_DR::SF11_BW125), + static_cast(LoRaWAN_DR::SF12_BW125) + }; + const FormSelectorOptions selector(NR_ELEMENTS(options), options, values); + + selector.addFormSelector(label, id, static_cast(selectedIndex)); +} + +/************************************************* +* LoRaWAN Air Time +*************************************************/ +float LoRa_Helper::getLoRaAirTime(uint8_t pl, LoRaWAN_DR dr) +{ + if (dr == LoRa_Helper::LoRaWAN_DR::ADR) { return -1.0f; } + uint8_t sf = getSF(dr); // Spreading factor 7 - 12 + const uint16_t bw = getBW(dr); // Bandwidth 125 kHz default for LoRaWAN. 250 kHz also supported. + uint8_t cr = 1; // Code Rate 4 / (CR + 4) = 4/5. 4/5 default for LoRaWAN + const uint8_t n_preamble = 8; // Preamble length Default for frame = 8, beacon = 10 + const bool header = true; // Explicit header Default on for LoRaWAN + const bool crc = true; // CRC Default on for LoRaWAN + + if (sf > 12) { + sf = 12; + } else if (sf < 7) { + sf = 7; + } + + if (cr > 4) { + cr = 4; + } else if (cr < 1) { + cr = 1; + } + + // Symbols in frame + int payload_length = 8; + { + int beta_offset = 28; + + if (crc) { beta_offset += 16; } + + if (!header) { beta_offset -= 20; } + float beta_f = 8.0f * pl - 4.0f * sf + beta_offset; + bool lowDataRateOptimization = (bw == 125 && sf >= 11); + + if (lowDataRateOptimization) { + beta_f = beta_f / (4.0f * (sf - 2)); + } else { + beta_f = beta_f / (4.0f * sf); + } + int beta = static_cast(beta_f + 1.0f); // ceil + + if (beta > 0) { + payload_length += (beta * (cr + 4)); + } + } + + // t_symbol and t_air in msec + const float t_symbol = static_cast(1 << sf) / bw; + const float t_air = ((n_preamble + 4.25f) + payload_length) * t_symbol; + return t_air; +} + +#endif // if defined(USES_C018) || defined(USES_C023) diff --git a/src/src/Helpers/_CPlugin_Helper_LoRa.h b/src/src/Helpers/_CPlugin_Helper_LoRa.h new file mode 100644 index 000000000..ebc7a84cf --- /dev/null +++ b/src/src/Helpers/_CPlugin_Helper_LoRa.h @@ -0,0 +1,105 @@ +#pragma once + +#include "../../ESPEasy_common.h" + +#if defined(USES_C018) || defined(USES_C023) + +class LoRa_Helper +{ +public: + + /************************************************* + * LoRaWAN Join Method + *************************************************/ + + enum class LoRaWAN_JoinMethod { + OTAA = 0, + ABP = 1, // N.B. C023 (AT-command LoRaWAN) uses 1 = OTAA, 0 = ABP + + // ,P2P + + }; + + static const __FlashStringHelper* toString(LoRaWAN_JoinMethod joinMethod); + + static void addLoRaWAN_JoinMethod_FormSelector( + const __FlashStringHelper *label, + const __FlashStringHelper *id, + LoRaWAN_JoinMethod selectedIndex); + + static void add_joinChanged_script_element_line(const String & id, + LoRaWAN_JoinMethod joinMethod); + + /************************************************* + * LoRaWAN Class + *************************************************/ + + enum class LoRaWANclass_e { + A, // Class A: Bi-directional, allows downlink only after an uplink transmission + B, // Class B: Bi-directional, allows periodic downlink windows + C // Class C: Bi-directional, allows continuous listening for downlinks + + }; + + static void addLoRaWANclass_FormSelector( + const __FlashStringHelper *label, + const __FlashStringHelper *id, + LoRaWANclass_e selectedIndex); + + /************************************************* + * LoRaWAN Downlink Event Format + *************************************************/ + + enum class DownlinkEventFormat_e { + PortNr_in_eventPar, + PortNr_as_first_eventvalue, + PortNr_both_eventPar_eventvalue + + }; + + static void addEventFormatStructure_FormSelector( + const __FlashStringHelper *label, + const __FlashStringHelper *id, + DownlinkEventFormat_e selectedIndex); + + /************************************************* + * LoRaWAN DataRate + *************************************************/ + + enum class LoRaWAN_DR { + SF12_BW125 = 0, // (KR920, AS923, EU868) + SF11_BW125 = 1, // (KR920, AS923, EU868) + SF10_BW125 = 2, // (KR920, AS923, EU868) + SF9_BW125 = 3, // (KR920, AS923, EU868) + SF8_BW125 = 4, // (KR920, AS923, EU868) + SF7_BW125 = 5, // (KR920, AS923, EU868) + SF7_BW250 = 6, // (AS923, EU868) + FSK = 7, // (AS923, EU868) + ADR = 127 // Just used as internal setting, either it is set to ADR or to a fixed value + + }; + + + static const __FlashStringHelper* toString(LoRaWAN_DR dr); + + static uint8_t getSF(LoRaWAN_DR dr); + + // Get bandwidth in kHz from given data rate + static uint16_t getBW(LoRaWAN_DR dr); + + static void addLoRaWAN_DR_FormSelector( + const __FlashStringHelper *label, + const __FlashStringHelper *id, + LoRaWAN_DR selectedIndex); + + /************************************************* + * LoRaWAN Air Time + *************************************************/ + + // Compute the air time of a LoRa packet based on packet length and data rate + static float getLoRaAirTime(uint8_t pl, + LoRaWAN_DR dr); + +}; // class LoRa_Helper + +#endif // if defined(USES_C018) || defined(USES_C023) diff --git a/src/src/Helpers/_CPlugin_Helper_mqtt.cpp b/src/src/Helpers/_CPlugin_Helper_mqtt.cpp index a4f327bc1..e1819eb40 100644 --- a/src/src/Helpers/_CPlugin_Helper_mqtt.cpp +++ b/src/src/Helpers/_CPlugin_Helper_mqtt.cpp @@ -9,6 +9,7 @@ # include "../Helpers/StringGenerator_System.h" # endif // if FEATURE_MQTT_DISCOVER # include "../Helpers/SystemVariables.h" +# include "../Helpers/ESPEasy_UnitOfMeasure.h" # ifdef USES_P001 # include "../PluginStructs/P001_data_struct.h" @@ -406,21 +407,23 @@ int Plugin_QueryVType_Weight(uint8_t value_nr) { } String makeHomeAssistantCompliantName(const String& name) { - return makeRFCCompliantName(name, '_', '_', 0); + return ESPEasy::net::makeRFCCompliantName(name, '_', '_', 0); } # if FEATURE_MQTT_DEVICECLASS const char mqtt_binary_deviceclass_names[] PROGMEM = "|" // Default/0 is empty value "power|light|plug|door|garage_door|cold|heat|lock|tamper|window|" // Guessed some often used options to be listed first - "battery|battery_charging|carbon_monoxide|connectivity|gas|" // power is selected as the default + "battery|battery_charging|carbon_monoxide|connectivity|gas|" // switch is selected as the default "moisture|motion|moving|occupancy|opening|presence|problem|" // *** DO NOT CHANGE VALUE ORDER!!! - "running|safety|smoke|sound|update|vibration|"; // *** Index is stored in task settings!!! + "running|safety|smoke|sound|update|vibration|" // *** Index is stored in task settings!!! + "switch|outlet|" // switch devices +; String MQTT_binary_deviceClassName(int devClassIndex) { - char tmp[17]{}; // length: battery_charging + \0 + char tmp[17]{}; // length: battery_charging + \0 - String result(GetTextIndexed(tmp, sizeof(tmp), devClassIndex, mqtt_binary_deviceclass_names)); + const String result(GetTextIndexed(tmp, sizeof(tmp), devClassIndex, mqtt_binary_deviceclass_names)); return result; } @@ -429,7 +432,7 @@ int MQTT_binary_deviceClassIndex(const String& deviceClassName) { return GetCommandCode(deviceClassName.c_str(), mqtt_binary_deviceclass_names); } -// TwoWay devices are marked with ² in the selector, and disvocered as 'light' instead of 'binary_sensor' +// TwoWay devices are marked with ² in the selector, and discovered as 'light' instead of 'binary_sensor' bool MQTT_binary_deviceClassTwoWay(int devClassIndex) { switch (devClassIndex) { // Index into mqtt_binary_deviceclass_names case 1: // power @@ -439,6 +442,20 @@ bool MQTT_binary_deviceClassTwoWay(int devClassIndex) { case 8: // lock case 26: // sound case 28: // vibration + case 29: // switch + case 30: // outlet + return true; + default: + break; + } + return false; +} + +// Switch devices are marked with ÷ in the selector, and discovered as 'switch' instead of 'light' +bool MQTT_binary_deviceClassSwitch(int devClassIndex) { + switch (devClassIndex) { // Index into mqtt_binary_deviceclass_names + case 29: // switch + case 30: // outlet return true; default: break; @@ -454,9 +471,9 @@ const __FlashStringHelper* MQTT_sensor_StateClass(uint8_t index, switch (index) { case 0: return F(""); case 1: return display ? F("Measurement") : F("measurement"); - case 2: return display ? F("Total") : F("total"); - case 3: return display ? F("Total-increasing") : F("total_increasing"); - case 4: return display ? F("Measurement-angle") : F("measurement_angle"); + case 2: return display ? F("Measurement-angle") : F("measurement_angle"); + case 3: return display ? F("Total") : F("total"); + case 4: return display ? F("Total-increasing") : F("total_increasing"); } return F(""); } @@ -630,22 +647,27 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn const String valuename = MQTT_DiscoveryHelperGetValueName(x, v, discoveryItems[s]); String valueDeviceClass = parseStringKeepCase(pluginDeviceClass, v + 1); // Device classes per value - if (valueDeviceClass.isEmpty()) { valueDeviceClass = F("power"); } // default + if (valueDeviceClass.isEmpty()) { valueDeviceClass = F("switch"); } // default # if FEATURE_MQTT_STATE_CLASS const String stateClass = MQTT_sensor_StateClass(Cache.getTaskVarStateClass(x, v), false); # else // if FEATURE_MQTT_STATE_CLASS const String stateClass = EMPTY_STRING; # endif // if FEATURE_MQTT_STATE_CLASS # if FEATURE_MQTT_DEVICECLASS - const bool twoWay = MQTT_binary_deviceClassTwoWay(MQTT_binary_deviceClassIndex(valueDeviceClass)); + const int devClass = MQTT_binary_deviceClassIndex(valueDeviceClass); + const bool twoWay = MQTT_binary_deviceClassTwoWay(devClass); + const bool isSwitch = MQTT_binary_deviceClassSwitch(devClass); # else // if FEATURE_MQTT_DEVICECLASS - const bool twoWay = true; + const bool twoWay = true; + const bool isSwitch = true; # endif // if FEATURE_MQTT_DEVICECLASS // Discover 2-way as Light - const __FlashStringHelper*componentClass = twoWay && discoveryItems[s].canSet ? F("light") : F("binary_sensor"); - const String deviceClass = strformat(F("%s\",\"pl_on\":\"%d\",\"pl_off\":\"%d"), - valueDeviceClass.c_str(), !inversedState, inversedState); + const __FlashStringHelper*componentClass = twoWay && discoveryItems[s].canSet + ? (isSwitch ? F("switch") : F("light")) + : F("binary_sensor"); + const String deviceClass = strformat(F("%s\",\"pl_on\":\"%d\",\"pl_off\":\"%d"), + valueDeviceClass.c_str(), !inversedState, inversedState); const String uom = MQTT_DiscoveryHelperGetValueUoM(x, v, discoveryItems[s]); if (discoveryItems[s].canSet) { @@ -1071,11 +1093,11 @@ bool MQTT_DiscoveryPublishWithStatusAndSet(taskIndex_t taskIndex, controllerIndex_t ControllerIndex, ControllerSettingsStruct& ControllerSettings, const __FlashStringHelper*componentClass, - String deviceClass, - String unitOfMeasure, + const String & deviceClass, + const String & unitOfMeasure, struct EventStruct *event, - const String deviceElement, - const String stateClass, + const String & deviceElement, + const String & stateClass, bool success, bool hasSet, bool hasIcon, @@ -1083,52 +1105,70 @@ bool MQTT_DiscoveryPublishWithStatusAndSet(taskIndex_t taskIndex, const String & elementId, bool sendTrigger) { if (!valueName.isEmpty()) { - const String withSet = hasSet ? F(",\"cmd_t\":\"~/set\"") : EMPTY_STRING; - const String schema = hasSet ? EMPTY_STRING : "\"schema\":\"basic\","; - const String devOrIcon = hasIcon ? F("ic") : F("dev_cla"); - const String withUoM = unitOfMeasure.isEmpty() ? EMPTY_STRING : - strformat(F(",\"unit_of_meas\":\"%s\""), unitOfMeasure.c_str()); - const String stateJson = stateClass.isEmpty() ? EMPTY_STRING : - strformat(F(",\"stat_cla\":\"%s\""), stateClass.c_str()); - const String taskName = makeHomeAssistantCompliantName(getTaskDeviceName(taskIndex)); - const String valName = makeHomeAssistantCompliantName(valueName); - const bool retainDsc = ControllerSettings.mqtt_retainDiscovery(); - const String discoveryTopic(ControllerSettings.MqttAutoDiscoveryTopic); - const String publishTopic(ControllerSettings.Publish); - const String discoveryConfig(parseStringKeepCase(ControllerSettings.MqttAutoDiscoveryConfig, 1, '|')); + // const String discoveryTopic(ControllerSettings.MqttAutoDiscoveryTopic); + // const String publishTopic(ControllerSettings.Publish); + + const String taskName = makeHomeAssistantCompliantName(getTaskDeviceName(taskIndex)); + const String valName = makeHomeAssistantCompliantName(valueName); const String uniqueId = elementName.isEmpty() ? MQTT_TaskValueUniqueName(taskName, valName) : strformat(F("%s_%s"), elementId.c_str(), valName.c_str()); - const String publish = MQTT_DiscoveryBuildValueTopic(publishTopic, - event, - taskValue, - componentClass, - uniqueId, - elementId, - valName); - const String discoveryUrl = MQTT_DiscoveryBuildValueTopic(discoveryTopic, - event, - taskValue, - componentClass, - uniqueId, - elementId, - valName); - const String discoveryMessage = strformat(F("{\"~\":\"%s\",\"name\":\"%s %s\",\"uniq_id\":\"%s\",%s" - "\"%s\":\"%s\"%s%s%s,\"stat_t\":\"~\"" - "%s}"), // deviceElement last - publish.c_str(), taskName.c_str(), valName.c_str(), uniqueId.c_str(), schema.c_str(), - devOrIcon.c_str(), deviceClass.c_str(), withUoM.c_str(), stateJson.c_str(), withSet.c_str(), - deviceElement.c_str()); - const String triggerMessage = strformat(F("{\"atype\":\"trigger\",\"t\":\"%s\"," - "\"p\":\"device_automation\"," - "\"type\":\"button_short_press\"," // FIXME ? - "\"stype\":\"switch_1\"" // FIXME ? - "%s}"), // deviceElement is used to pass in the TriggerState - publish.c_str(), deviceElement.c_str()); + String message; + { + const String publish = MQTT_DiscoveryBuildValueTopic(ControllerSettings.Publish, + event, + taskValue, + componentClass, + uniqueId, + elementId, + valName); + + if (sendTrigger) { + // triggerMessage + message = strformat(F("{\"atype\":\"trigger\",\"t\":\"%s\"," + "\"p\":\"device_automation\"," + "\"type\":\"button_short_press\"," // FIXME ? + "\"stype\":\"switch_1\"" // FIXME ? + "%s}"), // deviceElement is used to pass in the TriggerState + publish.c_str(), deviceElement.c_str()); + } else { + // discoveryMessage + const String withSet = hasSet ? F(",\"cmd_t\":\"~/set\"") : EMPTY_STRING; + const String schema = hasSet ? EMPTY_STRING : "\"schema\":\"basic\","; + const String devOrIcon = hasIcon ? F("ic") : F("dev_cla"); + const String withUoM = unitOfMeasure.isEmpty() ? EMPTY_STRING : + strformat(F(",\"unit_of_meas\":\"%s\""), unitOfMeasure.c_str()); + const String stateJson = stateClass.isEmpty() ? EMPTY_STRING : + strformat(F(",\"stat_cla\":\"%s\""), stateClass.c_str()); + + message = strformat(F("{\"~\":\"%s\",\"name\":\"%s %s\",\"uniq_id\":\"%s\",%s" + "\"%s\":\"%s\"%s%s%s,\"stat_t\":\"~\"" + "%s}"), // deviceElement last + publish.c_str(), taskName.c_str(), valName.c_str(), uniqueId.c_str(), schema.c_str(), + devOrIcon.c_str(), deviceClass.c_str(), withUoM.c_str(), stateJson.c_str(), withSet.c_str(), + deviceElement.c_str()); + } + } + + const bool retainDsc = ControllerSettings.mqtt_retainDiscovery(); + String topic; + { + const String discoveryUrl = MQTT_DiscoveryBuildValueTopic(ControllerSettings.MqttAutoDiscoveryTopic, + event, + taskValue, + componentClass, + uniqueId, + elementId, + valName); + const String discoveryConfig(parseStringKeepCase(ControllerSettings.MqttAutoDiscoveryConfig, 1, '|')); + topic = discoveryConfig.isEmpty() + ? concat(discoveryUrl, F("/config")) + : concat(discoveryUrl, discoveryConfig); + } return MQTT_DiscoveryPublish(ControllerIndex, - discoveryConfig.isEmpty() ? concat(discoveryUrl, F("/config")) : concat(discoveryUrl, discoveryConfig), - sendTrigger ? triggerMessage : discoveryMessage, + topic, + message, taskIndex, taskValue, retainDsc); diff --git a/src/src/Helpers/_CPlugin_Helper_mqtt.h b/src/src/Helpers/_CPlugin_Helper_mqtt.h index c636efa71..5494fbc8b 100644 --- a/src/src/Helpers/_CPlugin_Helper_mqtt.h +++ b/src/src/Helpers/_CPlugin_Helper_mqtt.h @@ -38,6 +38,7 @@ String makeHomeAssistantCompliantName(const String& name); # if FEATURE_MQTT_DEVICECLASS String MQTT_binary_deviceClassName(int devClassIndex); bool MQTT_binary_deviceClassTwoWay(int devClassIndex); +bool MQTT_binary_deviceClassSwitch(int devClassIndex); int MQTT_binary_deviceClassIndex(const String& deviceClassName); # endif // if FEATURE_MQTT_DEVICECLASS # if FEATURE_MQTT_STATE_CLASS @@ -96,11 +97,11 @@ bool MQTT_DiscoveryPublishWithStatusAndSet(taskIndex_t taskIndex, controllerIndex_t ControllerIndex, ControllerSettingsStruct& ControllerSettings, const __FlashStringHelper*componentClass, - String deviceClass, - String unitOfMeasure, + const String & deviceClass, + const String & unitOfMeasure, struct EventStruct *event, - const String deviceElement, - const String stateClass, + const String & deviceElement, + const String & stateClass, bool success, bool hasSet, bool hasIcon, diff --git a/src/src/Helpers/_CPlugin_Helper_webform.cpp b/src/src/Helpers/_CPlugin_Helper_webform.cpp index 249456a1b..7cd4b459b 100644 --- a/src/src/Helpers/_CPlugin_Helper_webform.cpp +++ b/src/src/Helpers/_CPlugin_Helper_webform.cpp @@ -92,7 +92,8 @@ String getControllerParameterName(protocolIndex_t ProtocolInde EventStruct tmpEvent; tmpEvent.idx = parameterIdx; - if (CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_GET_PROTOCOL_DISPLAY_NAME, &tmpEvent, name)) { + // Only Controller Plugin specific call, so may call do_CPluginCall directly + if (do_CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_GET_PROTOCOL_DISPLAY_NAME, &tmpEvent, name)) { // Found an alternative name for it. isAlternative = true; return name; @@ -200,7 +201,7 @@ void addControllerParameterForm(const ControllerSettingsStruct & ControllerSett addFormNumericBox(displayName, internalName, ControllerSettings.Port, 1, 65535); break; } -#if FEATURE_MQTT_TLS +#if FEATURE_MQTT_TLS || FEATURE_HTTP_TLS case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_TYPE: { const int choice = static_cast(ControllerSettings.TLStype()); @@ -265,7 +266,7 @@ void addControllerParameterForm(const ControllerSettingsStruct & ControllerSett */ break; } -#endif // if FEATURE_MQTT_TLS +#endif // if FEATURE_MQTT_TLS || FEATURE_HTTP_TLS case ControllerSettingsStruct::CONTROLLER_USER: { const size_t fieldMaxLength = @@ -454,7 +455,7 @@ void saveControllerParameterForm(ControllerSettingsStruct & ControllerSet case ControllerSettingsStruct::CONTROLLER_PORT: ControllerSettings.Port = getFormItemInt(internalName, ControllerSettings.Port); break; -#if FEATURE_MQTT_TLS +#if FEATURE_MQTT_TLS || FEATURE_HTTP_TLS case ControllerSettingsStruct::CONTROLLER_MQTT_TLS_TYPE: { const int current = static_cast(ControllerSettings.TLStype()); @@ -492,7 +493,7 @@ void saveControllerParameterForm(ControllerSettingsStruct & ControllerSet } break; } -#endif // if FEATURE_MQTT_TLS +#endif // if FEATURE_MQTT_TLS || FEATURE_HTTP_TLS case ControllerSettingsStruct::CONTROLLER_USER: setControllerUser(controllerindex, ControllerSettings, webArg(internalName)); break; diff --git a/src/src/Helpers/_CPlugin_init.cpp b/src/src/Helpers/_CPlugin_init.cpp index 863debffb..6fde51e14 100644 --- a/src/src/Helpers/_CPlugin_init.cpp +++ b/src/src/Helpers/_CPlugin_init.cpp @@ -9,6 +9,7 @@ #include "../Globals/Settings.h" #include "../Helpers/Misc.h" +#include "../Helpers/StringConverter.h" // ******************************************************************************** // Initialize all Controller CPlugins that where defined earlier @@ -2090,7 +2091,7 @@ ProtocolStruct& getProtocolStruct(protocolIndex_t protocolIndex) return ProtocolArray[protocolIndex]; } -protocolIndex_t getProtocolIndex_from_CPluginID_(cpluginID_t cpluginID) +protocolIndex_t do_getProtocolIndex_from_CPluginID(cpluginID_t cpluginID) { if (cpluginID < CPlugin_id_to_ProtocolIndex_size) { @@ -2099,7 +2100,7 @@ protocolIndex_t getProtocolIndex_from_CPluginID_(cpluginID_t cpluginID) return INVALID_PROTOCOL_INDEX; } -cpluginID_t getCPluginID_from_ProtocolIndex_(protocolIndex_t protocolIndex) +cpluginID_t do_getCPluginID_from_ProtocolIndex(protocolIndex_t protocolIndex) { if (protocolIndex < ProtocolIndex_to_CPlugin_id_size) { @@ -2109,7 +2110,7 @@ cpluginID_t getCPluginID_from_ProtocolIndex_(protocolIndex_t protocolIndex) return INVALID_C_PLUGIN_ID; } -bool validProtocolIndex_init(protocolIndex_t protocolIndex) +bool do_check_validProtocolIndex(protocolIndex_t protocolIndex) { return protocolIndex < ProtocolIndex_to_CPlugin_id_size; } @@ -2120,10 +2121,27 @@ cpluginID_t getHighestIncludedCPluginID() } -bool CPluginCall(protocolIndex_t protocolIndex, CPlugin::Function Function, struct EventStruct *event, String& string) +bool do_CPluginCall(protocolIndex_t protocolIndex, CPlugin::Function Function, struct EventStruct *event, String& string) { + static uint32_t controllerIndex_initialized{}; if (protocolIndex < ProtocolIndex_to_CPlugin_id_size) { + if (Function == CPlugin::Function::CPLUGIN_INIT) { + if (bitRead(controllerIndex_initialized, event->ControllerIndex)) { + // FIXME TD-er: What to do here? Was already initialized + addLog(LOG_LEVEL_ERROR, strformat(F("Controller %d was already initialized"), event->ControllerIndex + 1)); + return false; + } + bitSet(controllerIndex_initialized, event->ControllerIndex); + } else if (Function == CPlugin::Function::CPLUGIN_EXIT) { + if (!bitRead(controllerIndex_initialized, event->ControllerIndex)) { + // FIXME TD-er: What to do here? Was not (yet) initialized +// addLog(LOG_LEVEL_ERROR, strformat(F("Controller %d was not (yet) initialized"), event->ControllerIndex + 1)); + return false; + } + bitClear(controllerIndex_initialized, event->ControllerIndex); + } + START_TIMER; CPlugin_ptr_t cplugin_call = (CPlugin_ptr_t)pgm_read_ptr(CPlugin_ptr + protocolIndex); const bool res = cplugin_call(Function, event, string); @@ -2146,14 +2164,14 @@ void CPluginSetup() for (protocolIndex_t protocolIndex = 0; protocolIndex < ProtocolIndex_to_CPlugin_id_size; ++protocolIndex) { - const cpluginID_t cpluginID = getCPluginID_from_ProtocolIndex_(protocolIndex); + const cpluginID_t cpluginID = do_getCPluginID_from_ProtocolIndex(protocolIndex); if (INVALID_C_PLUGIN_ID != cpluginID) { CPlugin_id_to_ProtocolIndex[cpluginID] = protocolIndex; struct EventStruct TempEvent; TempEvent.idx = protocolIndex; String dummy; - CPluginCall(protocolIndex, CPlugin::Function::CPLUGIN_PROTOCOL_ADD, &TempEvent, dummy); + do_CPluginCall(protocolIndex, CPlugin::Function::CPLUGIN_PROTOCOL_ADD, &TempEvent, dummy); } } setupDone = true; @@ -2169,3 +2187,22 @@ void CPluginInit() } CPluginCall(CPlugin::Function::CPLUGIN_INIT_ALL, 0); } + +void CPlugin_Exit_Init(controllerIndex_t controllerIndex) +{ + protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(controllerIndex); + + if (validProtocolIndex(ProtocolIndex)) { + struct EventStruct TempEvent; + TempEvent.ControllerIndex = controllerIndex; + String dummy; + + + // May need to call init later, so make sure exit is called first + CPluginCall(CPlugin::Function::CPLUGIN_EXIT, &TempEvent, dummy); + + if (Settings.ControllerEnabled[controllerIndex]) { + CPluginCall(CPlugin::Function::CPLUGIN_INIT, &TempEvent, dummy); + } + } +} \ No newline at end of file diff --git a/src/src/Helpers/_CPlugin_init.h b/src/src/Helpers/_CPlugin_init.h index dc0627007..17470757d 100644 --- a/src/src/Helpers/_CPlugin_init.h +++ b/src/src/Helpers/_CPlugin_init.h @@ -5,26 +5,32 @@ #include "../DataTypes/ESPEasy_plugin_functions.h" +#include "../DataTypes/ControllerIndex.h" #include "../DataTypes/CPluginID.h" #include "../DataTypes/ProtocolIndex.h" #include "../DataStructs/ProtocolStruct.h" struct EventStruct; -protocolIndex_t getProtocolIndex_from_CPluginID_(cpluginID_t pluginID); -cpluginID_t getCPluginID_from_ProtocolIndex_(protocolIndex_t protocolIndex); -bool validProtocolIndex_init(protocolIndex_t protocolIndex); +protocolIndex_t do_getProtocolIndex_from_CPluginID(cpluginID_t pluginID); +cpluginID_t do_getCPluginID_from_ProtocolIndex(protocolIndex_t protocolIndex); +bool do_check_validProtocolIndex(protocolIndex_t protocolIndex); cpluginID_t getHighestIncludedCPluginID(); ProtocolStruct& getProtocolStruct(protocolIndex_t protocolIndex); -bool CPluginCall(protocolIndex_t protocolIndex, CPlugin::Function Function, struct EventStruct *event, String& string); +// Should only be called from CPluginCall, or maybe for very special occasions +bool do_CPluginCall(protocolIndex_t protocolIndex, CPlugin::Function Function, struct EventStruct *event, String& string); void CPluginSetup(); void CPluginInit(); +// Start or stop controller as how it is set in the Settings +void CPlugin_Exit_Init(controllerIndex_t controllerIndex); + + // Macro to forward declare the CPlugin_NNN functions. // N.B. Some controllers also have a do_process_cNNN_delay_queue function. // Forward declaration of these is done in ControllerQueue/ControllerDelayHandlerStruct.h diff --git a/src/src/Helpers/_ESPEasy_key_value_store.cpp b/src/src/Helpers/_ESPEasy_key_value_store.cpp new file mode 100644 index 000000000..2ce7638f6 --- /dev/null +++ b/src/src/Helpers/_ESPEasy_key_value_store.cpp @@ -0,0 +1,1148 @@ +#include "../Helpers/_ESPEasy_key_value_store.h" + +#if FEATURE_ESPEASY_KEY_VALUE_STORE + +# include "../Helpers/ESPEasy_Storage.h" +# include "../Helpers/ESPEasy_time_calc.h" +# include "../Helpers/KeyValueWriter.h" +# include "../Helpers/StringConverter.h" +# include "../Helpers/StringConverter_Numerical.h" + + +bool ESPEasy_key_value_store::isEmpty() const +{ + return + _4byte_data.empty() && + _8byte_data.empty() && + _string_data.empty(); +} + +void ESPEasy_key_value_store::clear() +{ + _4byte_data.clear(); + _8byte_data.clear(); + _string_data.clear(); + _state = State::Empty; +} + +bool ESPEasy_key_value_store::load( + SettingsType::Enum settingsType, + int index, + uint32_t offset_in_block, + uint16_t id_to_match) +{ + if (_state == State::NotChanged) { return true; } + int offset, max_size; + + if (!SettingsType::getSettingsParameters(settingsType, index, offset, max_size)) + { + _lastError = F("KVS: Invalid index"); + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_ERROR, _lastError); // addLog(LOG_LEVEL_DEBUG, _lastError); + # endif // ifndef BUILD_NO_DEBUG + return false; + } + + clear(); + + const uint32_t bufferSize = 128; + + char buffer[bufferSize] = { 0 }; + const uint8_t*buf_start = (const uint8_t *)buffer; + String result; + uint32_t readPos = offset_in_block; + + // Read header: + // - uint8_t: version of storage layer + // - uint8_t[3]: rawSize (offset to start of checksum) + result += LoadFromFile(settingsType, + index, + reinterpret_cast(&buffer), + 6, // readSize + readPos); + readPos += 6; + + + // TODO TD-er: Compute checksum & check version + + const uint8_t version = buffer[0]; + + // uint8_t[2]: ID to match, if not matched, then stop loading + const uint16_t id = (buffer[2] << 8) | buffer[1]; + + if (id != id_to_match) { + # ifndef BUILD_NO_DEBUG + _lastError = strformat(F("KVS: Stored ID %d != expected ID %d, stop loading"), id, id_to_match); + addLog(LOG_LEVEL_DEBUG, _lastError); + # endif // ifndef BUILD_NO_DEBUG + return false; + } + + const size_t payloadSize = (buffer[5] << 16) | (buffer[4] << 8) | buffer[3]; + const size_t totalSize = 6 + payloadSize + 16; // header + payload + checksum + const size_t startChecksumPos = offset_in_block + 6 + payloadSize; + + if ((offset_in_block + totalSize) > static_cast(max_size)) { + _state = State::ErrorOnLoad; + # ifndef BUILD_NO_DEBUG + _lastError = strformat(F("KVS: Total size %d + offset %d exceeds max size %d"), totalSize, offset_in_block, max_size); + addLog(LOG_LEVEL_ERROR, _lastError); // addLog(LOG_LEVEL_DEBUG, _lastError); + # endif // ifndef BUILD_NO_DEBUG + return false; + } +# ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, strformat(F("KVS Load : Total size %d + offset %d, max size %d"), totalSize, offset_in_block, max_size)); +# endif + + size_t bytesLeftPartialString{}; + String partialString; + KVS_StorageType::Enum storageType{}; + size_t sizePerType{}; + uint32_t key{}; + + const unsigned long start = millis(); + + while (readPos < startChecksumPos && timePassedSince(start) < 1000) { + // This loop always starts at the beginning of a key/value type, + // except when we're parsing a string which may be longer than the buffer + const uint32_t readSize = std::min(bufferSize, startChecksumPos - readPos); + result += LoadFromFile(settingsType, + index, + reinterpret_cast(&buffer), + readSize, + readPos); + addLog(LOG_LEVEL_INFO, strformat( + F("KVS: LoadFromFile readSize %d readPos %d"), + readSize, + readPos)); + uint32_t bufPos = 0; + bool loadNextFromFile = false; + + for (; !loadNextFromFile && bufPos < readSize;) { + // In this loop, only update bufPos and at the end also correct readPos + // as it might be easy to forget adding the same to both bufPos and readPos + // However in logs we might need to refer to the actual pos in the file/block + // For this the following define is used + # define LOG_READPOS_OFFSET (readPos + (bufPos - bufPos_start_loop)) + const uint32_t bufPos_start_loop = bufPos; + + if (!bytesLeftPartialString) { + // Parse KVS_StorageType::Enum + key + if ((readSize - bufPos) < 4) { + // Read next block + loadNextFromFile = true; + + } else { + const ESPEasy_key_value_store_4byte_data_t combined_key(buf_start + bufPos); + storageType = KVS_StorageType::get_StorageType_from_combined_key(combined_key.getUint32()); + sizePerType = KVS_StorageType::getStorageSizePerType(storageType); + key = KVS_StorageType::getKey_from_combined_key(combined_key.getUint32()); + + if ((sizePerType < 4) && (storageType != KVS_StorageType::Enum::string_type)) { + // Should not happen as those should not be stored in the file + _state = State::ErrorOnLoad; + # ifndef BUILD_NO_DEBUG + _lastError = strformat(F("KVS: Invalid storage type %d at readPos %d"), static_cast(storageType), LOG_READPOS_OFFSET); + addLog(LOG_LEVEL_ERROR, _lastError); + # endif // ifndef BUILD_NO_DEBUG + return false; + } + } + } + + if (!loadNextFromFile) { + switch (storageType) + { + case KVS_StorageType::Enum::bool_false: + case KVS_StorageType::Enum::bool_type: + case KVS_StorageType::Enum::bool_true: + { + // Store bool type + const bool res = storageType != KVS_StorageType::Enum::bool_false; + setValue(key, res); + bufPos += 4; + break; + } + + case KVS_StorageType::Enum::string_type: + { + if (bytesLeftPartialString == 0) { + // Found new string type + // Read expected size in next 2 bytes + if ((readSize - bufPos) < 6) { + // Read next block + loadNextFromFile = true; + } else { + bufPos += 4; + bytesLeftPartialString = (buffer[bufPos] << 8) | buffer[bufPos + 1]; + bufPos += 2; + + if (!partialString.reserve(bytesLeftPartialString)) + { + _state = State::ErrorOnLoad; + # ifndef BUILD_NO_DEBUG + _lastError = strformat(F("KVS: Could not allocate string size %d at readPos %d"), bytesLeftPartialString, + LOG_READPOS_OFFSET); + addLog(LOG_LEVEL_ERROR, _lastError); + # endif // ifndef BUILD_NO_DEBUG + return false; + } + } + } + + while (bytesLeftPartialString > 0 && bufPos < readSize) { + const char c = buffer[bufPos]; + --bytesLeftPartialString; + ++bufPos; + + if (c != '\0') { + partialString += c; + } else if (bytesLeftPartialString > 0) { + // What to do here if bytesLeftPartialString != 0 ???? + _state = State::ErrorOnLoad; + # ifndef BUILD_NO_DEBUG + _lastError = strformat(F("KVS: Unexpected end-of-string, expected %d more at readPos %d"), + bytesLeftPartialString, + LOG_READPOS_OFFSET); + addLog(LOG_LEVEL_ERROR, _lastError); + # endif // ifndef BUILD_NO_DEBUG + return false; + } + } + + if ((bytesLeftPartialString == 0) && (partialString.length() > 0)) { + setValue(key, std::move(partialString)); + partialString.clear(); + + // ++bufPos; // Skip over nul-termination char + } + break; + + } + + default: + { + if ((readSize - bufPos) < sizePerType) { + // Read next block + loadNextFromFile = true; + } else { + bufPos += 4; + const size_t bytesLeftPerType = sizePerType - 4; + + if (bytesLeftPerType == 8) { + // 8-byte value type + const ESPEasy_key_value_store_8byte_data_t data_8byte(buf_start + bufPos); + setValue(storageType, key, data_8byte); + } else if (bytesLeftPerType > 0) { + if (bytesLeftPerType == 4) { + const ESPEasy_key_value_store_4byte_data_t data_4byte(buf_start + bufPos); + setValue(storageType, key, data_4byte); + } else { + ESPEasy_key_value_store_4byte_data_t data_4byte{}; + + // FIXME TD-er: Need to check the byte order + memcpy(data_4byte.getBinary(), buf_start + bufPos, bytesLeftPerType); + setValue(storageType, key, data_4byte); + } + } + bufPos += bytesLeftPerType; + } + + break; + } + } + } + + if ((bufPos_start_loop == bufPos) && !loadNextFromFile) { + _state = State::ErrorOnLoad; + # ifndef BUILD_NO_DEBUG + _lastError = strformat(F("KVS: BUG! Did not increment bufPos while processing KVS_StorageType::Enum %d at readPos %d"), + static_cast(storageType), LOG_READPOS_OFFSET); + addLog(LOG_LEVEL_ERROR, _lastError); + # endif // ifndef BUILD_NO_DEBUG + return false; + } + readPos += (bufPos - bufPos_start_loop); + # undef LOG_READPOS_OFFSET + } + } + + if (timePassedSince(start) >= 1000) { + _state = State::ErrorOnLoad; +# ifndef BUILD_NO_DEBUG + _lastError = strformat( + F("KVS: Timeout! bufPos while processing KVS_StorageType::Enum %d at readPos %d, startChecksumPos: %d"), + static_cast(storageType), readPos, startChecksumPos); + addLog(LOG_LEVEL_ERROR, _lastError); + dump(); +# endif // ifndef BUILD_NO_DEBUG + return false; + + } + + // TODO TD-er: Read checksum + _state = State::NotChanged; + return true; +} + +bool ESPEasy_key_value_store::store( + SettingsType::Enum settingsType, + int index, + uint32_t offset_in_block, + uint16_t id_to_match) +{ + if (getState() == State::NotChanged) { + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, F("KVS: Content not changed, no need to save")); + # endif + return true; + } + + // FIXME TD-er: Must add some check to see if the existing data has changed before saving. + int offset, max_size; + + if (!SettingsType::getSettingsParameters(settingsType, index, offset, max_size)) + { + _state = State::ErrorOnSave; + # ifndef BUILD_NO_DEBUG + _lastError = F("KVS: Invalid index"); + addLog(LOG_LEVEL_ERROR, F("KVS: Invalid index")); // addLog(LOG_LEVEL_DEBUG, _lastError); + # endif // ifndef BUILD_NO_DEBUG + return false; + } + + # ifdef ESP8266 + uint16_t bufferSize = 256; + # endif // ifdef ESP8266 + # ifdef ESP32 + uint16_t bufferSize = 1024; + # endif // ifdef ESP32 + + if (bufferSize > max_size) { + bufferSize = max_size; + } + + std::vector buffer; + buffer.resize(bufferSize, 0); + uint8_t*buf_start = &(buffer[0]); + + String result; + + size_t writePos = offset_in_block; + size_t bufPos = 0; + + // Write header + buffer[bufPos++] = 0; // TODO TD-er: Add version + buffer[bufPos++] = id_to_match & 0xFF; + buffer[bufPos++] = (id_to_match >> 8) & 0xFF; + const size_t payloadSize = getPayloadStorageSize(); + const size_t totalSize = 6 + payloadSize + 16; // header + payload + checksum + const size_t startChecksumPos = offset_in_block + 4 + payloadSize; + + if ((offset_in_block + totalSize) > static_cast(max_size)) { + _state = State::ErrorOnSave; + # ifndef BUILD_NO_DEBUG + _lastError = strformat(F("KVS: Total size %d + offset %d exceeds max size %d"), totalSize, offset_in_block, max_size); + addLog(LOG_LEVEL_ERROR, _lastError); // addLog(LOG_LEVEL_DEBUG, _lastError); + # endif // ifndef BUILD_NO_DEBUG + return false; + } + + buffer[bufPos++] = payloadSize & 0xFF; + buffer[bufPos++] = (payloadSize >> 8) & 0xFF; + buffer[bufPos++] = (payloadSize >> 16) & 0xFF; + + bool mustFlushToFile = false; + auto it_str = _string_data.begin(); + auto it_4byte = _4byte_data.begin(); + auto it_8byte = _8byte_data.begin(); + int pos_in_partial_string = -1; + + while (writePos < startChecksumPos) { + if (it_str != _string_data.end()) { + // Write strings + + /* + const size_t expectedSize = + 4 + // key + 2 + // length + it_str->second.length() // string + + 1; // nul-terminator + */ + const uint16_t strLength = it_str->second.length(); + + if (pos_in_partial_string == -1) { + // Need to write key + length + if ((buffer.size() - bufPos) < 6) { + mustFlushToFile = true; + } else { + ESPEasy_key_value_store_4byte_data_t key_data; + key_data.setUint32(it_str->first); + memcpy(buf_start + bufPos, key_data.getBinary(), 4); + bufPos += 4; + buffer[bufPos++] = strLength >> 8; + buffer[bufPos++] = strLength & 0xFF; + + // Now we're at the start of the string + pos_in_partial_string = 0; + } + } + + if (!mustFlushToFile) { + mustFlushToFile = bufPos == buffer.size(); + + while (pos_in_partial_string >= 0 && + pos_in_partial_string < strLength && + !mustFlushToFile) + { + buffer[bufPos++] = it_str->second[pos_in_partial_string]; + ++pos_in_partial_string; + mustFlushToFile = bufPos == buffer.size(); + } + + if ((pos_in_partial_string == strLength) && + !mustFlushToFile) + { + // Write 0-termination + move to next string + // buffer[bufPos++] = 0; + pos_in_partial_string = -1; + ++it_str; + } + } + + } else if (it_8byte != _8byte_data.end()) { + // Write 8-byte data + if ((buffer.size() - bufPos) < 12) { + mustFlushToFile = true; + } else { + ESPEasy_key_value_store_4byte_data_t key_data; + key_data.setUint32(it_8byte->first); + memcpy(buf_start + bufPos, key_data.getBinary(), 4); + bufPos += 4; + + memcpy(buf_start + bufPos, it_8byte->second.getBinary(), 8); + bufPos += 8; + ++it_8byte; + } + + } else if (it_4byte != _4byte_data.end()) { + // Write 4-byte data + uint32_t combined_key = it_4byte->first; + KVS_StorageType::Enum storageType = KVS_StorageType::get_StorageType_from_combined_key(combined_key); + const uint32_t key = KVS_StorageType::getKey_from_combined_key(combined_key); + const size_t sizePerType = KVS_StorageType::getStorageSizePerType(storageType); + + if (sizePerType >= 4) { + if ((buffer.size() - bufPos) < sizePerType) { + mustFlushToFile = true; + } else { + + switch (storageType) + { + case KVS_StorageType::Enum::bool_false: + case KVS_StorageType::Enum::bool_true: + case KVS_StorageType::Enum::bool_type: + { + bool value{}; + getValue(key, value); + combined_key = KVS_StorageType::combine_StorageType_and_key( + value + ? KVS_StorageType::Enum::bool_true + : KVS_StorageType::Enum::bool_false + , key); + break; + } + default: + break; + + } + ESPEasy_key_value_store_4byte_data_t key_data; + key_data.setUint32(combined_key); + memcpy(buf_start + bufPos, key_data.getBinary(), 4); + bufPos += 4; + + const size_t bytesLeftPerType = sizePerType - 4; + + if (bytesLeftPerType != 0) { + if (bytesLeftPerType == 4) { + memcpy(buf_start + bufPos, it_4byte->second.getBinary(), bytesLeftPerType); + } else { + // FIXME TD-er: Need to check the byte order + memcpy(buf_start + bufPos, it_4byte->second.getBinary(), bytesLeftPerType); + } + bufPos += bytesLeftPerType; + ++it_4byte; + } + + } + } else { + _state = State::ErrorOnSave; + # ifndef BUILD_NO_DEBUG + _lastError = strformat(F("KVS: BUG! Should not have storage type %d stored with 0 bytes at writePos %d"), + static_cast(storageType), writePos + bufPos); + addLog(LOG_LEVEL_ERROR, _lastError); + # endif // ifndef BUILD_NO_DEBUG + return false; + } + } else { + mustFlushToFile = true; + } + + if (mustFlushToFile) { + result += SaveToFile(settingsType, index, buf_start, bufPos, writePos); + writePos += bufPos; + mustFlushToFile = false; + bufPos = 0; + + for (size_t i = 0; i < buffer.size(); ++i) { + buffer[i] = 0; + } + } + } + + if (bufPos) { + + // Some kind of error??? We should be done and + // writePos should be equal to startChecksumPos + + result += SaveToFile(settingsType, index, buf_start, bufPos, writePos); + writePos += bufPos; + mustFlushToFile = false; + bufPos = 0; + } + + // Consider a successful save the same as a fresh load. + // The data is now the same as what is stored +# ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, strformat(F("KVS: Written %d bytes"), writePos)); +# endif + _state = State::NotChanged; + dump(); + return true; +} + +size_t ESPEasy_key_value_store::getPayloadStorageSize() const +{ + size_t res{}; + + for (auto it = _string_data.begin(); it != _string_data.end(); ++it) + { + res += 4; // key size + res += 2; // string length + res += it->second.length(); // + 1; // include 0-termination + } + + for (auto it = _4byte_data.begin(); it != _4byte_data.end(); ++it) + { + res += KVS_StorageType::getStorageSizePerType( + KVS_StorageType::get_StorageType_from_combined_key(it->first)); + } + + for (auto it = _8byte_data.begin(); it != _8byte_data.end(); ++it) + { + res += KVS_StorageType::getStorageSizePerType( + KVS_StorageType::get_StorageType_from_combined_key(it->first)); + } + return res; +} + +bool ESPEasy_key_value_store::hasKey(KVS_StorageType::Enum storageType, uint32_t key) const +{ + if (!hasStorageType(storageType)) { return false; } + + const uint32_t combined_key = KVS_StorageType::combine_StorageType_and_key(storageType, key); + + if (storageType == KVS_StorageType::Enum::string_type) + { + return _string_data.find(combined_key) != _string_data.end(); + } + const size_t size = KVS_StorageType::getStorageSizePerType(storageType); + + if (size == 12) + { + return _8byte_data.find(combined_key) != _8byte_data.end(); + } + + if (size >= 4) { + return _4byte_data.find(combined_key) != _4byte_data.end(); + } + return false; +} + +KVS_StorageType::Enum ESPEasy_key_value_store::getStorageType(uint32_t key) const +{ + uint32_t cached_bitmap = _storage_type_present_cache; + + // Search all storage types, skipping the 'not_set' + for (size_t i = 0; + cached_bitmap && + i < static_cast(KVS_StorageType::Enum::MAX_Type); + ++i) + { + const KVS_StorageType::Enum res = static_cast(i); + + if (hasKey(res, key)) { return res; } + cached_bitmap >>= 1; + } + return KVS_StorageType::Enum::not_set; +} +#define KVS_STRINGPAIR_SEPARATOR ((char)1) +bool ESPEasy_key_value_store::getValue(uint32_t key, StringPair& stringPair) const +{ + String str; + if (!getValue(key, str)) { + return false; + } + const int separatorPos = str.indexOf(KVS_STRINGPAIR_SEPARATOR); + if (separatorPos < 0) return false; + stringPair.first = str.substring(0, separatorPos); + stringPair.second = str.substring(separatorPos + 1); + return true; +} + +void ESPEasy_key_value_store::setValue(uint32_t key, const StringPair& stringPair) +{ + String str; + str.reserve(stringPair.first.length() + 1 + stringPair.second.length()); + str += stringPair.first; + str += KVS_STRINGPAIR_SEPARATOR; + str += stringPair.second; + setValue(key, str); +} +#undef KVS_STRINGPAIR_SEPARATOR + +bool ESPEasy_key_value_store::getValue(uint32_t key, String& value) const +{ + const uint32_t combined_key = KVS_StorageType::combine_StorageType_and_key( + KVS_StorageType::Enum::string_type, + key); + + auto it = _string_data.find(combined_key); + + if (it == _string_data.end()) { return false; } + value = it->second; + return true; +} + +void ESPEasy_key_value_store::setValue(uint32_t key, const String& value) { setValue(key, String(value)); } + +void ESPEasy_key_value_store::setValue(uint32_t key, + const __FlashStringHelper*value) { setValue(key, String(value)); } + +void ESPEasy_key_value_store::setValue(uint32_t key, String&& value) +{ + const uint32_t combined_key = KVS_StorageType::combine_StorageType_and_key( + KVS_StorageType::Enum::string_type, + key); + auto it = _string_data.find(combined_key); + + if (it == _string_data.end()) { + // Does not yet exist. + _string_data.emplace(combined_key, std::move(value)); + _state = State::Changed; + } else { + if (!it->second.equals(value)) { + it->second = std::move(value); + _state = State::Changed; + } + } + setHasStorageType(KVS_StorageType::Enum::string_type); +} + +ESPEasy_key_value_store::map_4byte_data::const_iterator ESPEasy_key_value_store::get4byteIterator( + KVS_StorageType::Enum storageType, + uint32_t key) const +{ + if (!hasStorageType(storageType)) { return _4byte_data.end(); } + const uint32_t combined_key = KVS_StorageType::combine_StorageType_and_key(storageType, key); + return _4byte_data.find(combined_key); +} + +ESPEasy_key_value_store::map_8byte_data::const_iterator ESPEasy_key_value_store::get8byteIterator( + KVS_StorageType::Enum storageType, + uint32_t key) const +{ + if (!hasStorageType(storageType)) { return _8byte_data.end(); } + const uint32_t combined_key = KVS_StorageType::combine_StorageType_and_key(storageType, key); + return _8byte_data.find(combined_key); +} + +# define GET_4BYTE_TYPE(T, GF) \ + auto it = get4byteIterator(KVS_StorageType::Enum::T, key); \ + if (it == _4byte_data.end()) return false; \ + value = it->second.GF(); \ + return true; \ + +# define GET_8BYTE_TYPE(T, GF) \ + auto it = get8byteIterator(KVS_StorageType::Enum::T, key); \ + if (it == _8byte_data.end()) return false; \ + value = it->second.GF(); \ + return true; \ + + +# define SET_4BYTE_TYPE(T, SF) \ + const uint32_t combined_key = KVS_StorageType::combine_StorageType_and_key( \ + KVS_StorageType::Enum::T, key); \ + if (_4byte_data[combined_key].SF(value)) _state = State::Changed; \ + setHasStorageType(KVS_StorageType::Enum::T); + + +# define SET_8BYTE_TYPE(T, SF) \ + const uint32_t combined_key = KVS_StorageType::combine_StorageType_and_key( \ + KVS_StorageType::Enum::T, key); \ + if (_8byte_data[combined_key].SF(value)) _state = State::Changed; \ + setHasStorageType(KVS_StorageType::Enum::T); + +bool ESPEasy_key_value_store::getValue(uint32_t key, int8_t& value) const +{ + GET_4BYTE_TYPE(int8_type, getInt32) +} + +void ESPEasy_key_value_store::setValue(uint32_t key, const int8_t& value) { SET_4BYTE_TYPE(int8_type, setInt32) } + +bool ESPEasy_key_value_store::getValue(uint32_t key, uint8_t& value) const +{ + GET_4BYTE_TYPE(uint8_type, getUint32); +} + +void ESPEasy_key_value_store::setValue(uint32_t key, const uint8_t& value) { SET_4BYTE_TYPE(uint8_type, setUint32); } + +bool ESPEasy_key_value_store::getValue(uint32_t key, int16_t& value) const +{ + GET_4BYTE_TYPE(int16_type, getInt32); +} + +void ESPEasy_key_value_store::setValue(uint32_t key, const int16_t& value) { SET_4BYTE_TYPE(int16_type, setInt32); } + +bool ESPEasy_key_value_store::getValue(uint32_t key, uint16_t& value) const +{ + GET_4BYTE_TYPE(uint16_type, getUint32); + +} + +void ESPEasy_key_value_store::setValue(uint32_t key, const uint16_t& value) { SET_4BYTE_TYPE(uint16_type, setUint32); } + +bool ESPEasy_key_value_store::getValue(uint32_t key, int32_t& value) const +{ + GET_4BYTE_TYPE(int32_type, getInt32); +} + +void ESPEasy_key_value_store::setValue(uint32_t key, const int32_t& value) { SET_4BYTE_TYPE(int32_type, setInt32); } + +bool ESPEasy_key_value_store::getValue(uint32_t key, uint32_t& value) const +{ + GET_4BYTE_TYPE(uint32_type, getUint32); +} + +void ESPEasy_key_value_store::setValue(uint32_t key, const uint32_t& value) { SET_4BYTE_TYPE(uint32_type, setUint32); } + +bool ESPEasy_key_value_store::getValue(uint32_t key, int64_t& value) const +{ + GET_8BYTE_TYPE(int64_type, getInt64); +} + +void ESPEasy_key_value_store::setValue(uint32_t key, const int64_t& value) { SET_8BYTE_TYPE(int64_type, setInt64); } + +bool ESPEasy_key_value_store::getValue(uint32_t key, uint64_t& value) const +{ + GET_8BYTE_TYPE(uint64_type, getUint64); +} + +void ESPEasy_key_value_store::setValue(uint32_t key, const uint64_t& value) { SET_8BYTE_TYPE(uint64_type, setUint64); } + +bool ESPEasy_key_value_store::getValue(uint32_t key, float& value) const +{ + GET_4BYTE_TYPE(float_type, getFloat); +} + +void ESPEasy_key_value_store::setValue(uint32_t key, const float& value) { SET_4BYTE_TYPE(float_type, setFloat); } + +bool ESPEasy_key_value_store::getValue(uint32_t key, double& value) const +{ + GET_8BYTE_TYPE(double_type, getDouble); +} + +void ESPEasy_key_value_store::setValue(uint32_t key, const double& value) { SET_8BYTE_TYPE(double_type, setDouble); } + +bool ESPEasy_key_value_store::getValue(uint32_t key, bool& boolValue) const +{ + boolValue = false; + uint32_t value{}; + auto it = get4byteIterator(KVS_StorageType::Enum::bool_type, key); + + if (it == _4byte_data.end()) { return false; } + value = it->second.getUint32(); + boolValue = value != 0u; + return true; +} + +void ESPEasy_key_value_store::setValue(uint32_t key, const bool& boolValue) { + uint32_t value = boolValue ? 1 : 0; + SET_4BYTE_TYPE(bool_type, setUint32); +} + +bool ESPEasy_key_value_store::getValueAsString(const KVS_StorageType::Enum& storageType, uint32_t key, String& value) const +{ + if (!hasKey(storageType, key)) { return false; } + + switch (storageType) + { + case KVS_StorageType::Enum::string_type: + return getValue(key, value); + + case KVS_StorageType::Enum::bool_type: + case KVS_StorageType::Enum::int8_type: + case KVS_StorageType::Enum::uint8_type: + case KVS_StorageType::Enum::int16_type: + case KVS_StorageType::Enum::uint16_type: + case KVS_StorageType::Enum::int32_type: + case KVS_StorageType::Enum::uint32_type: + case KVS_StorageType::Enum::int64_type: + { + int64_t int64_value{}; + + if (getValueAsInt(key, int64_value)) { + value = ll2String(int64_value); + return true; + } + break; + } + + case KVS_StorageType::Enum::float_type: + { + float v{}; + + if (getValue(key, v)) { + value = floatToString(v, 6, true); + return true; + } + break; + } + + case KVS_StorageType::Enum::uint64_type: + { + uint64_t v{}; + + if (getValue(key, v)) { + value = ull2String(v); + return true; + } + break; + } + + case KVS_StorageType::Enum::double_type: + { + double v{}; + + if (getValue(key, v)) { + value = doubleToString(v, 6, true); + return true; + } + break; + } + + case KVS_StorageType::Enum::not_set: + case KVS_StorageType::Enum::bool_true: + case KVS_StorageType::Enum::bool_false: + case KVS_StorageType::Enum::binary: + case KVS_StorageType::Enum::MAX_Type: + break; + } + return false; +} + +bool ESPEasy_key_value_store::getValueAsString(uint32_t key, String& value) const +{ + return getValueAsString(getStorageType(key), key, value); +} + +# define GET_TYPE_AS_INT64(T, CT) \ + case KVS_StorageType::Enum::T: \ + { \ + CT v{}; \ + if (getValue(key, v)) { \ + value = v; \ + return true; \ + } \ + break; \ + } + +bool ESPEasy_key_value_store::getValueAsInt( + uint32_t key, + int64_t& value) const +{ + auto storageType = getStorageType(key); + + if (!hasKey(storageType, key)) { return false; } + + switch (storageType) + { + case KVS_StorageType::Enum::bool_type: + case KVS_StorageType::Enum::bool_true: + case KVS_StorageType::Enum::bool_false: + { + bool v{}; + + if (getValue(key, v)) + { + value = v ? 1 : 0; + return true; + } + break; + } + GET_TYPE_AS_INT64(int8_type, int8_t) + GET_TYPE_AS_INT64(uint8_type, uint8_t) + GET_TYPE_AS_INT64(int16_type, int16_t) + GET_TYPE_AS_INT64(uint16_type, uint16_t) + GET_TYPE_AS_INT64(int32_type, int32_t) + GET_TYPE_AS_INT64(uint32_type, uint32_t) + GET_TYPE_AS_INT64(float_type, float) + GET_TYPE_AS_INT64(uint64_type, uint64_t) + GET_TYPE_AS_INT64(double_type, double) + + case KVS_StorageType::Enum::int64_type: + return getValue(key, value); + default: break; + } + return false; +} + +int64_t ESPEasy_key_value_store::getValueAsInt_or_default(uint32_t key, int64_t default_value) const +{ + int64_t value = default_value; + + if (getValueAsInt(key, value)) { return value; } + return default_value; +} + +int64_t ESPEasy_key_value_store::getValueAsInt(uint32_t key) const +{ + return getValueAsInt_or_default(key, 0); +} + +# define GET_4BYTE_INT_TYPE_FROM_STRING(T, CT) \ + case KVS_StorageType::Enum::T: \ + { \ + CT v(value.toInt()); \ + setValue(key, v); \ + break; \ + } + +void ESPEasy_key_value_store::setValue(const KVS_StorageType::Enum& storageType, uint32_t key, const String& value) +{ + switch (storageType) + { + case KVS_StorageType::Enum::string_type: + setValue(key, value); + break; + GET_4BYTE_INT_TYPE_FROM_STRING(int8_type, int8_t) + GET_4BYTE_INT_TYPE_FROM_STRING(uint8_type, uint8_t) + GET_4BYTE_INT_TYPE_FROM_STRING(int16_type, int16_t) + GET_4BYTE_INT_TYPE_FROM_STRING(uint16_type, uint16_t) + GET_4BYTE_INT_TYPE_FROM_STRING(int32_type, int32_t) + GET_4BYTE_INT_TYPE_FROM_STRING(uint32_type, uint32_t) + case KVS_StorageType::Enum::double_type: + setValue(key, value.toDouble()); + break; + case KVS_StorageType::Enum::float_type: + setValue(key, value.toFloat()); + break; + case KVS_StorageType::Enum::int64_type: + { + // TODO TD-er: Implement + break; + } + + case KVS_StorageType::Enum::uint64_type: + { + // TODO TD-er: Implement + break; + } + case KVS_StorageType::Enum::bool_type: + case KVS_StorageType::Enum::bool_true: + case KVS_StorageType::Enum::bool_false: + { + const bool val = !(value.equalsIgnoreCase(F("false")) || value.equals(F("0"))); + setValue(key, val); + break; + } + default: return; + } + setHasStorageType(storageType); +} + +bool ESPEasy_key_value_store::getValue( + KVS_StorageType::Enum & storageType, + uint32_t key, + ESPEasy_key_value_store_4byte_data_t& value) const +{ + auto it = _4byte_data.find(KVS_StorageType::combine_StorageType_and_key(storageType, key)); + + if (it == _4byte_data.end()) { return false; } + memcpy(value.getBinary(), it->second.getBinary(), 4); + return true; +} + +void ESPEasy_key_value_store::setValue( + KVS_StorageType::Enum & storageType, + uint32_t key, + const ESPEasy_key_value_store_4byte_data_t& value) +{ + auto combined_key = KVS_StorageType::combine_StorageType_and_key(storageType, key); + auto it = _4byte_data.find(combined_key); + + if (it == _4byte_data.end()) { + // new entry + _4byte_data.emplace(combined_key, value); + _state = State::Changed; + } else { + if (it->second.set(value.getBinary())) { _state = State::Changed; } + } + setHasStorageType(storageType); +} + +bool ESPEasy_key_value_store::getValue( + KVS_StorageType::Enum & storageType, + uint32_t key, + ESPEasy_key_value_store_8byte_data_t& value) const +{ + auto it = _8byte_data.find(KVS_StorageType::combine_StorageType_and_key(storageType, key)); + + if (it == _8byte_data.end()) { return false; } + memcpy(value.getBinary(), it->second.getBinary(), 8); + return true; +} + +void ESPEasy_key_value_store::setValue( + KVS_StorageType::Enum & storageType, + uint32_t key, + const ESPEasy_key_value_store_8byte_data_t& value) +{ + auto combined_key = KVS_StorageType::combine_StorageType_and_key(storageType, key); + auto it = _8byte_data.find(combined_key); + + if (it == _8byte_data.end()) { + // new entry + _8byte_data.emplace(combined_key, value); + _state = State::Changed; + } else { + if (it->second.set(value.getBinary())) { _state = State::Changed; } + } + setHasStorageType(storageType); +} + +bool ESPEasy_key_value_store::hasStorageType(KVS_StorageType::Enum storageType) const +{ + // return true; + if ((storageType == KVS_StorageType::Enum::bool_true) || + (storageType == KVS_StorageType::Enum::bool_false)) { + storageType = KVS_StorageType::Enum::bool_type; + } + + const uint32_t bitnr = static_cast(storageType); + constexpr uint32_t max_bitnr = static_cast(KVS_StorageType::Enum::MAX_Type); + + if (bitnr >= max_bitnr) { return false; } + return bitRead(_storage_type_present_cache, bitnr); +} + +void ESPEasy_key_value_store::setHasStorageType(KVS_StorageType::Enum storageType) +{ + if ((storageType == KVS_StorageType::Enum::bool_true) || + (storageType == KVS_StorageType::Enum::bool_false)) { + storageType = KVS_StorageType::Enum::bool_type; + } + const uint32_t bitnr = static_cast(storageType); + constexpr uint32_t max_bitnr = static_cast(KVS_StorageType::Enum::MAX_Type); + + if (bitnr < max_bitnr) { + if (!bitRead(_storage_type_present_cache, bitnr)) { + _state = State::Changed; + bitSet(_storage_type_present_cache, bitnr); + } + } + + // TODO TD-er: Whenever this is called, there has been a change, so invalidate checksum +} + +void ESPEasy_key_value_store::dump() const +{ + addLog(LOG_LEVEL_INFO, strformat(F("KVS: Payload Storage size : %d"), getPayloadStorageSize())); + + for (auto it = _string_data.begin(); it != _string_data.end(); ++it) + { + + String val; + + if (!getValueAsString( + KVS_StorageType::get_StorageType_from_combined_key(it->first), + KVS_StorageType::getKey_from_combined_key(it->first), + val)) { + val = '-'; + } + + addLog(LOG_LEVEL_INFO, strformat( + F("KVS: type: %d, combined-key: %x, key: %d, value: '%s' '%s'"), + KVS_StorageType::get_StorageType_from_combined_key(it->first), + it->first, KVS_StorageType::getKey_from_combined_key(it->first), + it->second.c_str(), + val.c_str())); + + } + + for (auto it = _4byte_data.begin(); it != _4byte_data.end(); ++it) + { + String val; + + if (!getValueAsString( + KVS_StorageType::get_StorageType_from_combined_key(it->first), + KVS_StorageType::getKey_from_combined_key(it->first), + val)) { + val = '-'; + } + + addLog(LOG_LEVEL_INFO, strformat( + F("KVS: type: %d, comb: %x, key: %d, val: '%x %x %x %x' strval: '%s'"), + KVS_StorageType::get_StorageType_from_combined_key(it->first), + it->first, KVS_StorageType::getKey_from_combined_key(it->first), + it->second.getBinary()[0], + it->second.getBinary()[1], + it->second.getBinary()[2], + it->second.getBinary()[3], + val.c_str() + )); + + } + + for (auto it = _8byte_data.begin(); it != _8byte_data.end(); ++it) + { + String val; + + if (!getValueAsString( + KVS_StorageType::get_StorageType_from_combined_key(it->first), + KVS_StorageType::getKey_from_combined_key(it->first), + val)) { + val = '-'; + } + + addLog(LOG_LEVEL_INFO, strformat( + F("KVS: type: %d, comb: %x, key: %d, val: '%x %x %x %x %x %x %x %x' strval: '%s'"), + KVS_StorageType::get_StorageType_from_combined_key(it->first), + it->first, KVS_StorageType::getKey_from_combined_key(it->first), + it->second.getBinary()[0], + it->second.getBinary()[1], + it->second.getBinary()[2], + it->second.getBinary()[3], + it->second.getBinary()[4], + it->second.getBinary()[5], + it->second.getBinary()[6], + it->second.getBinary()[7], + val.c_str() + )); + + } + +} + + +#endif // if FEATURE_ESPEASY_KEY_VALUE_STORE diff --git a/src/src/Helpers/_ESPEasy_key_value_store.h b/src/src/Helpers/_ESPEasy_key_value_store.h new file mode 100644 index 000000000..5b4bec57e --- /dev/null +++ b/src/src/Helpers/_ESPEasy_key_value_store.h @@ -0,0 +1,226 @@ +#ifndef HELPERS_ESPEASY_KEY_VALUE_STORE_H +#define HELPERS_ESPEASY_KEY_VALUE_STORE_H + +#include "../../ESPEasy_common.h" + +#if FEATURE_ESPEASY_KEY_VALUE_STORE + +# include "../DataTypes/ESPEasy_key_value_store_data.h" +# include "../DataTypes/ESPEasy_key_value_store_StorageType.h" + +# include "../DataTypes/SettingsType.h" + +# include + +class KeyValueWriter; + +// Generic storage layer, which on a low level will store everything as a string. +// Low level storage structure: +// - uint8_t: version of storage layer +// - uint8_t[2]: ID to match, if not matched, then stop loading +// - uint8_t[3]: rawSize (offset to start of checksum) +// - List of key/value pairs: +// - uint8_t: storage type +// - uint8_t[3]: key +// - N bytes value; either: +// - String: 2 bytes length + string (without 0-termination) +// - shortened binary form +// ... +// - uint8_t[16] checksum + +class ESPEasy_key_value_store +{ +public: + + typedef std::pair StringPair; + + enum class State { + Empty, + NotChanged, // Not changed since last load/save + Changed, + ErrorOnLoad, + ErrorOnSave + + }; + + State getState() const { return _state; } + + bool isEmpty() const; + + void clear(); + + + bool load(SettingsType::Enum settingsType, + int index, + uint32_t offset_in_block, + uint16_t id_to_match); + bool store(SettingsType::Enum settingsType, + int index, + uint32_t offset_in_block, + uint16_t id_to_match); + + // Count all data to estimate how much storage space it would require to store everything in a somewhat compact form. + size_t getPayloadStorageSize() const; + + // Check to see if there is a key stored with given storage type + bool hasKey(KVS_StorageType::Enum storageType, + uint32_t key) const; + + // Try to find a key and get its storage type or 'not_set' if the key is not present + KVS_StorageType::Enum getStorageType(uint32_t key) const; + + + /* + ############################################### + ## get/set functions for all supported types ## + ############################################### + */ + + bool getValue(uint32_t key, + StringPair& stringPair) const; + void setValue(uint32_t key, + const StringPair& stringPair); + + bool getValue(uint32_t key, + String & value) const; + void setValue(uint32_t key, + const String& value); + void setValue(uint32_t key, + const __FlashStringHelper*value); + void setValue(uint32_t key, + String&& value); + + bool getValue(uint32_t key, + int8_t & value) const; + void setValue(uint32_t key, + const int8_t& value); + + bool getValue(uint32_t key, + uint8_t& value) const; + void setValue(uint32_t key, + const uint8_t& value); + + bool getValue(uint32_t key, + int16_t& value) const; + void setValue(uint32_t key, + const int16_t& value); + + bool getValue(uint32_t key, + uint16_t& value) const; + void setValue(uint32_t key, + const uint16_t& value); + + bool getValue(uint32_t key, + int32_t& value) const; + void setValue(uint32_t key, + const int32_t& value); + + bool getValue(uint32_t key, + uint32_t& value) const; + void setValue(uint32_t key, + const uint32_t& value); + + bool getValue(uint32_t key, + int64_t& value) const; + void setValue(uint32_t key, + const int64_t& value); + + bool getValue(uint32_t key, + uint64_t& value) const; + void setValue(uint32_t key, + const uint64_t& value); + + bool getValue(uint32_t key, + float & value) const; + void setValue(uint32_t key, + const float& value); + + bool getValue(uint32_t key, + double & value) const; + void setValue(uint32_t key, + const double& value); + + bool getValue(uint32_t key, + bool & value) const; + void setValue(uint32_t key, + const bool& value); + + + // Generic get function for any given storageType/key and represent its value as a string. + // Return false when storageType/key is not present. + bool getValueAsString(const KVS_StorageType::Enum& storageType, + uint32_t key, + String & value) const; + + bool getValueAsString(uint32_t key, + String & value) const; + + bool getValueAsInt(uint32_t key, + int64_t& value) const; + + int64_t getValueAsInt_or_default(uint32_t key, + int64_t default_value) const; + int64_t getValueAsInt(uint32_t key) const; + + // Generic set function for any given storageType/key. + // Given value is a string representation of that storage type. + // TODO TD-er: Implement + void setValue(const KVS_StorageType::Enum& storageType, + uint32_t key, + const String & value); + + String getLastError() const { return _lastError; } + + void dump() const; + +private: + + bool getValue(KVS_StorageType::Enum & storageType, + uint32_t key, + ESPEasy_key_value_store_4byte_data_t& value) const; + + void setValue(KVS_StorageType::Enum & storageType, + uint32_t key, + const ESPEasy_key_value_store_4byte_data_t& value); + + bool getValue(KVS_StorageType::Enum & storageType, + uint32_t key, + ESPEasy_key_value_store_8byte_data_t& value) const; + + void setValue(KVS_StorageType::Enum & storageType, + uint32_t key, + const ESPEasy_key_value_store_8byte_data_t& value); + + + // Query cache to see if we have any of the asked storage type + bool hasStorageType(KVS_StorageType::Enum storageType) const; + + // Update cache to indicate we have at least one of the given storage type + void setHasStorageType(KVS_StorageType::Enum storageType); + + typedef std::map map_4byte_data; + typedef std::map map_8byte_data; + + map_4byte_data::const_iterator get4byteIterator( + KVS_StorageType::Enum storageType, + uint32_t key) const; + map_8byte_data::const_iterator get8byteIterator( + KVS_StorageType::Enum storageType, + uint32_t key) const; + + String _lastError; + + std::map_string_data{}; + map_4byte_data _4byte_data{}; + map_8byte_data _8byte_data{}; + + uint32_t _storage_type_present_cache{}; + + // TODO TD-er: Add checksum and invalidate whenever anyting is being stored. + State _state{ State::Empty }; + +}; // class ESPEasy_key_value_store + +#endif // if FEATURE_ESPEASY_KEY_VALUE_STORE + +#endif // ifndef HELPERS_ESPEASY_KEY_VALUE_STORE_H diff --git a/src/src/Helpers/_Plugin_Helper_serial.cpp b/src/src/Helpers/_Plugin_Helper_serial.cpp index f3a495be4..7ab423bac 100644 --- a/src/src/Helpers/_Plugin_Helper_serial.cpp +++ b/src/src/Helpers/_Plugin_Helper_serial.cpp @@ -17,7 +17,7 @@ #include -String serialHelper_getSerialTypeLabel(ESPEasySerialPort serType, bool shortName) { +const __FlashStringHelper* serialHelper_getSerialTypeLabel(ESPEasySerialPort serType, bool shortName) { return ESPEasySerialPort_toString(serType, shortName); } @@ -227,6 +227,18 @@ void serialHelper_webformLoad(ESPEasySerialPort port, int rxPinDef, int txPinDef //" document.querySelector('#taskdevicepin2').value =" STRINGIFY(SOC_TX2) ";" " style = '';" #endif +# if USABLE_SOC_UART_NUM > 3 +" } else if (elem.value == 10) {" +" style = '';" +#endif +# if USABLE_SOC_UART_NUM > 4 +" } else if (elem.value == 11) {" +" style = '';" +#endif +# if USABLE_SOC_UART_NUM > 5 +" } else if (elem.value == 12) {" +" style = '';" +#endif #if USES_SW_SERIAL " } else if (elem.value == 6) {" " style = '';" @@ -256,6 +268,15 @@ void serialHelper_webformLoad(ESPEasySerialPort port, int rxPinDef, int txPinDef #if USABLE_SOC_UART_NUM > 2 ,static_cast(ESPEasySerialPort::serial2) #endif // if USABLE_SOC_UART_NUM > 2 +#if USABLE_SOC_UART_NUM > 3 + ,static_cast(ESPEasySerialPort::serial3) +#endif +#if USABLE_SOC_UART_NUM > 4 + ,static_cast(ESPEasySerialPort::serial4) +#endif +#if USABLE_SOC_UART_NUM > 5 + ,static_cast(ESPEasySerialPort::serial5) +#endif #if USES_SW_SERIAL ,static_cast(ESPEasySerialPort::software) #endif // if USES_SW_SERIAL diff --git a/src/src/Helpers/_Plugin_Helper_serial.h b/src/src/Helpers/_Plugin_Helper_serial.h index 1273c4085..c889331ef 100644 --- a/src/src/Helpers/_Plugin_Helper_serial.h +++ b/src/src/Helpers/_Plugin_Helper_serial.h @@ -11,7 +11,7 @@ struct ESPeasySerialType; -String serialHelper_getSerialTypeLabel(ESPEasySerialPort serType, +const __FlashStringHelper* serialHelper_getSerialTypeLabel(ESPEasySerialPort serType, bool shortName = false); void serialHelper_log_GpioDescription(ESPEasySerialPort typeHint, diff --git a/src/src/Helpers/_Plugin_init.cpp b/src/src/Helpers/_Plugin_init.cpp index 874df65f9..8c4c3cf4b 100644 --- a/src/src/Helpers/_Plugin_init.cpp +++ b/src/src/Helpers/_Plugin_init.cpp @@ -6,6 +6,7 @@ #include "../Globals/Settings.h" #include "../Helpers/Misc.h" +#include "../Helpers/StringConverter.h" // ******************************************************************************** @@ -2164,14 +2165,14 @@ deviceIndex_t getDeviceIndex_from_PluginID(pluginID_t pluginID) pluginID_t getPluginID_from_DeviceIndex(deviceIndex_t deviceIndex) { - if (validDeviceIndex_init(deviceIndex)) + if (do_check_validDeviceIndex(deviceIndex)) { return pluginID_t::toPluginID(pgm_read_byte(DeviceIndex_to_Plugin_id + deviceIndex.value)); } return INVALID_PLUGIN_ID; } -bool validDeviceIndex_init(deviceIndex_t deviceIndex) +bool do_check_validDeviceIndex(deviceIndex_t deviceIndex) { if (_Plugin_init_setupDone) { return deviceIndex < DeviceIndex_to_Plugin_id_size; @@ -2182,17 +2183,35 @@ bool validDeviceIndex_init(deviceIndex_t deviceIndex) // Array containing "DeviceIndex" alfabetically sorted. deviceIndex_t getDeviceIndex_sorted(deviceIndex_t deviceIndex) { - if (validDeviceIndex_init(deviceIndex)) { + if (do_check_validDeviceIndex(deviceIndex)) { return DeviceIndex_sorted[deviceIndex.value]; } return INVALID_DEVICE_INDEX; } -boolean PluginCall(deviceIndex_t deviceIndex, uint8_t function, struct EventStruct *event, String& string) +boolean do_PluginCall(deviceIndex_t deviceIndex, uint8_t function, struct EventStruct *event, String& string) { - if (validDeviceIndex_init(deviceIndex)) + // TODO TD-er: May need to be changed to some other type when we ever will have > 64 tasks + static uint64_t taskIndex_initialized{}; + if (do_check_validDeviceIndex(deviceIndex)) { + if (function == PLUGIN_INIT) { + if (bitReadULL(taskIndex_initialized, event->TaskIndex)) { + // FIXME TD-er: What to do here? Was already initialized + addLog(LOG_LEVEL_ERROR, strformat(F("Task %d was already initialized"), event->TaskIndex + 1)); + return false; + } + bitSetULL(taskIndex_initialized, event->TaskIndex); + } else if (function == PLUGIN_EXIT) { + if (!bitReadULL(taskIndex_initialized, event->TaskIndex)) { + // FIXME TD-er: What to do here? Was not (yet) initialized +// addLog(LOG_LEVEL_ERROR, strformat(F("Task %d was not (yet) initialized"), event->TaskIndex + 1)); + return false; + } + bitClearULL(taskIndex_initialized, event->TaskIndex); + } + Plugin_ptr_t plugin_call = (Plugin_ptr_t)pgm_read_ptr(Plugin_ptr + deviceIndex.value); return plugin_call(function, event, string); } @@ -2232,7 +2251,7 @@ void PluginSetup() struct EventStruct TempEvent; TempEvent.idx = deviceIndex.value; String dummy; - PluginCall(deviceIndex, PLUGIN_DEVICE_ADD, &TempEvent, dummy); + do_PluginCall(deviceIndex, PLUGIN_DEVICE_ADD, &TempEvent, dummy); } } } diff --git a/src/src/Helpers/_Plugin_init.h b/src/src/Helpers/_Plugin_init.h index 79436fd04..ac6f565eb 100644 --- a/src/src/Helpers/_Plugin_init.h +++ b/src/src/Helpers/_Plugin_init.h @@ -12,13 +12,13 @@ struct EventStruct; deviceIndex_t getDeviceIndex_from_PluginID(pluginID_t pluginID); pluginID_t getPluginID_from_DeviceIndex(deviceIndex_t deviceIndex); -bool validDeviceIndex_init(deviceIndex_t deviceIndex); +bool do_check_validDeviceIndex(deviceIndex_t deviceIndex); // Array containing "DeviceIndex" alfabetically sorted. deviceIndex_t getDeviceIndex_sorted(deviceIndex_t deviceIndex); -boolean PluginCall(deviceIndex_t deviceIndex, uint8_t function, struct EventStruct *event, String& string); +boolean do_PluginCall(deviceIndex_t deviceIndex, uint8_t function, struct EventStruct *event, String& string); // Get the sizeof() in number of bits for the number of actually included plugins in the build unsigned getNrBitsDeviceIndex(); diff --git a/src/src/NotifierStructs/N001_data_struct.cpp b/src/src/NotifierStructs/N001_data_struct.cpp index 38cee7e00..dca4cff69 100644 --- a/src/src/NotifierStructs/N001_data_struct.cpp +++ b/src/src/NotifierStructs/N001_data_struct.cpp @@ -206,7 +206,9 @@ bool NPlugin_001_send(const NotificationSettingsStruct& notificationsettings, St // The MTA Exchange if (!failFlag) { +#ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("Email: Initializing ...")); +#endif # ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, strformat(F("Email: Max Allowed Timeout is %d secs"), clientTimeout / 1000)); @@ -315,8 +317,9 @@ bool NPlugin_001_send(const NotificationSettingsStruct& notificationsettings, St int i = 0; String emailTo; const String receiver(tmp_ato); - +#ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, strformat(F("Email: Receiver(s): %s"), receiver.c_str())); +#endif if (!getNextMailAddress(receiver, emailTo, i)) { addLog(LOG_LEVEL_ERROR, F("Email: Receiver missing!")); @@ -324,9 +327,11 @@ bool NPlugin_001_send(const NotificationSettingsStruct& notificationsettings, St } while (nextAddressAvailable) { +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, concat(F("Email: To "), emailTo)); } +#endif if (!NPlugin_001_MTA(*client, strformat(F("RCPT TO:<%s>"), emailTo.c_str()), 250, clientTimeout)) { @@ -360,7 +365,7 @@ bool NPlugin_001_send(const NotificationSettingsStruct& notificationsettings, St break; } } - +#ifndef BUILD_NO_DEBUG if (myStatus) { addLog(LOG_LEVEL_INFO, F("Email: Connection Closed Successfully")); } else { @@ -368,6 +373,7 @@ bool NPlugin_001_send(const NotificationSettingsStruct& notificationsettings, St addLogMove(LOG_LEVEL_ERROR, concat(F("Email: Connection Closed With Error. Used header: "), mailheader)); } } +#endif } client->stop(); @@ -392,10 +398,11 @@ bool NPlugin_001_Auth(WiFiClient& client, const String& user, const String& pass if (!NPlugin_001_MTA(client, encoder.encode(user), 334, timeout)) { success = false; } if (!NPlugin_001_MTA(client, encoder.encode(pass), 235, timeout)) { success = false; } - +#ifndef BUILD_NO_DEBUG if (success) { addLog(LOG_LEVEL_INFO, F("Email: Credentials Accepted")); } +#endif return success; } diff --git a/src/src/PluginStructs/P002_data_struct.cpp b/src/src/PluginStructs/P002_data_struct.cpp index 6492fd526..d8e716c12 100644 --- a/src/src/PluginStructs/P002_data_struct.cpp +++ b/src/src/PluginStructs/P002_data_struct.cpp @@ -1,33 +1,32 @@ #include "../PluginStructs/P002_data_struct.h" #ifdef USES_P002 -#if SOC_ADC_SUPPORTED || defined(ESP8266) +# if SOC_ADC_SUPPORTED || defined(ESP8266) -# include "../Globals/RulesCalculate.h" +# include "../Globals/RulesCalculate.h" -# include "../Helpers/Hardware_ADC_cali.h" +# include "../Helpers/Hardware_ADC_cali.h" -# ifndef DEFAULT_VREF -# define DEFAULT_VREF 1100 -# endif // ifndef DEFAULT_VREF - -# ifndef P002_ADC_ATTEN_MAX -# if ESP_IDF_VERSION_MAJOR < 5 -# define P002_ADC_ATTEN_MAX ADC_ATTEN_MAX -# else // if ESP_IDF_VERSION_MAJOR < 5 -# define P002_ADC_ATTEN_MAX ADC_ATTENDB_MAX -# endif // if ESP_IDF_VERSION_MAJOR < 5 -# endif // ifndef P002_ADC_ATTEN_MAX +# ifndef DEFAULT_VREF +# define DEFAULT_VREF 1100 +# endif // ifndef DEFAULT_VREF +# ifndef P002_ADC_ATTEN_MAX +# if ESP_IDF_VERSION_MAJOR < 5 +# define P002_ADC_ATTEN_MAX ADC_ATTEN_MAX +# else // if ESP_IDF_VERSION_MAJOR < 5 +# define P002_ADC_ATTEN_MAX ADC_ATTENDB_MAX +# endif // if ESP_IDF_VERSION_MAJOR < 5 +# endif // ifndef P002_ADC_ATTEN_MAX void P002_data_struct::init(struct EventStruct *event) { _sampleMode = P002_OVERSAMPLING; - # ifdef ESP8266 + # ifdef ESP8266 _pin_analogRead = A0; - # endif // ifdef ESP8266 - # ifdef ESP32 + # endif // ifdef ESP8266 + # ifdef ESP32 _pin_analogRead = CONFIG_PIN1; _useFactoryCalibration = useFactoryCalibration(event); _attenuation = getAttenuation(event); @@ -35,7 +34,7 @@ void P002_data_struct::init(struct EventStruct *event) // Initialize attenuation and perform read // This way there is less chance of a big difference between 1st read and any next reads analog_read(); - # endif // ifdef ESP32 + # endif // ifdef ESP32 if (P002_CALIBRATION_ENABLED) { _use2pointCalibration = true; @@ -45,15 +44,16 @@ void P002_data_struct::init(struct EventStruct *event) _calib_out2 = P002_CALIBRATION_VALUE2; } _nrDecimals = Cache.getTaskDeviceValueDecimals(event->TaskIndex, 0); -# ifndef LIMIT_BUILD_SIZE +# ifndef LIMIT_BUILD_SIZE _nrMultiPointItems = P002_NR_MULTIPOINT_ITEMS; _useMultipoint = P002_MULTIPOINT_ENABLED; load(event); -# endif // ifndef LIMIT_BUILD_SIZE +# endif // ifndef LIMIT_BUILD_SIZE } -# ifndef LIMIT_BUILD_SIZE +# ifndef LIMIT_BUILD_SIZE + void P002_data_struct::load(struct EventStruct *event) { const size_t nr_lines = P002_Nlines; @@ -70,26 +70,27 @@ void P002_data_struct::load(struct EventStruct *event) if (validFloatFromString(lines[i], adc) && validFloatFromString(lines[i + 1], value)) { // sizeof() multipoint item is multiple of 4 bytes, so should work just fine on 2nd heap - # ifdef USE_SECOND_HEAP + # ifdef USE_SECOND_HEAP HeapSelectIram ephemeral; - # endif // ifdef USE_SECOND_HEAP + # endif // ifdef USE_SECOND_HEAP _multipoint.emplace_back(adc, value); } } } + std::sort(_multipoint.begin(), _multipoint.end()); { - # ifdef USE_SECOND_HEAP + # ifdef USE_SECOND_HEAP HeapSelectIram ephemeral; - # endif // ifdef USE_SECOND_HEAP + # endif // ifdef USE_SECOND_HEAP _binning.resize(_multipoint.size(), 0); _binningRange.resize(_multipoint.size()); } } -# endif // ifndef LIMIT_BUILD_SIZE +# endif // ifndef LIMIT_BUILD_SIZE void P002_data_struct::webformLoad_2p_calibPoint( const __FlashStringHelper *label, @@ -101,12 +102,12 @@ void P002_data_struct::webformLoad_2p_calibPoint( addRowLabel_tr_id(label, id_point); addTextBox(id_point, String(point), 10, F("number")); -# ifdef ESP32 +# ifdef ESP32 if (_useFactoryCalibration) { addUnit(F("mV")); } -# endif // ifdef ESP32 +# endif // ifdef ESP32 html_add_estimate_symbol(); const unsigned int display_nrDecimals = _nrDecimals > 3 ? _nrDecimals : 3; @@ -120,21 +121,21 @@ void P002_data_struct::webformLoad(struct EventStruct *event) int raw_value = 0; const float currentValue = P002_data_struct::getCurrentValue(event, raw_value); -# if FEATURE_PLUGIN_STATS +# if FEATURE_PLUGIN_STATS PluginStats *stats = getPluginStats(0); if (stats != nullptr) { stats->trackPeak(raw_value); } -# endif // if FEATURE_PLUGIN_STATS +# endif // if FEATURE_PLUGIN_STATS -# ifdef ESP32 +# ifdef ESP32 addRowLabel(F("Analog Pin")); - # if HAS_HALL_EFFECT_SENSOR + # if HAS_HALL_EFFECT_SENSOR addADC_PinSelect(AdcPinSelectPurpose::ADC_Touch_HallEffect, F("taskdevicepin1"), CONFIG_PIN1); - # else // if HAS_HALL_EFFECT_SENSOR + # else // if HAS_HALL_EFFECT_SENSOR addADC_PinSelect(AdcPinSelectPurpose::ADC_Touch, F("taskdevicepin1"), CONFIG_PIN1); - # endif // if HAS_HALL_EFFECT_SENSOR + # endif // if HAS_HALL_EFFECT_SENSOR { const __FlashStringHelper *outputOptions[] = { @@ -154,29 +155,29 @@ void P002_data_struct::webformLoad(struct EventStruct *event) selector.addFormSelector(F("Attenuation"), F("attn"), P002_ATTENUATION); } -# endif // ifdef ESP32 +# endif // ifdef ESP32 { const __FlashStringHelper *outputOptions[] = { F("Use Current Sample"), F("Oversampling") -# ifndef LIMIT_BUILD_SIZE +# ifndef LIMIT_BUILD_SIZE , F("Binning") -# endif // ifndef LIMIT_BUILD_SIZE +# endif // ifndef LIMIT_BUILD_SIZE }; const int outputOptionValues[] = { P002_USE_CURENT_SAMPLE, P002_USE_OVERSAMPLING -# ifndef LIMIT_BUILD_SIZE +# ifndef LIMIT_BUILD_SIZE , P002_USE_BINNING -# endif // ifndef LIMIT_BUILD_SIZE +# endif // ifndef LIMIT_BUILD_SIZE }; constexpr int nrOptions = NR_ELEMENTS(outputOptionValues); const FormSelectorOptions selector(nrOptions, outputOptions, outputOptionValues); selector.addFormSelector(F("Oversampling"), F("oversampling"), P002_OVERSAMPLING); } -# ifdef ESP32 +# ifdef ESP32 addFormSubHeader(F("Factory Calibration")); addFormCheckBox(F("Apply Factory Calibration"), F("fac_cal"), P002_APPLY_FACTORY_CALIB, !hasADC_factory_calibration()); addFormNote(F("When checked, reading is in mV")); @@ -184,18 +185,19 @@ void P002_data_struct::webformLoad(struct EventStruct *event) if (hasADC_factory_calibration()) { addRowLabel(F("Factory Calibration Type")); addHtml(getADC_factory_calibration_type()); - # if FEATURE_CHART_JS + # if FEATURE_CHART_JS webformLoad_calibrationCurve(event); - # endif // if FEATURE_CHART_JS - # ifdef ESP32 + # endif // if FEATURE_CHART_JS + # ifdef ESP32 + if (_useFactoryCalibration) { formatADC_statistics(F("Current Voltage"), raw_value); } else { formatADC_statistics(F("Current ADC raw value"), raw_value); } - #else + # else // ifdef ESP32 formatADC_statistics(F("Current ADC raw value"), raw_value); - #endif + # endif // ifdef ESP32 for (size_t att = 0; att < P002_ADC_ATTEN_MAX; ++att) { const adc_atten_t attenuation = static_cast(att); @@ -214,17 +216,19 @@ void P002_data_struct::webformLoad(struct EventStruct *event) addUnit(F("mV")); } } -# endif // ifdef ESP32 +# endif // ifdef ESP32 addFormSubHeader(F("Two Point Calibration")); addFormCheckBox(F("Calibration Enabled"), F("cal"), P002_CALIBRATION_ENABLED); -# ifdef ESP8266 -# if FEATURE_ADC_VCC +# ifdef ESP8266 +# if FEATURE_ADC_VCC +#ifndef LIMIT_BUILD_SIZE addFormNote(F("Measuring ESP VCC, not A0. Unit is 1/1024 V. See documentation.")); -# endif // if FEATURE_ADC_VCC -# endif // ifdef ESP8266 +#endif +# endif // if FEATURE_ADC_VCC +# endif // ifdef ESP8266 webformLoad_2p_calibPoint( @@ -239,15 +243,16 @@ void P002_data_struct::webformLoad(struct EventStruct *event) F("out2"), P002_CALIBRATION_POINT2, P002_CALIBRATION_VALUE2); - +#ifndef LIMIT_BUILD_SIZE addFormNote(F("Input float values will be stored as int, calibration values will be adjusted accordingly")); +#endif { // Output the statistics for the current settings. if (P002_CALIBRATION_ENABLED) { - # if FEATURE_CHART_JS + # if FEATURE_CHART_JS webformLoad_2pt_calibrationCurve(event); - # endif // if FEATURE_CHART_JS + # endif // if FEATURE_CHART_JS int minInputValue, maxInputValue; getInputRange(event, minInputValue, maxInputValue); @@ -268,7 +273,7 @@ void P002_data_struct::webformLoad(struct EventStruct *event) addHtmlFloat(currentValue, _nrDecimals); } } -# ifndef LIMIT_BUILD_SIZE +# ifndef LIMIT_BUILD_SIZE const bool useBinning = P002_OVERSAMPLING == P002_USE_BINNING; addFormSubHeader(useBinning ? F("Binning Processing") : F("Multipoint Processing")); addFormCheckBox(useBinning ? F("Binning Processing Enabled") : F("Multipoint Processing Enabled"), @@ -299,11 +304,11 @@ void P002_data_struct::webformLoad(struct EventStruct *event) getPluginCustomArgName(varNr), _multipoint.size() > line_nr ? -# if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE +# if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE doubleToString -# else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE +# else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE floatToString -# endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE +# endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE (static_cast(_multipoint[line_nr]._adc), _nrDecimals, true) : EMPTY_STRING, @@ -311,11 +316,11 @@ void P002_data_struct::webformLoad(struct EventStruct *event) html_add_estimate_symbol(); addTextBox(getPluginCustomArgName(varNr + 1), _multipoint.size() > line_nr ? -# if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE +# if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE doubleToString -# else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE +# else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE floatToString -# endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE +# endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE (static_cast(_multipoint[line_nr]._value), _nrDecimals, true) : EMPTY_STRING, @@ -324,13 +329,14 @@ void P002_data_struct::webformLoad(struct EventStruct *event) ++line_nr; } - # if FEATURE_CHART_JS + # if FEATURE_CHART_JS webformLoad_multipointCurve(event); - # endif // if FEATURE_CHART_JS -# endif // ifndef LIMIT_BUILD_SIZE + # endif // if FEATURE_CHART_JS +# endif // ifndef LIMIT_BUILD_SIZE } -# if FEATURE_PLUGIN_STATS +# if FEATURE_PLUGIN_STATS + bool P002_data_struct::webformLoad_show_stats(struct EventStruct *event) { bool somethingAdded = false; @@ -365,103 +371,111 @@ bool P002_data_struct::webformLoad_show_stats(struct EventStruct *event) return somethingAdded; } -# endif // if FEATURE_PLUGIN_STATS +# endif // if FEATURE_PLUGIN_STATS -# ifdef ESP32 -# if FEATURE_CHART_JS +# ifdef ESP32 +# if FEATURE_CHART_JS + void P002_data_struct::webformLoad_calibrationCurve(struct EventStruct *event) { if (!hasADC_factory_calibration()) { return; } - addRowLabel(F("Calibration Curve")); + addRowColspan(2); const int valueCount = 33; int xAxisValues[valueCount]; getChartRange(event, xAxisValues, valueCount, true); - String axisOptions; { ChartJS_options_scales scales; scales.add({ F("x"), F("ADC Value") }); scales.add({ F("y"), F("Input Voltage (mV)") }); - axisOptions = scales.toString(); - } - add_ChartJS_chart_header( - F("line"), - F("fact_cal"), - { F("Factory Calibration per Attenuation") }, - 500, - 500, - axisOptions); - add_ChartJS_chart_labels( - valueCount, - xAxisValues); + auto chart = add_ChartJS_chart_header( + F("line"), + F("fact_cal"), + { F("Factory Calibration per Attenuation") }, + scales); - const __FlashStringHelper *colors[] = { F("#A52422"), F("#BEA57D"), F("#0F4C5C"), F("#A4BAB7") }; + if (chart) { + auto data = chart->createChild(F("data")); - size_t current_attenuation = getAttenuation(event); + if (data) { + add_ChartJS_chart_labels( + *data, + valueCount, + xAxisValues); - if (current_attenuation >= P002_ADC_ATTEN_MAX) { -# if ESP_IDF_VERSION_MAJOR >= 5 - current_attenuation = ADC_ATTEN_DB_12; -# else // if ESP_IDF_VERSION_MAJOR >= 5 - current_attenuation = ADC_ATTEN_DB_11; -# endif // if ESP_IDF_VERSION_MAJOR >= 5 - } + const __FlashStringHelper *colors[] = { F("#A52422"), F("#BEA57D"), F("#0F4C5C"), F("#A4BAB7") }; - for (size_t att = 0; att < P002_ADC_ATTEN_MAX; ++att) - { - float values[valueCount]; + size_t current_attenuation = getAttenuation(event); - for (int i = 0; i < valueCount; ++i) { - values[i] = applyADCFactoryCalibration(xAxisValues[i], static_cast(att)); + if (current_attenuation >= P002_ADC_ATTEN_MAX) { +# if ESP_IDF_VERSION_MAJOR >= 5 + current_attenuation = ADC_ATTEN_DB_12; +# else // if ESP_IDF_VERSION_MAJOR >= 5 + current_attenuation = ADC_ATTEN_DB_11; +# endif // if ESP_IDF_VERSION_MAJOR >= 5 + } + { + auto datasets = data->createChildArray(F("datasets")); + + if (datasets) { + for (size_t att = 0; att < P002_ADC_ATTEN_MAX; ++att) + { + float values[valueCount]; + + for (int i = 0; i < valueCount; ++i) { + values[i] = applyADCFactoryCalibration(xAxisValues[i], static_cast(att)); + } + + ChartJS_dataset_config config( + AttenuationToString(static_cast(att)), + colors[att]); + config.hidden = att != current_attenuation; + + add_ChartJS_dataset( + *datasets, + config, + values, + valueCount, + Cache.getTaskDeviceValueDecimals(event->TaskIndex, 0)); + } + } + } + } } - - ChartJS_dataset_config config( - AttenuationToString(static_cast(att)), - colors[att]); - config.hidden = att != current_attenuation; - - if (att != 0) { - addHtml(','); - } - - add_ChartJS_dataset( - config, - values, - valueCount, - Cache.getTaskDeviceValueDecimals(event->TaskIndex, 0)); } - add_ChartJS_chart_footer(); + addHtml(F("")); } -# endif // if FEATURE_CHART_JS -# endif // ifdef ESP32 +# endif // if FEATURE_CHART_JS +# endif // ifdef ESP32 + +# if FEATURE_CHART_JS -# if FEATURE_CHART_JS const __FlashStringHelper * P002_data_struct::getChartXaxisLabel(struct EventStruct *event) { - # ifdef ESP32 + # ifdef ESP32 if (useFactoryCalibration(event)) { // reading in mVolt, not ADC return F("Input Voltage (mV)"); } - # endif // ifdef ESP32 + # endif // ifdef ESP32 return F("ADC Value"); } -# endif // if FEATURE_CHART_JS +# endif // if FEATURE_CHART_JS void P002_data_struct::getInputRange(struct EventStruct *event, int& minInputValue, int& maxInputValue, bool ignoreCalibration) { minInputValue = 0; maxInputValue = MAX_ADC_VALUE; -# ifdef ESP32 +# ifdef ESP32 if (useFactoryCalibration(event) && !ignoreCalibration) { // reading in mVolt, not ADC @@ -470,10 +484,10 @@ void P002_data_struct::getInputRange(struct EventStruct *event, int& minInputVal minInputValue = getADC_factory_calibrated_min(attenuation); maxInputValue = getADC_factory_calibrated_max(attenuation); } -# endif // ifdef ESP32 +# endif // ifdef ESP32 } -# if FEATURE_CHART_JS +# if FEATURE_CHART_JS void P002_data_struct::getChartRange(struct EventStruct *event, int values[], int count, bool ignoreCalibration) { @@ -490,57 +504,65 @@ void P002_data_struct::getChartRange(struct EventStruct *event, int values[], in void P002_data_struct::webformLoad_2pt_calibrationCurve(struct EventStruct *event) { - addRowLabel(F("Two Point Calibration")); + addRowColspan(2); const int valueCount = 33; int xAxisValues[valueCount]; getChartRange(event, xAxisValues, valueCount); - String axisOptions; + ChartJS_options_scales scales; { - ChartJS_options_scales scales; scales.add({ F("x"), getChartXaxisLabel(event) }); scales.add({ F("y"), F("Calibrated Output") }); - axisOptions = scales.toString(); } - - add_ChartJS_chart_header( - F("line"), - F("twoPointCurve"), - { F("Two Point Calibration Curve") }, - 500, - 500, - axisOptions); - - add_ChartJS_chart_labels( - valueCount, - xAxisValues); - { - float values[valueCount]; + auto chart = add_ChartJS_chart_header( + F("line"), + F("twoPointCurve"), + { F("Two Point Calibration Curve") }, + scales); - for (int i = 0; i < valueCount; ++i) { - values[i] = P002_data_struct::applyCalibration(event, xAxisValues[i]); + if (chart) { + auto data = chart->createChild(F("data")); + + if (data) { + add_ChartJS_chart_labels( + *data, + valueCount, + xAxisValues); + + { + float values[valueCount]; + + for (int i = 0; i < valueCount; ++i) { + values[i] = P002_data_struct::applyCalibration(event, xAxisValues[i]); + } + + auto datasets = data->createChildArray(F("datasets")); + + if (datasets) { + const ChartJS_dataset_config config( + F("2 Point Calibration"), + F("rgb(255, 99, 132)")); + + add_ChartJS_dataset( + *datasets, + config, + values, + valueCount, + Cache.getTaskDeviceValueDecimals(event->TaskIndex, 0)); + } + } + } } - - const ChartJS_dataset_config config( - F("2 Point Calibration"), - F("rgb(255, 99, 132)")); - - - add_ChartJS_dataset( - config, - values, - valueCount, - Cache.getTaskDeviceValueDecimals(event->TaskIndex, 0)); } - add_ChartJS_chart_footer(); + addHtml(F("")); } -# endif // if FEATURE_CHART_JS +# endif // if FEATURE_CHART_JS void P002_data_struct::formatADC_statistics(const __FlashStringHelper *label, int raw, bool includeOutputValue) const { @@ -559,28 +581,29 @@ String P002_data_struct::formatADC_statistics_to_str( float_value = raw; -# ifdef ESP32 +# ifdef ESP32 if (_useFactoryCalibration) { float_value = applyADCFactoryCalibration(raw, _attenuation); - res = strformat( + res = strformat( F("%s [mV] ≙ %d [ADC]"), toString(float_value, _nrDecimals).c_str(), raw); } else { res += raw; } -#else +# else // ifdef ESP32 res += raw; -# endif // ifdef ESP32 +# endif // ifdef ESP32 if (includeOutputValue) { res += F(" → "); float_value = applyCalibration(float_value); -# ifndef LIMIT_BUILD_SIZE +# ifndef LIMIT_BUILD_SIZE - switch (_sampleMode) { + switch (_sampleMode) + { case P002_USE_OVERSAMPLING: float_value = applyMultiPointInterpolation(float_value); break; @@ -595,7 +618,7 @@ String P002_data_struct::formatADC_statistics_to_str( break; } } -# endif // ifndef LIMIT_BUILD_SIZE +# endif // ifndef LIMIT_BUILD_SIZE res += toString(float_value, _nrDecimals); } @@ -606,16 +629,17 @@ void P002_data_struct::format_2point_calib_statistics(const __FlashStringHelper { addRowLabel(label); addHtmlInt(raw); - # ifdef ESP32 + # ifdef ESP32 addUnit(_useFactoryCalibration ? F("mV") : F("raw")); - # else // ifdef ESP32 + # else // ifdef ESP32 addUnit(F("raw")); - # endif // ifdef ESP32 + # endif // ifdef ESP32 html_add_estimate_symbol(); addHtmlFloat(float_value, _nrDecimals); } -# ifdef ESP32 +# ifdef ESP32 + const __FlashStringHelper * P002_data_struct::AttenuationToString(adc_atten_t attenuation) { const __FlashStringHelper *datalabels[] = { F("0 dB"), F("2.5 dB"), F("6 dB"), F("12 dB") }; @@ -630,164 +654,186 @@ adc_atten_t P002_data_struct::getAttenuation(struct EventStruct *event) { } P002_ATTENUATION = P002_ADC_11db; -# if ESP_IDF_VERSION_MAJOR >= 5 +# if ESP_IDF_VERSION_MAJOR >= 5 return ADC_ATTEN_DB_12; -# else // if ESP_IDF_VERSION_MAJOR >= 5 +# else // if ESP_IDF_VERSION_MAJOR >= 5 return ADC_ATTEN_DB_11; -# endif // if ESP_IDF_VERSION_MAJOR >= 5 +# endif // if ESP_IDF_VERSION_MAJOR >= 5 } -# endif // ifdef ESP32 +# endif // ifdef ESP32 + +# if FEATURE_CHART_JS -# if FEATURE_CHART_JS void P002_data_struct::webformLoad_multipointCurve(struct EventStruct *event) const { if (P002_MULTIPOINT_ENABLED) { const bool useBinning = P002_OVERSAMPLING == P002_USE_BINNING; - addRowLabel(useBinning ? F("Binning Curve") : F("Multipoint Curve")); - - String axisOptions; + addRowColspan(2); + ChartJS_options_scales scales; { - ChartJS_options_scales scales; scales.add({ F("x"), useBinning ? F("Bin Center Value") : F("Input") }); scales.add({ F("y"), useBinning ? F("Bin Output Value") : F("Output") }); - axisOptions = scales.toString(); } - - add_ChartJS_chart_header( - useBinning ? F("bar") : F("line"), - F("mpcurve"), - { useBinning ? F("Bin Values") : F("Multipoint Curve") }, - 500, - 500, - axisOptions); - - // Add labels - addHtml(F("\"labels\":[")); - - for (size_t i = 0; i < _multipoint.size(); ++i) { - if (i != 0) { - addHtml(','); - } - addHtmlFloat(_multipoint[i]._adc, _nrDecimals); - } - addHtml(F("],\n\"datasets\":[")); - - add_ChartJS_dataset_header( { - useBinning ? F("Bins") : F("Multipoint Values"), - F("rgb(255, 99, 132)") }); + auto chart = add_ChartJS_chart_header( + useBinning ? F("bar") : F("line"), + F("mpcurve"), + { useBinning ? F("Bin Values") : F("Multipoint Curve") }, + scales); - for (size_t i = 0; i < _multipoint.size(); ++i) { - if (i != 0) { - addHtml(','); + if (chart) { + auto data = chart->createChild(F("data")); + + if (data) { + { + auto labels = data->createChildArray(F("labels")); + + if (labels) { + for (size_t i = 0; i < _multipoint.size(); ++i) { + labels->write({ EMPTY_STRING, _multipoint[i]._adc, _nrDecimals }); + } + } + } + + { + auto datasets = data->createChildArray(F("datasets")); + + if (datasets) { + auto dataset = datasets->createChild(); + + if (dataset) { + + auto data = add_ChartJS_dataset_header( + *dataset, + { + useBinning ? F("Bins") : F("Multipoint Values"), + F("rgb(255, 99, 132)") }); + + if (data) { + for (size_t i = 0; i < _multipoint.size(); ++i) { + const float value = _multipoint[i]._value; + + data->write({ EMPTY_STRING, value, _nrDecimals }); + } + } + } + } + } + } } - addHtmlFloat(_multipoint[i]._value, _nrDecimals); } - add_ChartJS_dataset_footer(); - add_ChartJS_chart_footer(); + addHtml(F("")); + if (!useBinning) { // Try to compute the expected mapping from ADC to multipoint values - addRowLabel(F("Input to Output Curve")); + addRowColspan(2); const int valueCount = 33; int xAxisValues[valueCount]; getChartRange(event, xAxisValues, valueCount); - String axisOptions; + ChartJS_options_scales scales; { - ChartJS_options_scales scales; scales.add({ F("x"), getChartXaxisLabel(event) }); scales.add({ F("y"), F("Output") }); - axisOptions = scales.toString(); } - add_ChartJS_chart_header( - F("line"), - F("mpCurveSimulated"), - { F("Simulated Input to Output Curve") }, - 500, - 500, - axisOptions); - - add_ChartJS_chart_labels( - valueCount, - xAxisValues); - - const __FlashStringHelper *label = F("Multipoint"); - const __FlashStringHelper *color = F("rgb(255, 99, 132)"); - - for (int step = 0; step < 3; ++step) { - float values[valueCount]; - bool use2PointCalib = false; - bool useMultiPoint = false; + auto chart = add_ChartJS_chart_header( + F("line"), + F("mpCurveSimulated"), + { F("Simulated Input to Output Curve") }, + scales); - switch (step) { - case 0: - useMultiPoint = true; - break; - case 1: - label = F("2 Point Calibration & Multipoint"); - color = F("rgb(54, 162, 235)"); - use2PointCalib = true; - useMultiPoint = true; - break; - case 2: - label = F("2 Point Calibration"); - color = F("rgb(153, 102, 255)"); - use2PointCalib = true; - break; - } + if (chart) { + auto data = chart->createChild(F("data")); - bool hidden = !((use2PointCalib == _use2pointCalibration) && - useMultiPoint); + if (data) { + add_ChartJS_chart_labels( + *data, + valueCount, + xAxisValues); - for (int i = 0; i < valueCount; ++i) { - values[i] = xAxisValues[i]; + const __FlashStringHelper *label = F("Multipoint"); + const __FlashStringHelper *color = F("rgb(255, 99, 132)"); - if (use2PointCalib) { - values[i] = P002_data_struct::applyCalibration(event, values[i], true); - } + auto datasets = data->createChildArray(F("datasets")); - if (useMultiPoint) { - values[i] = applyMultiPointInterpolation(values[i], true); + if (datasets) { + for (int step = 0; step < 3; ++step) + { + float values[valueCount]; + bool use2PointCalib = false; + bool useMultiPoint = false; + + switch (step) + { + case 0: + useMultiPoint = true; + break; + case 1: + label = F("2 Point Calibration & Multipoint"); + color = F("rgb(54, 162, 235)"); + use2PointCalib = true; + useMultiPoint = true; + break; + case 2: + label = F("2 Point Calibration"); + color = F("rgb(153, 102, 255)"); + use2PointCalib = true; + break; + } + + bool hidden = !((use2PointCalib == _use2pointCalibration) && + useMultiPoint); + + for (int i = 0; i < valueCount; ++i) { + values[i] = xAxisValues[i]; + + if (use2PointCalib) { + values[i] = P002_data_struct::applyCalibration(event, values[i], true); + } + + if (useMultiPoint) { + values[i] = applyMultiPointInterpolation(values[i], true); + } + } + + ChartJS_dataset_config config( + label, + color); + config.hidden = hidden; + + add_ChartJS_dataset( + *datasets, + config, + values, + valueCount, + Cache.getTaskDeviceValueDecimals(event->TaskIndex, 0)); + } + } } } - - ChartJS_dataset_config config( - label, - color); - config.hidden = hidden; - - if (step != 0) { - addHtml(','); - } - - add_ChartJS_dataset( - config, - values, - valueCount, - Cache.getTaskDeviceValueDecimals(event->TaskIndex, 0)); } - add_ChartJS_chart_footer(); + addHtml(F("")); } } } -# endif // if FEATURE_CHART_JS +# endif // if FEATURE_CHART_JS String P002_data_struct::webformSave(struct EventStruct *event) { P002_OVERSAMPLING = getFormItemInt(F("oversampling"), 0); // Set a default for LIMIT_BUILD_SIZE P002_CALIBRATION_ENABLED = isFormItemChecked(F("cal")); - # ifdef ESP32 + # ifdef ESP32 P002_APPLY_FACTORY_CALIB = isFormItemChecked(F("fac_cal")); P002_ATTENUATION = getFormItemInt(F("attn")); - # endif // ifdef ESP32 + # endif // ifdef ESP32 // Map the input "point" values to the nearest int. setTwoPointCalibration( @@ -797,7 +843,7 @@ String P002_data_struct::webformSave(struct EventStruct *event) getFormItemFloat(F("out1")), getFormItemFloat(F("out2"))); -# ifndef LIMIT_BUILD_SIZE +# ifndef LIMIT_BUILD_SIZE P002_MULTIPOINT_ENABLED = isFormItemChecked(F("multi_en")); P002_NR_MULTIPOINT_ITEMS = getFormItemInt(F("nr_mp")); @@ -828,9 +874,9 @@ String P002_data_struct::webformSave(struct EventStruct *event) } return SaveCustomTaskSettings(event->TaskIndex, lines, nr_lines, 0); -# else // ifndef LIMIT_BUILD_SIZE +# else // ifndef LIMIT_BUILD_SIZE return EMPTY_STRING; -# endif // ifndef LIMIT_BUILD_SIZE +# endif // ifndef LIMIT_BUILD_SIZE } void P002_data_struct::takeSample() @@ -838,23 +884,24 @@ void P002_data_struct::takeSample() if (_sampleMode == P002_USE_CURENT_SAMPLE) { return; } const int raw = analog_read(); -# if FEATURE_PLUGIN_STATS +# if FEATURE_PLUGIN_STATS PluginStats *stats = getPluginStats(0); if (stats != nullptr) { stats->trackPeak(raw); } -# endif // if FEATURE_PLUGIN_STATS +# endif // if FEATURE_PLUGIN_STATS - switch (_sampleMode) { + switch (_sampleMode) + { case P002_USE_OVERSAMPLING: addOversamplingValue(raw); break; -# ifndef LIMIT_BUILD_SIZE +# ifndef LIMIT_BUILD_SIZE case P002_USE_BINNING: addBinningValue(raw); break; -# endif // ifndef LIMIT_BUILD_SIZE +# endif // ifndef LIMIT_BUILD_SIZE } } @@ -863,7 +910,8 @@ bool P002_data_struct::getValue(float& float_value, { bool mustTakeSample = false; - switch (_sampleMode) { + switch (_sampleMode) + { case P002_USE_OVERSAMPLING: if (getOversamplingValue(float_value, raw_value)) { @@ -871,7 +919,7 @@ bool P002_data_struct::getValue(float& float_value, } mustTakeSample = true; break; -# ifndef LIMIT_BUILD_SIZE +# ifndef LIMIT_BUILD_SIZE case P002_USE_BINNING: if (getBinnedValue(float_value, raw_value)) { @@ -879,7 +927,7 @@ bool P002_data_struct::getValue(float& float_value, } mustTakeSample = true; break; -# endif // ifndef LIMIT_BUILD_SIZE +# endif // ifndef LIMIT_BUILD_SIZE case P002_USE_CURENT_SAMPLE: mustTakeSample = true; break; @@ -890,27 +938,28 @@ bool P002_data_struct::getValue(float& float_value, } raw_value = analog_read(); -# if FEATURE_PLUGIN_STATS +# if FEATURE_PLUGIN_STATS PluginStats *stats = getPluginStats(0); if (stats != nullptr) { stats->trackPeak(raw_value); } -# endif // if FEATURE_PLUGIN_STATS +# endif // if FEATURE_PLUGIN_STATS float_value = raw_value; - # ifdef ESP32 + # ifdef ESP32 if (_useFactoryCalibration) { float_value = applyADCFactoryCalibration(raw_value, _attenuation); } - # endif // ifdef ESP32 + # endif // ifdef ESP32 float_value = applyCalibration(float_value); -# ifndef LIMIT_BUILD_SIZE +# ifndef LIMIT_BUILD_SIZE - switch (_sampleMode) { + switch (_sampleMode) + { case P002_USE_OVERSAMPLING: float_value = applyMultiPointInterpolation(float_value); break; @@ -925,16 +974,17 @@ bool P002_data_struct::getValue(float& float_value, break; } } -# endif // ifndef LIMIT_BUILD_SIZE +# endif // ifndef LIMIT_BUILD_SIZE return true; } void P002_data_struct::reset() { -# ifndef LIMIT_BUILD_SIZE +# ifndef LIMIT_BUILD_SIZE - switch (_sampleMode) { + switch (_sampleMode) + { case P002_USE_OVERSAMPLING: resetOversampling(); break; @@ -947,9 +997,9 @@ void P002_data_struct::reset() break; } } -# else // ifndef LIMIT_BUILD_SIZE +# else // ifndef LIMIT_BUILD_SIZE resetOversampling(); -# endif // ifndef LIMIT_BUILD_SIZE +# endif // ifndef LIMIT_BUILD_SIZE } uint32_t P002_data_struct::getOversamplingCount() const @@ -957,37 +1007,34 @@ uint32_t P002_data_struct::getOversamplingCount() const return OverSampling.getCount(); } -void P002_data_struct::resetOversampling() { - OverSampling.reset(); -} +void P002_data_struct::resetOversampling() { OverSampling.reset(); } -void P002_data_struct::addOversamplingValue(int currentValue) { - OverSampling.add(currentValue); -} +void P002_data_struct::addOversamplingValue(int currentValue) { OverSampling.add(currentValue); } bool P002_data_struct::getOversamplingValue(float& float_value, int& raw_value) const { if (OverSampling.peek(float_value)) { raw_value = static_cast(float_value); -# ifdef ESP32 +# ifdef ESP32 if (_useFactoryCalibration) { float_value = applyADCFactoryCalibration(float_value, _attenuation); } -# endif // ifdef ESP32 +# endif // ifdef ESP32 // We counted the raw oversampling values, so now we need to apply the calibration and multi-point processing float_value = applyCalibration(float_value); -# ifndef LIMIT_BUILD_SIZE +# ifndef LIMIT_BUILD_SIZE float_value = applyMultiPointInterpolation(float_value); -# endif // ifndef LIMIT_BUILD_SIZE +# endif // ifndef LIMIT_BUILD_SIZE return true; } return false; } -# ifndef LIMIT_BUILD_SIZE +# ifndef LIMIT_BUILD_SIZE + int P002_data_struct::getBinIndex(float currentValue) const { const size_t mp_size = _multipoint.size(); @@ -1020,12 +1067,12 @@ int P002_data_struct::computeADC_to_bin(const int& currentValue) const // First apply calibration, then find the bin index float calibrated_value = static_cast(currentValue); -# ifdef ESP32 +# ifdef ESP32 if (_useFactoryCalibration) { calibrated_value = applyADCFactoryCalibration(calibrated_value, _attenuation); } -# endif // ifdef ESP32 +# endif // ifdef ESP32 calibrated_value = applyCalibration(calibrated_value); @@ -1076,7 +1123,7 @@ bool P002_data_struct::getBinnedValue(float& float_value, int& raw_value) const raw_value = _multipoint[i]._adc; } } - # ifndef BUILD_NO_DEBUG + # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { addLogMove(LOG_LEVEL_DEBUG, @@ -1085,12 +1132,12 @@ bool P002_data_struct::getBinnedValue(float& float_value, int& raw_value) const float_value, raw_value)); } - # endif // ifndef BUILD_NO_DEBUG + # endif // ifndef BUILD_NO_DEBUG return highest_bin_count != 0; } -# endif // ifndef LIMIT_BUILD_SIZE +# endif // ifndef LIMIT_BUILD_SIZE float P002_data_struct::applyCalibration(struct EventStruct *event, float float_value, bool force) { if (force || P002_CALIBRATION_ENABLED) @@ -1106,25 +1153,25 @@ float P002_data_struct::applyCalibration(struct EventStruct *event, float float_ float P002_data_struct::getCurrentValue(struct EventStruct *event, int& raw_value) { - # ifdef ESP8266 + # ifdef ESP8266 const int pin = A0; - # endif // ifdef ESP8266 - # ifdef ESP32 + # endif // ifdef ESP8266 + # ifdef ESP32 const int pin = CONFIG_PIN1; auto att = getAttenuation(event); analogSetPinAttenuation(pin, static_cast(att)); - # endif // ifdef ESP32 + # endif // ifdef ESP32 raw_value = espeasy_analogRead(pin); - # ifdef ESP32 + # ifdef ESP32 if (useFactoryCalibration(event)) { return applyADCFactoryCalibration(raw_value, att); } - # endif // ifdef ESP32 + # endif // ifdef ESP32 return raw_value; } @@ -1140,7 +1187,8 @@ float P002_data_struct::applyCalibration(float float_value) const _calib_out2); } -# ifdef ESP32 +# ifdef ESP32 + bool P002_data_struct::useFactoryCalibration(struct EventStruct *event) { if (P002_APPLY_FACTORY_CALIB) { const int adc_num = getADC_num_for_gpio(CONFIG_PIN1); @@ -1152,9 +1200,10 @@ bool P002_data_struct::useFactoryCalibration(struct EventStruct *event) { return false; } -# endif // ifdef ESP32 +# endif // ifdef ESP32 + +# ifndef LIMIT_BUILD_SIZE -# ifndef LIMIT_BUILD_SIZE float P002_data_struct::applyMultiPointInterpolation(float float_value, bool force) const { if (!_useMultipoint && !force) { return float_value; } @@ -1226,7 +1275,7 @@ float P002_data_struct::applyMultiPointInterpolation(float float_value, bool for return float_value; } -# endif // ifndef LIMIT_BUILD_SIZE +# endif // ifndef LIMIT_BUILD_SIZE void P002_data_struct::setTwoPointCalibration( struct EventStruct *event, @@ -1291,17 +1340,17 @@ bool P002_data_struct::plugin_set_config(struct EventStruct *event, } int P002_data_struct::analog_read() const { -#ifdef ESP32 +# ifdef ESP32 int channel{}; const int adc = getADC_num_for_gpio(_pin_analogRead, channel); if ((adc == 1) || (adc == 2)) { analogSetPinAttenuation(_pin_analogRead, static_cast(_attenuation)); } -#endif +# endif // ifdef ESP32 return espeasy_analogRead(_pin_analogRead); } -#endif +# endif // if SOC_ADC_SUPPORTED || defined(ESP8266) #endif // ifdef USES_P002 diff --git a/src/src/PluginStructs/P020_data_struct.cpp b/src/src/PluginStructs/P020_data_struct.cpp index 647e3b6b0..d30b6b708 100644 --- a/src/src/PluginStructs/P020_data_struct.cpp +++ b/src/src/PluginStructs/P020_data_struct.cpp @@ -3,7 +3,7 @@ #ifdef USES_P020 # include "../ESPEasyCore/Serial.h" -# include "../ESPEasyCore/ESPEasyNetwork.h" +# include "../../ESPEasy/net/ESPEasyNetwork.h" # include "../Globals/EventQueue.h" @@ -71,7 +71,7 @@ void P020_Task::startServer(uint16_t portnumber) { gatewayPort = portnumber; ser2netServer = new (std::nothrow) WiFiServer(portnumber); - if ((nullptr != ser2netServer) && NetworkConnected()) { + if ((nullptr != ser2netServer) && ESPEasy::net::NetworkConnected(true)) { ser2netServer->begin(); if (serverActive(ser2netServer)) { @@ -90,7 +90,7 @@ void P020_Task::startServer(uint16_t portnumber) { } void P020_Task::checkServer() { - if ((nullptr != ser2netServer) && !serverActive(ser2netServer) && NetworkConnected()) { + if ((nullptr != ser2netServer) && !serverActive(ser2netServer) && ESPEasy::net::NetworkConnected()) { ser2netServer->close(); ser2netServer->begin(); @@ -100,7 +100,7 @@ void P020_Task::checkServer() { } # if P020_USE_PROTOCOL - if ((nullptr != ser2netUdp) && !_udpInit && NetworkConnected()) { + if ((nullptr != ser2netUdp) && !_udpInit && ESPEasy::net::NetworkConnected()) { _udpInit = true; // Init only once if (ser2netUdp->begin(_udpport) == 0) { diff --git a/src/src/PluginStructs/P021_data_struct.h b/src/src/PluginStructs/P021_data_struct.h index 1a8bde1fe..a2e705087 100644 --- a/src/src/PluginStructs/P021_data_struct.h +++ b/src/src/PluginStructs/P021_data_struct.h @@ -87,6 +87,26 @@ # define P021_FORCE_TIME PCONFIG_ULONG(3) # define P021_GUID_FORCE_TIME "f11" +// Operation modes for the control algorithm +enum class P021_opmode : uint8_t +{ + P021_OPMODE_CLASSIC, // Original, stateless control + P021_OPMODE_OFF, // Output is fully shut down, no forced curculation + P021_OPMODE_STANDBY, // Output is only switched on for forced maintenance runs + P021_OPMODE_ON, // Output is always switched on + P021_OPMODE_TEMP, // Control algorithm based on temperature only + P021_OPMODE_REMOTE // Both temperature and remote command can switch on Output +}; + +// Control state for the control algorithm +enum class P021_control_state : uint8_t +{ + P021_STATE_IDLE, // Output is inactive + P021_STATE_ACTIVE, // Output is active due to level control (based on input value) + P021_STATE_EXTEND, // Output is active due to minimum duration + P021_STATE_FORCE // Output is forced active due to maximum inactive duration is exceeded +}; + // Positions in userVar array for the output values of this plugin // For now we only advertise output & state. Autosave bookkeeping is hidden and used as static storage # define P021_VALUE_OUTPUT 0 // Switch output, logical state [inactive/active] diff --git a/src/src/PluginStructs/P023_data_struct.cpp b/src/src/PluginStructs/P023_data_struct.cpp index 715dcd364..451e9e4c1 100644 --- a/src/src/PluginStructs/P023_data_struct.cpp +++ b/src/src/PluginStructs/P023_data_struct.cpp @@ -435,7 +435,6 @@ void P023_data_struct::sendStrXY(const char *string, int X, int Y) { for (i = 0; i < char_width && currentPixels < maxPixels; ++i,++currentPixels) { // Prevent display overflow on the pixel-level sendChar((i == 0 || i > 5) ? 0x00 : pgm_read_byte(baddr + i - 1)); } - currentPixels += char_width; string++; } } diff --git a/src/src/PluginStructs/P026_data_struct.cpp b/src/src/PluginStructs/P026_data_struct.cpp index da6576ee1..6e491d350 100644 --- a/src/src/PluginStructs/P026_data_struct.cpp +++ b/src/src/PluginStructs/P026_data_struct.cpp @@ -3,8 +3,10 @@ #ifdef USES_P026 # include "../DataStructs/ESPEasy_packed_raw_data.h" -# include "../ESPEasyCore/ESPEasyNetwork.h" -# include "../Globals/ESPEasyWiFiEvent.h" +# include "../../ESPEasy/net/ESPEasyNetwork.h" +# include "../../ESPEasy/net/wifi/ESPEasyWifi.h" +# include "../../ESPEasy/net/Globals/ESPEasyWiFiEvent.h" +# include "../Helpers/ESPEasy_UnitOfMeasure.h" # include "../Helpers/Memory.h" # include "../Helpers/Hardware_temperature_sensor.h" # ifdef ESP32 @@ -128,13 +130,17 @@ float P026_get_value(uint8_t type) case P026_VALUETYPE_ip2: case P026_VALUETYPE_ip3: case P026_VALUETYPE_ip4: - res = NetworkLocalIP()[type - P026_VALUETYPE_ip1]; + res = ESPEasy::net::NetworkLocalIP()[type - P026_VALUETYPE_ip1]; break; case P026_VALUETYPE_web: res = timePassedSince(lastWeb) / 1000.0f; break; // respond in seconds case P026_VALUETYPE_freestack: res = getCurrentFreeStack(); break; - case P026_VALUETYPE_txpwr: res = WiFiEventData.wifi_TX_pwr; break; +# if FEATURE_SET_WIFI_TX_PWR + case P026_VALUETYPE_txpwr: res = ESPEasy::net::wifi::GetWiFiTXpower(); break; +#else + case P026_VALUETYPE_txpwr: res = 0; break; +#endif # ifdef USE_SECOND_HEAP case P026_VALUETYPE_free2ndheap: res = FreeMem2ndHeap(); @@ -172,7 +178,7 @@ Sensor_VType P026_get_valueVType(uint8_t type) { case P026_VALUETYPE_ip4: return Sensor_VType::SENSOR_TYPE_NONE; case P026_VALUETYPE_web: return Sensor_VType::SENSOR_TYPE_DURATION; case P026_VALUETYPE_freestack: return Sensor_VType::SENSOR_TYPE_DATA_SIZE; - case P026_VALUETYPE_txpwr: return Sensor_VType::SENSOR_TYPE_POWER_FACT_ONLY; + case P026_VALUETYPE_txpwr: return Sensor_VType::SENSOR_TYPE_SOUND_PRESSURE; # ifdef USE_SECOND_HEAP case P026_VALUETYPE_free2ndheap: return Sensor_VType::SENSOR_TYPE_DATA_SIZE; # endif // ifdef USE_SECOND_HEAP diff --git a/src/src/PluginStructs/P036_data_struct.cpp b/src/src/PluginStructs/P036_data_struct.cpp index cdc551b78..ed50b507d 100644 --- a/src/src/PluginStructs/P036_data_struct.cpp +++ b/src/src/PluginStructs/P036_data_struct.cpp @@ -2,7 +2,7 @@ #ifdef USES_P036 -# include "../ESPEasyCore/ESPEasyNetwork.h" +# include "../../ESPEasy/net/ESPEasyNetwork.h" # include "../Globals/RTC.h" # include "../Helpers/ESPEasy_Storage.h" # include "../Helpers/Memory.h" @@ -295,7 +295,7 @@ bool P036_data_struct::init(taskIndex_t taskIndex, prepare_pagescrolling(ScrollSpeed, NrLines); } - bRunning = NetworkConnected(); + bRunning = ESPEasy::net::NetworkConnected(); return isInitialized(); } @@ -785,7 +785,7 @@ String P036_data_struct::create_display_header_text(eHeaderContent iHeaderConten switch (iHeaderContent) { case eHeaderContent::eSSID: - if (NetworkConnected()) { + if (ESPEasy::net::NetworkConnected()) { strHeader = WiFi.SSID(); use_newString_f = false; } @@ -1976,7 +1976,7 @@ bool P036_data_struct::display_wifibars() { return false; } - const bool connected = NetworkConnected(); + const bool connected = ESPEasy::net::NetworkConnected(); const int nbars_filled = (WiFi.RSSI() + 100) / 12; // all bars filled if RSSI better than -46dB const int newState = connected ? nbars_filled : P36_WIFI_STATE_NOT_CONNECTED; @@ -2001,7 +2001,7 @@ bool P036_data_struct::display_wifibars() { display->fillRect(x, y, size_x, size_y); display->setColor(WHITE); - if (NetworkConnected()) { + if (ESPEasy::net::NetworkConnected()) { for (uint8_t ibar = 0; ibar < nbars; ++ibar) { const int16_t height = size_y * (ibar + 1) / nbars; const int16_t xpos = x + ibar * width; @@ -2220,7 +2220,7 @@ void P036_data_struct::P036_DisplayPage(struct EventStruct *event) const bool bScrollWithoutWifi = bitRead(PCONFIG_LONG(0), 24); // Bit 24 const bool bScrollLines = bitRead(PCONFIG_LONG(0), 17); // Bit 17 - bRunning = NetworkConnected() || bScrollWithoutWifi; + bRunning = ESPEasy::net::NetworkConnected() || bScrollWithoutWifi; bLineScrollEnabled = ((bScrollLines # if P036_ENABLE_TICKER || bUseTicker diff --git a/src/src/PluginStructs/P037_data_struct.h b/src/src/PluginStructs/P037_data_struct.h index 7c7bd6b9c..974abca08 100644 --- a/src/src/PluginStructs/P037_data_struct.h +++ b/src/src/PluginStructs/P037_data_struct.h @@ -60,7 +60,7 @@ // #endif # endif // if (defined(LIMIT_BUILD_SIZE) && !defined(P037_OVERRIDE)) || defined(P037_LIMIT_BUILD_SIZE) -# ifdef PLUGIN_DISPLAY_COLLECTION +# if defined(PLUGIN_DISPLAY_A_COLLECTION) || defined(PLUGIN_DISPLAY_B_COLLECTION) # if P037_FILTER_SUPPORT # undef P037_FILTER_SUPPORT # define P037_FILTER_SUPPORT 0 @@ -69,7 +69,7 @@ # undef P037_REPLACE_BY_COMMA_SUPPORT # define P037_REPLACE_BY_COMMA_SUPPORT 0 # endif // if P037_REPLACE_BY_COMMA_SUPPORT -# endif // ifdef PLUGIN_DISPLAY_COLLECTION +# endif // if defined(PLUGIN_DISPLAY_A_COLLECTION) || defined(PLUGIN_DISPLAY_B_COLLECTION) # endif // ifndef PLUGIN_BUILD_MAX_ESP32 # define P037_MAX_MAPPINGS 25 diff --git a/src/src/PluginStructs/P044_data_struct.cpp b/src/src/PluginStructs/P044_data_struct.cpp index f61fe04bb..f98649734 100644 --- a/src/src/PluginStructs/P044_data_struct.cpp +++ b/src/src/PluginStructs/P044_data_struct.cpp @@ -3,7 +3,7 @@ #ifdef USES_P044_ORG # include "../ESPEasyCore/Serial.h" -# include "../ESPEasyCore/ESPEasyNetwork.h" +# include "../../ESPEasy/net/ESPEasyNetwork.h" # include "../Globals/EventQueue.h" @@ -50,7 +50,7 @@ void P044_Task::startServer(uint16_t portnumber) { gatewayPort = portnumber; P1GatewayServer = new (std::nothrow) WiFiServer(portnumber); - if ((nullptr != P1GatewayServer) && NetworkConnected()) { + if ((nullptr != P1GatewayServer) && ESPEasy::net::NetworkConnected()) { P1GatewayServer->begin(); if (serverActive(P1GatewayServer)) { @@ -64,7 +64,7 @@ void P044_Task::startServer(uint16_t portnumber) { } void P044_Task::checkServer() { - if ((nullptr != P1GatewayServer) && !serverActive(P1GatewayServer) && NetworkConnected()) { + if ((nullptr != P1GatewayServer) && !serverActive(P1GatewayServer) && ESPEasy::net::NetworkConnected()) { P1GatewayServer->close(); P1GatewayServer->begin(); diff --git a/src/src/PluginStructs/P078_data_struct.cpp b/src/src/PluginStructs/P078_data_struct.cpp index 34f65d49e..4a1d513d7 100644 --- a/src/src/PluginStructs/P078_data_struct.cpp +++ b/src/src/PluginStructs/P078_data_struct.cpp @@ -4,6 +4,8 @@ #ifdef USES_P078 +# include "../Helpers/ESPEasy_UnitOfMeasure.h" + # include # include // Requires SDM library from Reaper7 - https://github.com/reaper7/SDM_Energy_Meter/ @@ -247,6 +249,39 @@ Sensor_VType Plugin_078_QueryVType(SDM_MODEL model, int choice) { # endif // if FEATURE_MQTT_DISCOVER +# if FEATURE_TASKVALUE_UNIT_OF_MEASURE +uint64_t Plugin_078_QueryUOMGroup(SDM_MODEL model, int choice) { + const int index = SDM_getRegisterDescriptionIndexForModel(model, choice); + + if (index >= 0) { + const uint8_t value_nr = static_cast(register_description_list[index].getUnitOfMeasure()); + const uint64_t vtypes[] = { + UOM_GROUP_PERC_HUM_BAT_MOIST, // THD + UOM_GROUP_VOLTAGE_CURRENT, // Voltage + UOM_GROUP_VOLTAGE_CURRENT, // Current + UOM_GROUP_POWER, // Power + UOM_GROUP_ENERGY_STORAGE, // Active energy + UOM_GROUP_REACTIVE_POWER, // Ah + UOM_GROUP_FREQUENCY, // Frequency + UOM_GROUP_WIND_DIRECTION, // Phase angle + UOM_GROUP_PERC_HUM_BAT_MOIST, // Power factor + UOM_GROUP_APPARENT_POWER, // Apparant power + UOM_GROUP_REACTIVE_POWER, // Reactive power + UOM_GROUP_APPARENT_POWER, // Apparent energy + UOM_GROUP_REACTIVE_POWER, // Reactive energy + UOM_GROUP_NONE, // Nature of load + }; + constexpr uint8_t valueCount = NR_ELEMENTS(vtypes); + + if (value_nr < valueCount) { + return vtypes[value_nr]; + } + } + return UOM_GROUP_ALL; +} + +# endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE + void SDM_loadOutputSelector(struct EventStruct *event, uint8_t pconfigIndex, uint8_t valuenr) { const SDM_MODEL model = static_cast(P078_MODEL); diff --git a/src/src/PluginStructs/P078_data_struct.h b/src/src/PluginStructs/P078_data_struct.h index b967213de..b237bda2c 100644 --- a/src/src/PluginStructs/P078_data_struct.h +++ b/src/src/PluginStructs/P078_data_struct.h @@ -146,8 +146,12 @@ String SDM_getValueNameForModel(SDM_MODEL model, int choice); # if FEATURE_MQTT_DISCOVER Sensor_VType Plugin_078_QueryVType(SDM_MODEL model, - int choice); + int choice); # endif // if FEATURE_MQTT_DISCOVER +# if FEATURE_TASKVALUE_UNIT_OF_MEASURE +uint64_t Plugin_078_QueryUOMGroup(SDM_MODEL model, + int choice); +# endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE struct SDM_RegisterReadQueueElement { SDM_RegisterReadQueueElement(taskIndex_t TaskIndex, taskVarIndex_t TaskVarIndex, uint16_t reg, uint8_t dev_id) diff --git a/src/src/PluginStructs/P082_data_struct.cpp b/src/src/PluginStructs/P082_data_struct.cpp index c51b682bf..a4bf7edab 100644 --- a/src/src/PluginStructs/P082_data_struct.cpp +++ b/src/src/PluginStructs/P082_data_struct.cpp @@ -8,9 +8,9 @@ # include # include - const __FlashStringHelper* Plugin_082_valuename(P082_query value_nr, bool displayString) { - switch (value_nr) { + switch (value_nr) + { case P082_query::P082_QUERY_LONG: return displayString ? F("Longitude") : F("long"); case P082_query::P082_QUERY_LAT: return displayString ? F("Latitude") : F("lat"); case P082_query::P082_QUERY_ALT: return displayString ? F("Altitude") : F("alt"); @@ -40,7 +40,8 @@ P082_query Plugin_082_from_valuename(const String& valuename) } const __FlashStringHelper* toString(P082_PowerMode mode) { - switch (mode) { + switch (mode) + { case P082_PowerMode::Max_Performance: return F("Max Performance"); case P082_PowerMode::Power_Save: return F("Power Save"); case P082_PowerMode::Eco: return F("ECO"); @@ -49,7 +50,8 @@ const __FlashStringHelper* toString(P082_PowerMode mode) { } const __FlashStringHelper* toString(P082_DynamicModel model) { - switch (model) { + switch (model) + { case P082_DynamicModel::Portable: return F("Portable"); case P082_DynamicModel::Stationary: return F("Stationary"); case P082_DynamicModel::Pedestrian: return F("Pedestrian"); @@ -89,7 +91,7 @@ void P082_software_pps::setSentenceType( const uint64_t endOfLine_received_usec = getMicros64() - bytesToUsec(bytesAvailableInSerialBuffer); const int64_t sentence_duration = timeDiff64(_cur_start_sentence_usec, endOfLine_received_usec); -// if (usecPassedSince(_cur_start_sentence_usec) < 1000000ll) { + // if (usecPassedSince(_cur_start_sentence_usec) < 1000000ll) { // Assume a NMEA sentence cannot be over 80 bytes // Apply some tolerance, thus check for duration to receive 120 bytes @@ -144,34 +146,43 @@ uint64_t P082_software_pps::bytesToUsec(uint32_t bytes) const return duration_usec; } -#ifndef BUILD_NO_DEBUG +# ifndef BUILD_NO_DEBUG + String P082_software_pps::getStats() const { String res; constexpr uint32_t nrelements = NR_ELEMENTS(_second_frac_in_usec); + for (size_t i = 0; i < nrelements; ++i) { uint32_t value{}; _second_frac_in_usec[i].peek(value); { - switch (i) { - case TinyGPSPlus::GPS_SENTENCE_GPGGA: res += F("GGA"); break; - case TinyGPSPlus::GPS_SENTENCE_GPRMC: res += F("RMC"); break; - case TinyGPSPlus::GPS_SENTENCE_GPGSA: res += F("GSA"); break; - case TinyGPSPlus::GPS_SENTENCE_GPGSV: res += F("GSV"); break; - case TinyGPSPlus::GPS_SENTENCE_GPGLL: res += F("GLL"); break; - case TinyGPSPlus::GPS_SENTENCE_GPTXT: res += F("TXT"); break; + switch (i) + { + case TinyGPSPlus::GPS_SENTENCE_GPGGA: res += F("GGA"); + break; + case TinyGPSPlus::GPS_SENTENCE_GPRMC: res += F("RMC"); + break; + case TinyGPSPlus::GPS_SENTENCE_GPGSA: res += F("GSA"); + break; + case TinyGPSPlus::GPS_SENTENCE_GPGSV: res += F("GSV"); + break; + case TinyGPSPlus::GPS_SENTENCE_GPGLL: res += F("GLL"); + break; + case TinyGPSPlus::GPS_SENTENCE_GPTXT: res += F("TXT"); + break; default: - res += F("---"); - break; + res += F("---"); + break; } res += strformat(F(": %06d (%d)
"), value, _second_frac_in_usec[i].getCount()); } } - return res; + return res; } -#endif +# endif // ifndef BUILD_NO_DEBUG P082_data_struct::P082_data_struct() : gps(nullptr), easySerial(nullptr) { @@ -297,7 +308,8 @@ bool P082_data_struct::loop() { const int c = easySerial->read(); if (c >= 0) { - switch (bytesRead) { + switch (bytesRead) + { case 0: if (c != 0x62) { @@ -341,7 +353,7 @@ bool P082_data_struct::loop() { if (gps->encode(c)) { // Full sentence received # ifdef P082_SEND_GPS_TO_LOG - _lastSentence = _currentSentence; + _lastSentence = _currentSentence; free_string(_currentSentence); # endif // ifdef P082_SEND_GPS_TO_LOG completeSentence = true; @@ -352,6 +364,7 @@ bool P082_data_struct::loop() { available = easySerial->available(); _softwarePPS.addStartOfSentence(available); } + if (available == 0) { available = easySerial->available(); } @@ -397,10 +410,10 @@ ESPEASY_RULES_FLOAT_TYPE P082_data_struct::distanceSinceLast(unsigned int maxAge // @param age is the time in msec since the last update of the time + // additional centiseconds given by the GPS. bool P082_data_struct::getDateTime( - struct tm& dateTime, - uint8_t & centiseconds, - uint32_t & age, - bool & updated) { + struct tm&dateTime, + uint8_t & centiseconds, + uint32_t& age, + bool & updated) { updated = false; if (!isInitialized()) { @@ -453,7 +466,7 @@ bool P082_data_struct::getDateTime( return true; } -bool P082_data_struct::getDateTime(struct tm& dateTime) const +bool P082_data_struct::getDateTime(struct tm&dateTime) const { uint64_t value_usec{}; @@ -559,8 +572,10 @@ bool P082_data_struct::wakeUp() { } # ifdef P082_USE_U_BLOX_SPECIFIC + bool P082_data_struct::setPowerMode(P082_PowerMode mode) { - switch (mode) { + switch (mode) + { case P082_PowerMode::Max_Performance: { const uint8_t UBLOX_command[] = { 0xB5, 0x62, 0x06, 0x11, 0x02, 0x00, 0x08, 0x00, 0x21, 0x91 }; @@ -612,6 +627,7 @@ bool P082_data_struct::setDynamicModel(P082_DynamicModel model) { # endif // ifdef P082_USE_U_BLOX_SPECIFIC # ifdef P082_USE_U_BLOX_SPECIFIC + void P082_data_struct::computeUbloxChecksum(const uint8_t *data, size_t size, uint8_t& CK_A, uint8_t& CK_B) { CK_A = 0; CK_B = 0; @@ -647,12 +663,10 @@ bool P082_data_struct::writeToGPS(const uint8_t *data, size_t size) { return false; } -void ICACHE_RAM_ATTR P082_data_struct::pps_interrupt(P082_data_struct *self) -{ - self->_pps_time_micros = getMicros64(); -} +void ICACHE_RAM_ATTR P082_data_struct::pps_interrupt(P082_data_struct *self) { self->_pps_time_micros = getMicros64(); } # if FEATURE_PLUGIN_STATS + bool P082_data_struct::webformLoad_show_stats(struct EventStruct *event, uint8_t var_index, P082_query query_type) const { bool somethingAdded = false; @@ -670,7 +684,8 @@ bool P082_data_struct::webformLoad_show_stats(struct EventStruct *event, uint8_t ESPEASY_RULES_FLOAT_TYPE dist_stddev{}; if (gps != nullptr) { - switch (query_type) { + switch (query_type) + { case P082_query::P082_QUERY_LAT: show_custom = true; @@ -738,6 +753,7 @@ bool P082_data_struct::webformLoad_show_stats(struct EventStruct *event, uint8_t } # if FEATURE_CHART_JS + void P082_data_struct::webformLoad_show_position_scatterplot(struct EventStruct *event) { taskVarIndex_t stats_long = INVALID_TASKVAR_INDEX; @@ -747,7 +763,8 @@ void P082_data_struct::webformLoad_show_position_scatterplot(struct EventStruct const uint8_t pconfigIndex = var_index + P082_QUERY1_CONFIG_POS; const P082_query query = static_cast(PCONFIG(pconfigIndex)); - switch (query) { + switch (query) + { case P082_query::P082_QUERY_LONG: stats_long = var_index; break; @@ -758,26 +775,27 @@ void P082_data_struct::webformLoad_show_position_scatterplot(struct EventStruct break; } } - + addRowColspan(2); plot_ChartJS_scatter( stats_long, stats_lat, F("positionscatter"), { F("Position Scatter Plot") }, - { F("Coordinates"), F("rgb(255, 99, 132)") }, - 500, - 500); + { F("Coordinates"), F("rgb(255, 99, 132)") }); + addHtml(F("")); } # endif // if FEATURE_CHART_JS # endif // if FEATURE_PLUGIN_STATS -#ifndef BUILD_NO_DEBUG +# ifndef BUILD_NO_DEBUG + String P082_data_struct::getPPSStats() const { return _softwarePPS.getStats(); } -#endif + +# endif // ifndef BUILD_NO_DEBUG #endif // ifdef USES_P082 diff --git a/src/src/PluginStructs/P087_data_struct.cpp b/src/src/PluginStructs/P087_data_struct.cpp index 5adc8c637..111308955 100644 --- a/src/src/PluginStructs/P087_data_struct.cpp +++ b/src/src/PluginStructs/P087_data_struct.cpp @@ -106,42 +106,53 @@ bool P087_data_struct::loop() { while (available > 0 && !fullSentenceReceived) { // Look for end marker - char c = easySerial->read(); + uint8_t c = easySerial->read(); --available; if (available == 0) { available = easySerial->available(); delay(0); } + const size_t length = sentence_part.length(); + # ifdef LIMIT_BUILD_SIZE + const bool addCharacter = (10u != c); + const bool finished = (13u == c); + # else // ifdef LIMIT_BUILD_SIZE + const bool addCharacter = (10u != c) || handle_binary; // Skip LF in ascii-mode + bool finished = (13u == c) && !handle_binary; // Done on CR in ascii-mode - switch (c) { - case 13: - { - const size_t length = sentence_part.length(); - bool valid = length > 0; + if (0 != fixed_length) { + finished = (length == fixed_length); - for (size_t i = 0; i < length && valid; ++i) { - if ((sentence_part[i] > 127) || (sentence_part[i] < 32)) { - sentence_part = EMPTY_STRING; - ++sentences_received_error; - valid = false; - } - } - - if (valid) { - fullSentenceReceived = true; - last_sentence = sentence_part; - sentence_part = EMPTY_STRING; - } - break; + if (finished) { + available = 0; // Forced exit } - case 10: + } + # endif // ifdef LIMIT_BUILD_SIZE - // Ignore LF - break; - default: - sentence_part += c; - break; + if (finished) { + bool valid = length > 0; + + for (size_t i = 0; i < length && valid + # ifndef LIMIT_BUILD_SIZE + && !handle_binary // Skip valid-ascii check + # endif // ifndef LIMIT_BUILD_SIZE + ; ++i) { + if ((sentence_part[i] > 127) || (sentence_part[i] < 32)) { + sentence_part = EMPTY_STRING; + ++sentences_received_error; + valid = false; + } + } + + if (valid) { + fullSentenceReceived = true; + last_sentence = sentence_part; + sentence_part = EMPTY_STRING; + } + } + else if (addCharacter) { + sentence_part += static_cast(c); } if (max_length_reached()) { fullSentenceReceived = true; } @@ -152,6 +163,16 @@ bool P087_data_struct::loop() { ++sentences_received; length_last_received = last_sentence.length(); } + # ifndef LIMIT_BUILD_SIZE + else if (handle_binary && last_sentence.isEmpty() && !sentence_part.isEmpty()) { // Receive binary data (no end-marker) + fullSentenceReceived = true; + last_sentence = sentence_part; + sentence_part = EMPTY_STRING; + ++sentences_received; + length_last_received = last_sentence.length(); + } + # endif // ifndef LIMIT_BUILD_SIZE + return fullSentenceReceived; } diff --git a/src/src/PluginStructs/P087_data_struct.h b/src/src/PluginStructs/P087_data_struct.h index 031e88a52..8f888174e 100644 --- a/src/src/PluginStructs/P087_data_struct.h +++ b/src/src/PluginStructs/P087_data_struct.h @@ -21,6 +21,31 @@ # define P87_Nchars 128 # define P87_MAX_CAPTURE_INDEX 32 +# define P087_BAUDRATE PCONFIG_LONG(0) +# define P087_BAUDRATE_LABEL PCONFIG_LABEL(0) +# define P087_SERIAL_CONFIG PCONFIG_LONG(1) + +# define P087_QUERY_VALUE 0 // Temp placement holder until we know what selectors are needed. +# define P087_NR_OUTPUT_OPTIONS 1 + +# define P087_NR_OUTPUT_VALUES 1 +# define P087_QUERY1_CONFIG_POS 3 + +# define P087_DEFAULT_BAUDRATE 38400 + +# define P087_READ_BIN_LABEL PCONFIG_LABEL(1) +# define P087_EVENT_HEX_LABEL PCONFIG_LABEL(2) +# define P087_FIXED_LENGTH_LABEL PCONFIG_LABEL(3) +# define P087_CONFIG_FLAGS PCONFIG_ULONG(2) +# define P087_FLAG_READ_BIN 0 +# define P087_FLAG_EVENT_HEX 1 +# define P087_FLAG_FIXED_LENGTH 2 // 8 bit +# define P087_CONFIG_SET_READ_BIN(S) (bitWrite(P087_CONFIG_FLAGS, P087_FLAG_READ_BIN, (S))) +# define P087_CONFIG_GET_READ_BIN (bitRead(P087_CONFIG_FLAGS, P087_FLAG_READ_BIN)) +# define P087_CONFIG_SET_EVENT_HEX(S) (bitWrite(P087_CONFIG_FLAGS, P087_FLAG_EVENT_HEX, (S))) +# define P087_CONFIG_GET_EVENT_HEX (bitRead(P087_CONFIG_FLAGS, P087_FLAG_EVENT_HEX)) +# define P087_CONFIG_SET_FIXED_LENGTH(S) (set8BitToUL(P087_CONFIG_FLAGS, P087_FLAG_FIXED_LENGTH, (S))) +# define P087_CONFIG_GET_FIXED_LENGTH (get8BitFromUL(P087_CONFIG_FLAGS, P087_FLAG_FIXED_LENGTH)) enum P087_Filter_Comp { Equal = 0, @@ -115,6 +140,25 @@ public: bool plugin_get_config_value(struct EventStruct *event, String & string); + # ifndef LIMIT_BUILD_SIZE + void setHandleBinary(bool state) { + handle_binary = state; + } + + void setEventAsHex(bool state) { + event_hex = state; + } + + bool isEventAsHex() const { + return event_hex; + } + + void setFixedLength(uint8_t value) { + fixed_length = value; + } + + # endif // ifndef LIMIT_BUILD_SIZE + private: bool max_length_reached() const; @@ -133,6 +177,11 @@ private: bool capture_index_used[P87_MAX_CAPTURE_INDEX] = { 0 }; bool capture_index_must_not_match[P87_MAX_CAPTURE_INDEX] = { 0 }; bool regex_empty = false; + # ifndef LIMIT_BUILD_SIZE + bool handle_binary = false; + bool event_hex = false; + uint8_t fixed_length{}; + # endif // ifndef LIMIT_BUILD_SIZE }; diff --git a/src/src/PluginStructs/P089_data_struct.cpp b/src/src/PluginStructs/P089_data_struct.cpp index 0652b6da0..69b141fa2 100644 --- a/src/src/PluginStructs/P089_data_struct.cpp +++ b/src/src/PluginStructs/P089_data_struct.cpp @@ -4,9 +4,10 @@ # include "../Helpers/Networking.h" - # include "../Helpers/_Plugin_init.h" +# include "../../ESPEasy/net/ESPEasyNetwork.h" + # ifdef ESP32 P089_data_struct::P089_data_struct() { if (nullptr == P089_ping_service) { @@ -34,7 +35,7 @@ P089_data_struct::~P089_data_struct() { bool P089_data_struct::send_ping(struct EventStruct *event) { /* This ping lost for sure */ - if (!isInitialized() || !NetworkConnected()) { + if (!isInitialized() || !ESPEasy::net::NetworkConnected()) { return true; } @@ -148,7 +149,7 @@ bool P089_data_struct::send_ping(struct EventStruct *event) { } /* This ping lost for sure */ - if (!NetworkConnected()) { + if (!ESPEasy::net::NetworkConnected()) { return true; } diff --git a/src/src/PluginStructs/P089_data_struct.h b/src/src/PluginStructs/P089_data_struct.h index 60b91461a..48ae549a1 100644 --- a/src/src/PluginStructs/P089_data_struct.h +++ b/src/src/PluginStructs/P089_data_struct.h @@ -4,7 +4,7 @@ #include "../../_Plugin_Helper.h" #ifdef USES_P089 -# include "src/ESPEasyCore/ESPEasyNetwork.h" +# include "../../ESPEasy/net/ESPEasyNetwork.h" # define PLUGIN_ID_089 89 @@ -54,6 +54,7 @@ extern "C" # include } +# include "ESPEasy/net/ESPEasyNetwork.h" struct P089_icmp_pcb { P089_icmp_pcb() = default; diff --git a/src/src/PluginStructs/P097_data_struct.h b/src/src/PluginStructs/P097_data_struct.h index 9f1d4c8c9..34cc1c577 100644 --- a/src/src/PluginStructs/P097_data_struct.h +++ b/src/src/PluginStructs/P097_data_struct.h @@ -27,6 +27,16 @@ # define LAST_TOUCH_INPUT_INDEX 14 # define P097_MAX_THRESHOLD_VALUE 500000 // couldn't find a max value but threshold for ESP32S2 & ESP32S3 is uint32_t # define P097_DEFAULT_TOUCH_THRESHOLD 1500 +# elif defined(ESP32P4) +// TODO TD-er: Must use SOC_TOUCH_MIN_CHAN_ID and SOC_TOUCH_MAX_CHAN_ID from soc/soc_caps.h + # define HAS_T0_INPUT 0 + # define HAS_T10_TO_T14 0 // Temporary disabled since T10 to T14 are causing problems + # ifdef LAST_TOUCH_INPUT_INDEX + # undef LAST_TOUCH_INPUT_INDEX + # endif // ifdef LAST_TOUCH_INPUT_INDEX + # define LAST_TOUCH_INPUT_INDEX 14 + # define P097_MAX_THRESHOLD_VALUE 500000 // couldn't find a max value but threshold for ESP32S2 & ESP32S3 is uint32_t + # define P097_DEFAULT_TOUCH_THRESHOLD 1500 # endif // if defined(ESP32_CLASSIC) # define P097_MAX_LONGPRESS_VALUE 10000 diff --git a/src/src/PluginStructs/P099_data_struct.cpp b/src/src/PluginStructs/P099_data_struct.cpp index c89004787..03fbbaa5a 100644 --- a/src/src/PluginStructs/P099_data_struct.cpp +++ b/src/src/PluginStructs/P099_data_struct.cpp @@ -1,7 +1,7 @@ #include "../PluginStructs/P099_data_struct.h" #ifdef USES_P099 -# include "../ESPEasyCore/ESPEasyNetwork.h" +# include "../../ESPEasy/net/ESPEasyNetwork.h" # include "../Helpers/ESPEasy_Storage.h" # include "../Helpers/Scheduler.h" # include "../Helpers/StringConverter.h" diff --git a/src/src/PluginStructs/P104_data_struct.h b/src/src/PluginStructs/P104_data_struct.h index 0fde33829..f50975328 100644 --- a/src/src/PluginStructs/P104_data_struct.h +++ b/src/src/PluginStructs/P104_data_struct.h @@ -18,10 +18,12 @@ # include -// # if defined(PLUGIN_SET_MAX) || defined(PLUGIN_BUILD_CUSTOM) || (defined(PLUGIN_DISPLAY_COLLECTION) && defined(ESP32)) +// # if defined(PLUGIN_SET_MAX) || defined(PLUGIN_BUILD_CUSTOM) || ((defined(PLUGIN_DISPLAY_A_COLLECTION) || +// defined(PLUGIN_DISPLAY_B_COLLECTION)) && defined(ESP32)) # ifdef ESP32 # define P104_USE_NUMERIC_DOUBLEHEIGHT_FONT // Enables double height numeric font for double-height time/date -// # endif // if defined(PLUGIN_SET_MAX) || defined(PLUGIN_BUILD_CUSTOM) || (defined(PLUGIN_DISPLAY_COLLECTION) && defined(ESP32)) +// # endif // if defined(PLUGIN_SET_MAX) || defined(PLUGIN_BUILD_CUSTOM) || ((defined(PLUGIN_DISPLAY_A_COLLECTION) || +// defined(PLUGIN_DISPLAY_B_COLLECTION)) && defined(ESP32)) # endif // ifdef ESP32 # define P104_USE_FULL_DOUBLEHEIGHT_FONT // Enables the use of a full (lower ascii only) set double height font # define P104_USE_VERTICAL_FONT // Enables the use of a vertical font @@ -45,7 +47,7 @@ # endif // if FEATURE_EXTENDED_CUSTOM_SETTINGS && defined(ESP32) && defined(USE_LITTLEFS) // To make it fit in the ESP8266 display build -# if defined(PLUGIN_DISPLAY_COLLECTION) && defined(ESP8266) && !defined(LIMIT_BUILD_SIZE) +# if (defined(PLUGIN_DISPLAY_A_COLLECTION) || defined(PLUGIN_DISPLAY_B_COLLECTION)) && defined(ESP8266) && !defined(LIMIT_BUILD_SIZE) # ifdef P104_USE_FULL_DOUBLEHEIGHT_FONT # undef P104_USE_FULL_DOUBLEHEIGHT_FONT # ifndef P104_USE_NUMERIC_DOUBLEHEIGHT_FONT @@ -83,7 +85,7 @@ # undef P104_DEBUG_DEV # endif // ifdef P104_DEBUG_DEV # define P104_MEDIUM_ANIMATIONS -# endif // if defined(PLUGIN_DISPLAY_COLLECTION) && defined(ESP8266) +# endif // if (defined(PLUGIN_DISPLAY_A_COLLECTION) || defined(PLUGIN_DISPLAY_B_COLLECTION)) && defined(ESP8266) && !defined(LIMIT_BUILD_SIZE) // # define P104_MINIMAL_ANIMATIONS // disable most animations // # define P104_MEDIUM_ANIMATIONS // disable some complex animations diff --git a/src/src/PluginStructs/P109_data_struct.cpp b/src/src/PluginStructs/P109_data_struct.cpp index fc4a398c7..20597f632 100644 --- a/src/src/PluginStructs/P109_data_struct.cpp +++ b/src/src/PluginStructs/P109_data_struct.cpp @@ -501,7 +501,7 @@ void P109_data_struct::actionMode(struct EventStruct *event) { * Display header, alternating between WiFi AP SSID and Sysname */ void P109_data_struct::display_header() { - if (_alternateTitle && _showWiFiName && WiFiEventData.WiFiServicesInitialized()) { + if (_alternateTitle && _showWiFiName && ESPEasy::net::NetworkConnected()) { // String newString = ; display_title(WiFi.SSID()); } else { @@ -536,7 +536,7 @@ void P109_data_struct::display_title(const String& title) { * Draw Signal Strength Bars, return true when there was an update. */ bool P109_data_struct::display_wifibars() { - const bool connected = WiFiEventData.WiFiServicesInitialized(); + const bool connected = ESPEasy::net::NetworkConnected(); const int nbars_filled = (WiFi.RSSI() + 100) / 8; const int newState = connected ? nbars_filled : P109_WIFI_STATE_UNSET; @@ -561,7 +561,7 @@ bool P109_data_struct::display_wifibars() { _display->fillRect(x, y, size_x, size_y); _display->setColor(WHITE); - if (WiFiEventData.WiFiServicesInitialized()) { + if (ESPEasy::net::NetworkConnected()) { for (uint8_t ibar = 0; ibar < nbars; ++ibar) { const int16_t height = size_y * (ibar + 1) / nbars; const int16_t xpos = x + ibar * width; diff --git a/src/src/PluginStructs/P109_data_struct.h b/src/src/PluginStructs/P109_data_struct.h index 68b750586..9cdf63be0 100644 --- a/src/src/PluginStructs/P109_data_struct.h +++ b/src/src/PluginStructs/P109_data_struct.h @@ -4,7 +4,8 @@ #include "../../_Plugin_Helper.h" #ifdef USES_P109 -# include "../Globals/ESPEasyWiFiEvent.h" + +# include "../../ESPEasy/net/Globals/ESPEasyWiFiEvent.h" # include "../Helpers/OLed_helper.h" # include "SSD1306.h" diff --git a/src/src/PluginStructs/P111_data_struct.cpp b/src/src/PluginStructs/P111_data_struct.cpp index 2f5fcebc6..821b38605 100644 --- a/src/src/PluginStructs/P111_data_struct.cpp +++ b/src/src/PluginStructs/P111_data_struct.cpp @@ -2,6 +2,7 @@ #ifdef USES_P111 +# include "../ESPEasyCore/ESPEasyRules.h" # include "../PluginStructs/P111_data_struct.h" # include "../Helpers/PrintToString.h" @@ -28,12 +29,12 @@ P111_data_struct::~P111_data_struct() { void P111_data_struct::init() { delete mfrc522; - mfrc522 = new (std::nothrow) MFRC522(_csPin, _rstPin); // Instantiate a MFRC522 + mfrc522 = new (std::nothrow) MFRC522(_csPin, _rstPin); // Instantiate a MFRC522 if (mfrc522 != nullptr) { - mfrc522->PCD_Init(); // Initialize MFRC522 reader + mfrc522->PCD_Init(); // Initialize MFRC522 reader mfrc522->PCD_WriteRegister(MFRC522::ComIEnReg, 0b10100000); // enable receiver interrupt - mfrc522->PCD_WriteRegister(MFRC522::DivIEnReg, 0x80); // Set as CMOS output pin + mfrc522->PCD_WriteRegister(MFRC522::DivIEnReg, 0x80); // Set as CMOS output pin initPhase = P111_initPhases::Ready; if (validGpio(_irqPin)) { @@ -50,19 +51,20 @@ void P111_data_struct::init() { /** * read status and tag */ -uint8_t P111_data_struct::readCardStatus(uint32_t *key, +uint8_t P111_data_struct::readCardStatus(uint64_t *key, bool *removedTag) { if (initPhase != P111_initPhases::Ready) { // No read during reset return P111_ERROR_RESET_BUSY; } uint8_t error = P111_NO_ERROR; - uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; + uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; uint8_t uidLength; error = readPassiveTargetID(uid, &uidLength); - switch (error) { + switch (error) + { case P111_ERROR_READ: // Read error { errorCount++; @@ -95,9 +97,10 @@ uint8_t P111_data_struct::readCardStatus(uint32_t *key, return P111_ERROR_RESET_BUSY; } } - uint32_t tmpKey = uid[0]; + uint64_t tmpKey = uid[0]; - for (uint8_t i = 1; i < 4; i++) { + for (uint8_t i = 1; i < uidLength && i < sizeof(uid); i++) { + // FIXME TD-er: 10-byte UID will not fit in an uint64_t tmpKey <<= 8; tmpKey += uid[i]; } @@ -109,9 +112,7 @@ uint8_t P111_data_struct::readCardStatus(uint32_t *key, /** * Returns last read card (type) name */ -String P111_data_struct::getCardName() { - return mfrc522->PICC_GetTypeName(mfrc522->PICC_GetType(mfrc522->uid.sak)); -} +String P111_data_struct::getCardName() { return mfrc522->PICC_GetTypeName(mfrc522->PICC_GetType(mfrc522->uid.sak)); } /*********************************************************************************************\ * MFRC522 init @@ -128,13 +129,13 @@ String P111_data_struct::getCardName() { * - phase = Ready * - exit true, assuming initialization succeeds \*********************************************************************************************/ -bool P111_data_struct::reset(int8_t csPin, - int8_t resetPin) { +bool P111_data_struct::reset(int8_t csPin, + int8_t resetPin) { if ((resetPin != -1) && (initPhase == P111_initPhases::Ready)) { if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove( - LOG_LEVEL_INFO, + LOG_LEVEL_INFO, concat(F("MFRC522: Reset on pin: "), resetPin)); } @@ -161,9 +162,9 @@ bool P111_data_struct::reset(int8_t csPin, pinMode(csPin, OUTPUT); digitalWrite(csPin, LOW); - mfrc522->PCD_Init(csPin, resetPin); // Init MFRC522 module + mfrc522->PCD_Init(csPin, resetPin); // Init MFRC522 module mfrc522->PCD_WriteRegister(MFRC522::ComIEnReg, 0b10100000); // enable receiver interrupt - mfrc522->PCD_WriteRegister(MFRC522::DivIEnReg, 0x80); // Set as CMOS output pin + mfrc522->PCD_WriteRegister(MFRC522::DivIEnReg, 0x80); // Set as CMOS output pin // If you set Antenna Gain to Max it will increase reading distance mfrc522->PCD_SetAntennaGain(mfrc522->RxGain_max); @@ -226,39 +227,36 @@ uint8_t P111_data_struct::readPassiveTargetID(uint8_t *uid, // Until we support 7 uint8_t PICCs addLog(LOG_LEVEL_INFO, F("MFRC522: Scanned PICC's UID")); - for (uint8_t i = 0; i < 4; i++) { // + for (uint8_t i = 0; i < mfrc522->uid.size; i++) { // uid[i] = mfrc522->uid.uidByte[i]; } - *uidLength = 4; + *uidLength = mfrc522->uid.size; - #ifndef BUILD_NO_DEBUG + # ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { PrintToString p2str; mfrc522->PICC_DumpToSerial(&(mfrc522->uid), p2str); + if (p2str.length()) { addLog(LOG_LEVEL_DEBUG, concat(F("MFRC522: "), p2str.get())); } } - #endif + # endif // ifndef BUILD_NO_DEBUG mfrc522->PICC_HaltA(); // Stop reading return P111_NO_ERROR; } -void P111_data_struct::mfrc522_interrupt(P111_data_struct * self) -{ - self->_irq_pin_time_micros = getMicros64(); -} +void P111_data_struct::mfrc522_interrupt(P111_data_struct *self) { self->_irq_pin_time_micros = getMicros64(); } /********************************************************************************************* * Handle regular read and reset processing ********************************************************************************************/ -bool P111_data_struct::plugin_ten_per_second(struct EventStruct *event) { - return loop(event); -} +bool P111_data_struct::plugin_ten_per_second(struct EventStruct *event) { return loop(event); } bool P111_data_struct::loop(struct EventStruct *event) { bool success = false; @@ -272,20 +270,24 @@ bool P111_data_struct::loop(struct EventStruct *event) { return success; } - counter++; // This variable replaces a static variable in the original implementation - const bool ComIrqReg_bits = (mfrc522->PCD_ReadRegister(MFRC522::ComIrqReg) & (1<<5)) != 0; + counter++; // This variable replaces a static variable in the original implementation + const bool ComIrqReg_bits = (mfrc522->PCD_ReadRegister(MFRC522::ComIrqReg) & (1 << 5)) != 0; mfrc522->PCD_WriteRegister(MFRC522::ComIrqReg, 0x34); - if (counter >= 10 || ComIrqReg_bits) { // Only every 3rd 0.1 second we do a read + if ((counter >= 5) || ComIrqReg_bits) { // Only every 3rd 0.1 second we do a read counter = 0; - uint32_t key = P111_NO_KEY; + uint64_t key = P111_NO_KEY; bool removedTag = false; const uint8_t error = readCardStatus(&key, &removedTag); if (error == P111_NO_ERROR) { - const uint32_t old_key = UserVar.getSensorTypeLong(event->TaskIndex); - bool new_key = false; +# if FEATURE_EXTENDED_TASK_VALUE_TYPES + const uint64_t old_key = UserVar.getUint64(event->TaskIndex, 0); +# else + const uint64_t old_key = UserVar.getSensorTypeLong(event->TaskIndex, 0); +# endif // if FEATURE_EXTENDED_TASK_VALUE_TYPES + bool new_key = false; # ifdef P111_USE_REMOVAL @@ -294,33 +296,66 @@ bool P111_data_struct::loop(struct EventStruct *event) { } # endif // P111_USE_REMOVAL - if ((old_key != key) && (key != P111_NO_KEY)) { - UserVar.setSensorTypeLong(event->TaskIndex, key); - new_key = true; - } + if (old_key != key) { + if (key != P111_NO_KEY) { +# if FEATURE_EXTENDED_TASK_VALUE_TYPES + UserVar.setUint64(event->TaskIndex, 0, key); +# else + UserVar.setSensorTypeLong(event->TaskIndex, 0, key); +# endif // if FEATURE_EXTENDED_TASK_VALUE_TYPES + new_key = true; - if (loglevelActiveFor(LOG_LEVEL_INFO) && (key != P111_NO_KEY)) { - String log = F("MFRC522: "); + if (Settings.UseRules) + { + // Event values: + // - Full tag UID + // - Least significant 32 bit (byte 0 .. 3) + // - Extra bytes of 7-byte UID (byte 4 .. 6) + // - Extra bytes of 10-byte UID (byte 7 .. 9) - if (new_key) { - log += F("New Tag: "); - } else { - log += F("Old Tag: "); + // TODO TD-er: No support yet for 10 byte UIDs + + // TD-er: Process event of read key now. + rulesProcessing(strformat( + F("%s#taguid=%s,%s,%s"), + getTaskDeviceName(event->TaskIndex).c_str(), + formatULLtoHex(key, 1).c_str(), + formatToHex(static_cast(key & 0xFFFFFFFF), 1).c_str(), + formatToHex(static_cast((key >> 32) & 0xFFFFFF), 1).c_str()) + ); + } + + + if (loglevelActiveFor(LOG_LEVEL_INFO) && (key != P111_NO_KEY)) { + String log = F("MFRC522: "); + + if (new_key) { + log += F("New Tag: "); + } else { + log += F("Old Tag: "); + } + log += formatULLtoHex_decimal(key); + + if (!removedTag) { + log += F(" card: "); + log += getCardName(); + } + addLogMove(LOG_LEVEL_INFO, log); + } + + if (new_key && !removedTag) { // Removal event sent from PLUGIN_TIMER_IN, if any + sendData(event); + } + Scheduler.setPluginTaskTimer(P111_REMOVALTIMEOUT, event->TaskIndex, event->Par1); + success = true; + } + if (removedTag && Settings.UseRules) { + // TD-er: Process event of read key now. + rulesProcessing(concat( + getTaskDeviceName(event->TaskIndex), + F("#tagremoved"))); } - log += formatToHex_decimal(key); - - if (!removedTag) { - log += F(" card: "); - log += getCardName(); - } - addLogMove(LOG_LEVEL_INFO, log); } - - if (new_key && !removedTag) { // Removal event sent from PLUGIN_TIMER_IN, if any - sendData(event); - } - Scheduler.setPluginTaskTimer(P111_REMOVALTIMEOUT, event->TaskIndex, event->Par1); - success = true; } } return success; @@ -346,24 +381,29 @@ bool P111_data_struct::plugin_fifty_per_second(struct EventStruct *event) { } } } + if (_irq_pin_time_micros > _last_served_irq_pin_time_micros) { _last_served_irq_pin_time_micros = _irq_pin_time_micros; - //addLog(LOG_LEVEL_INFO, F("P111: acting on interrupt")); + + // addLog(LOG_LEVEL_INFO, F("P111: acting on interrupt")); loop(event); } return true; } - String P111_data_struct::PCD_getVersion(uint8_t& v) { v = 0xFF; + if (mfrc522) { v = mfrc522->PCD_ReadRegister(MFRC522::VersionReg); - if (v != 0xFF && v != 0) { + + if ((v != 0xFF) && (v != 0)) { // Human readable version. String res = concat(formatToHex(v, 2), F(" = ")); - switch(v) { + + switch (v) + { case 0xb2: res += F("FM17522_1"); break; @@ -395,5 +435,4 @@ String P111_data_struct::PCD_getVersion(uint8_t& v) { return EMPTY_STRING; } - #endif // ifdef USES_P111 diff --git a/src/src/PluginStructs/P111_data_struct.h b/src/src/PluginStructs/P111_data_struct.h index 8acabdc6f..72da64742 100644 --- a/src/src/PluginStructs/P111_data_struct.h +++ b/src/src/PluginStructs/P111_data_struct.h @@ -52,7 +52,7 @@ private: uint8_t counter = 0; String getCardName(); - uint8_t readCardStatus(uint32_t *key, + uint8_t readCardStatus(uint64_t *key, bool *removedTag); bool reset(int8_t csPin, int8_t resetPin); diff --git a/src/src/PluginStructs/P116_data_struct.cpp b/src/src/PluginStructs/P116_data_struct.cpp index b803d1703..ad65da83e 100644 --- a/src/src/PluginStructs/P116_data_struct.cpp +++ b/src/src/PluginStructs/P116_data_struct.cpp @@ -15,6 +15,8 @@ const __FlashStringHelper* ST77xx_type_toString(const ST77xx_type_e& device) { case ST77xx_type_e::ST7735s_135x240: return F("ST7735 135 x 240px"); case ST77xx_type_e::ST7735s_172x320: return F("ST7735 172 x 320px"); case ST77xx_type_e::ST77xxs_170x320: return F("ST77xx 170 x 320px"); + case ST77xx_type_e::ST77xxs_240x320: return F("ST77xx 240 x 320px"); + case ST77xx_type_e::ST77xxs_240x280: return F("ST77xx 240 x 280px"); # endif // if P116_EXTRA_ST7735 case ST77xx_type_e::ST7789vw_240x320: return F("ST7789 240 x 320px"); case ST77xx_type_e::ST7789vw_240x240: return F("ST7789 240 x 240px"); @@ -83,6 +85,14 @@ void ST77xx_type_toResolution(const ST77xx_type_e& device, x = 170; y = 320; break; + case ST77xx_type_e::ST77xxs_240x320: + x = 240; + y = 320; + break; + case ST77xx_type_e::ST77xxs_240x280: + x = 240; + y = 280; + break; # endif // if P116_EXTRA_ST7735 case ST77xx_type_e::ST7796s_320x480: x = 320; @@ -201,6 +211,20 @@ bool P116_data_struct::plugin_init(struct EventStruct *event) { initRoptions = INITR_BLACKTAB170x320; // 170x320px } + // fall through + case ST77xx_type_e::ST77xxs_240x320: + + if (initRoptions == 0xFF) { + initRoptions = INITR_BLACKTAB240x320; // 240x320px + } + + // fall through + case ST77xx_type_e::ST77xxs_240x280: + + if (initRoptions == 0xFF) { + initRoptions = INITR_BLACKTAB240x280; // 240x280px + } + // fall through # endif // if P116_EXTRA_ST7735 case ST77xx_type_e::ST7735s_80x160: diff --git a/src/src/PluginStructs/P116_data_struct.h b/src/src/PluginStructs/P116_data_struct.h index deaf3cfd4..b70d3a381 100644 --- a/src/src/PluginStructs/P116_data_struct.h +++ b/src/src/PluginStructs/P116_data_struct.h @@ -57,12 +57,14 @@ # define P116_CONFIG_FLAG_TYPE 16 // Flag-offset to store 4 bits for Hardwaretype, uses bits 16, 17, 18 and 19 # define P116_CONFIG_FLAG_CMD_TRIGGER 20 // Flag-offset to store 4 bits for Command trigger, uses bits 20, 21, 22 and 23 # define P116_CONFIG_FLAG_BACK_FILL 28 // Flag: Background fill when printing text +# define P116_CONFIG_FLAG_TYPE2 29 // Flag-offset to store 2 more bits for Hardwaretype, uses bits 29 and 30 // Getters # define P116_CONFIG_FLAG_GET_MODE (get4BitFromUL(P116_CONFIG_FLAGS, P116_CONFIG_FLAG_MODE)) # define P116_CONFIG_FLAG_GET_ROTATION (get4BitFromUL(P116_CONFIG_FLAGS, P116_CONFIG_FLAG_ROTATION)) # define P116_CONFIG_FLAG_GET_FONTSCALE (get4BitFromUL(P116_CONFIG_FLAGS, P116_CONFIG_FLAG_FONTSCALE)) -# define P116_CONFIG_FLAG_GET_TYPE (get4BitFromUL(P116_CONFIG_FLAGS, P116_CONFIG_FLAG_TYPE)) +# define P116_CONFIG_FLAG_GET_TYPE (get4BitFromUL(P116_CONFIG_FLAGS, P116_CONFIG_FLAG_TYPE) \ + + (get2BitFromUL(P116_CONFIG_FLAGS, P116_CONFIG_FLAG_TYPE2) << 4)) # define P116_CONFIG_FLAG_GET_CMD_TRIGGER (get4BitFromUL(P116_CONFIG_FLAGS, P116_CONFIG_FLAG_CMD_TRIGGER)) # define P116_CONFIG_GET_COLOR_FOREGROUND (P116_CONFIG_COLORS & 0xFFFF) # define P116_CONFIG_GET_COLOR_BACKGROUND ((P116_CONFIG_COLORS >> 16) & 0xFFFF) @@ -103,6 +105,8 @@ enum class ST77xx_type_e : uint8_t { ST7735s_135x240 = 12u, ST7735s_172x320 = 13u, ST77xxs_170x320 = 14u, + ST77xxs_240x320 = 15u, + ST77xxs_240x280 = 16u, # endif // if P116_EXTRA_ST7735 }; diff --git a/src/src/PluginStructs/P118_data_struct.cpp b/src/src/PluginStructs/P118_data_struct.cpp index a33615c8d..9b2db6e8a 100644 --- a/src/src/PluginStructs/P118_data_struct.cpp +++ b/src/src/PluginStructs/P118_data_struct.cpp @@ -153,7 +153,7 @@ bool P118_data_struct::plugin_write(struct EventStruct *event, const String& str if (equals(cmd, F("itho")) || stateCmd) { # ifndef BUILD_NO_DEBUG - if (stateCmd) { addLogMove(LOG_LEVEL_ERROR, F("ITHO: Command 'state' is deprecated, use 'itho' instead, see documentation.")); } + if (stateCmd) { addLog(LOG_LEVEL_ERROR, F("ITHO: Command 'state' is deprecated, use 'itho' instead, see documentation.")); } # endif // ifndef BUILD_NO_DEBUG success = true; diff --git a/src/src/PluginStructs/P123_data_struct.cpp b/src/src/PluginStructs/P123_data_struct.cpp index dd9daf867..f4501d51a 100644 --- a/src/src/PluginStructs/P123_data_struct.cpp +++ b/src/src/PluginStructs/P123_data_struct.cpp @@ -144,7 +144,7 @@ bool P123_data_struct::init(struct EventStruct *event) { addLogMove(LOG_LEVEL_INFO, concat(concat(F("P123 DEBUG Plugin"), nullptr != touchscreen ? F(" & touchscreen") : F("")), F(" initialized."))); } else { - addLogMove(LOG_LEVEL_INFO, F("P123 DEBUG Touchscreen initialization FAILED.")); + addLog(LOG_LEVEL_INFO, F("P123 DEBUG Touchscreen initialization FAILED.")); # endif // PLUGIN_123_DEBUG } return isInitialized(); diff --git a/src/src/PluginStructs/P128_data_struct.cpp b/src/src/PluginStructs/P128_data_struct.cpp index 65bc1c9c1..5a0a5b0d2 100644 --- a/src/src/PluginStructs/P128_data_struct.cpp +++ b/src/src/PluginStructs/P128_data_struct.cpp @@ -2,6 +2,7 @@ #ifdef USES_P128 # include "../PluginStructs/P128_data_struct.h" +# include "../Helpers/KeyValueWriter_JSON.h" // ***************************************************************/ // Constructor @@ -1546,39 +1547,30 @@ void P128_data_struct::NeoPixelSendStatus(struct EventStruct *eventSource) { HsbColor hsbColor = HsbColor(RgbColor(rgb.R, rgb.G, rgb.B)); // Calculate only once - SendStatus( - eventSource, - strformat( - F("{\n%s" // "plugin" - ",\n%s" // "mode" - ",\n%s" // "lastmode" - ",\n%s" // "fadetime" - ",\n%s" // "fadedelay" - ",\n%s" // "dim" - ",\n%s" // "rgb" - ",\n%s" // "hue" - ",\n%s" // "saturation" - ",\n%s" // "brightness" - ",\n%s" // "bgcolor" - ",\n%s" // "count" - ",\n%s" // "speed" - ",\n%s" // "pixelcount" - "\n}\n"), - to_json_object_value(F("plugin"), 128).c_str(), - to_json_object_value(F("mode"), P128_modeType_toString(mode)).c_str(), - to_json_object_value(F("lastmode"), P128_modeType_toString(savemode)).c_str(), - to_json_object_value(F("fadetime"), static_cast(fadetime)).c_str(), - to_json_object_value(F("fadedelay"), static_cast(fadedelay)).c_str(), - to_json_object_value(F("dim"), static_cast(Plugin_128_pixels->GetBrightness())).c_str(), - to_json_object_value(F("rgb"), colorStr, true).c_str(), - to_json_object_value(F("hue"), static_cast(hsbColor.H * 360.0f)).c_str(), - to_json_object_value(F("saturation"), static_cast(hsbColor.S * 100.0f)).c_str(), - to_json_object_value(F("brightness"), static_cast(hsbColor.B * 100.0f)).c_str(), - to_json_object_value(F("bgcolor"), backgroundcolorStr, true).c_str(), - to_json_object_value(F("count"), static_cast(count)).c_str(), - to_json_object_value(F("speed"), static_cast(speed)).c_str(), - to_json_object_value(F("pixelcount"), static_cast(pixelCount)).c_str() - )); + static size_t statusSize = 128; + + PrintToString status; + status.reserve(statusSize); + { + KeyValueWriter_JSON writer(true, &status); + + writer.write({F("plugin"), 128}); + writer.write({F("mode"), P128_modeType_toString(mode)}); + writer.write({F("lastmode"), P128_modeType_toString(savemode)}); + writer.write({F("fadetime"), static_cast(fadetime)}); + writer.write({F("fadedelay"), static_cast(fadedelay)}); + writer.write({F("dim"), static_cast(Plugin_128_pixels->GetBrightness())}); + writer.write({F("rgb"), colorStr}); + writer.write({F("hue"), static_cast(hsbColor.H * 360.0f)}); + writer.write({F("saturation"), static_cast(hsbColor.S * 100.0f)}); + writer.write({F("brightness"), static_cast(hsbColor.B * 100.0f)}); + writer.write({F("bgcolor"), backgroundcolorStr}); + writer.write({F("count"), static_cast(count)}); + writer.write({F("speed"), static_cast(speed)}); + writer.write({F("pixelcount"), static_cast(pixelCount)}); + } + statusSize = status.get().length(); + SendStatus(eventSource, status.get()); printToWeb = false; } diff --git a/src/src/PluginStructs/P132_data_struct.cpp b/src/src/PluginStructs/P132_data_struct.cpp index 5d999089e..0f7cb4c31 100644 --- a/src/src/PluginStructs/P132_data_struct.cpp +++ b/src/src/PluginStructs/P132_data_struct.cpp @@ -125,7 +125,7 @@ void P132_data_struct::setCalibration_INA3221(struct EventStruct *event) { # endif // ifndef BUILD_NO_DEBUG if (mfgid != 0x5449) { - addLogMove(LOG_LEVEL_ERROR, F("INA3221: Invalid Manufacturer ID! (0x5449)")); + addLog(LOG_LEVEL_ERROR, F("INA3221: Invalid Manufacturer ID! (0x5449)")); } I2C_write16_reg(_i2c_address, 0x00, static_cast(config)); diff --git a/src/src/PluginStructs/P145_data_struct.cpp b/src/src/PluginStructs/P145_data_struct.cpp index 9b3102e9f..5d91cba68 100644 --- a/src/src/PluginStructs/P145_data_struct.cpp +++ b/src/src/PluginStructs/P145_data_struct.cpp @@ -13,7 +13,7 @@ // #define P145_TEST // #define P145_CALIBRATION_INTERVAL (5*60*1000) -# include "../Globals/ESPEasyWiFiEvent.h" // Need to know when WiFi is ruining the ADC measurements +# include "../../ESPEasy/net/Globals/ESPEasyWiFiEvent.h" // Need to know when WiFi is ruining the ADC measurements // The table sensorDefs[] contains string items for representation. // Storage is in PROGMEM where a (fixed format) C-style string does not fit well @@ -678,7 +678,7 @@ bool P145_data_struct::plugin_ten_per_second() // Algorithm uses mean value with exeption of max and min values // See declaraion of the global variables for their usage // Skip measurement in analog input is used to calibrate WiFi - if (!WiFiEventData.wifiConnectInProgress) +// if (!WiFiEventData.wifiConnectInProgress) { ovs.add(espeasy_analogRead(analogPin)); } diff --git a/src/src/PluginStructs/P146_data_struct.cpp b/src/src/PluginStructs/P146_data_struct.cpp index 680d1b2f1..648f781fe 100644 --- a/src/src/PluginStructs/P146_data_struct.cpp +++ b/src/src/PluginStructs/P146_data_struct.cpp @@ -3,9 +3,10 @@ #ifdef USES_P146 # include "../ControllerQueue/C016_queue_element.h" +# include "../ESPEasyCore/ESPEasyRules.h" # include "../Globals/C016_ControllerCache.h" # include "../Globals/MQTT.h" - +# include "../Helpers/_CPlugin_init.h" P146_data_struct::P146_data_struct(struct EventStruct *event) { @@ -27,7 +28,7 @@ P146_data_struct::~P146_data_struct() uint32_t writeToMqtt(const String& str, bool send) { if (send) { - MQTTclient.write((const uint8_t*)str.c_str(), str.length()); + MQTTclient.write((const uint8_t *)str.c_str(), str.length()); } return str.length(); } @@ -338,17 +339,22 @@ bool P146_data_struct::sendViaOriginalTask( const int peekReadPos = ControllerCache.getPeekFilePos(peekFileNr); - unsigned long timestamp; - uint8_t valueCount; - float taskValues[VARS_PER_TASK] = {}; - EventStruct tmpEvent(C016_getTaskSample( - timestamp, valueCount, - taskValues[0], taskValues[1], taskValues[2], taskValues[3])); + C016_binary_element element{}; - if (!validTaskIndex(tmpEvent.TaskIndex)) { + if (!C016_getTaskSample(element)) { return false; } + if (!validTaskIndex(element.TaskIndex)) { + return false; + } + + if (Settings.UseRules) { + EventStruct tmpEvent(element.TaskIndex); + createRuleEvents(&tmpEvent); + } + + bool success = false; bool unusableSample = false; @@ -362,16 +368,23 @@ bool P146_data_struct::sendViaOriginalTask( // - Copy values from the cache bin files to the task it had // - Call CPLUGIN_PROTOCOL_SEND // - Restore the values - for (int valIndex = 0; valIndex < VARS_PER_TASK; ++valIndex) { - const float tmp = UserVar.getFloat(tmpEvent.TaskIndex, valIndex); - UserVar.setFloat(tmpEvent.TaskIndex, valIndex, taskValues[valIndex]); - taskValues[valIndex] = tmp; + + TaskValues_Data_t tmp; + { + const TaskValues_Data_t*data = UserVar.getRawTaskValues_Data(element.TaskIndex); + + if (data != nullptr) { + for (uint8_t i = 0; i < element.valueCount; ++i) { + tmp.copyValue(*data, i, element.sensorType); + } + } } for (controllerIndex_t x = 0; !unusableSample && x < CONTROLLER_MAX; x++) { // Make sure we are not sending to the cache controller. if (Settings.Protocol[x] != 16) { + EventStruct tmpEvent(element.TaskIndex); tmpEvent.ControllerIndex = x; tmpEvent.idx = Settings.TaskDeviceID[x][P146_TaskIndex]; @@ -385,7 +398,9 @@ bool P146_data_struct::sendViaOriginalTask( String dummy; // FIXME TD-er: Must think about how to include the timestamp - if (CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_PROTOCOL_SEND, &tmpEvent, dummy)) { + // FIXME TD-er: Must this one call CPluginCall instead of do_CPluginCall ???? + // Device VType might not be correct when calling do_CPluginCall + if (do_CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_PROTOCOL_SEND, &tmpEvent, dummy)) { // FIXME TD-er: What to do when multiple controllers are selected and one fails? // Also, what if we only sent 1 value instead of all? success = true; @@ -397,10 +412,14 @@ bool P146_data_struct::sendViaOriginalTask( } } - for (int valIndex = 0; valIndex < VARS_PER_TASK; ++valIndex) { - const float tmp = UserVar.getFloat(tmpEvent.TaskIndex, valIndex); - UserVar.setFloat(tmpEvent.TaskIndex, valIndex, taskValues[valIndex]); - taskValues[valIndex] = tmp; + { + TaskValues_Data_t*data = UserVar.getRawTaskValues_Data(element.TaskIndex); + + if (data != nullptr) { + for (uint8_t i = 0; i < element.valueCount; ++i) { + data->copyValue(tmp, i, element.sensorType); + } + } } if (!success && !unusableSample) { @@ -410,6 +429,54 @@ bool P146_data_struct::sendViaOriginalTask( return success; } +bool P146_data_struct::sendViaEvent_AllCache(taskIndex_t P146_TaskIndex, + bool sendTimestamp) +{ + if (!Settings.UseRules) { return false; } + + C016_binary_element element{}; + + if (!C016_getTaskSample(element)) { + return false; + } + + if (!validTaskIndex(element.TaskIndex)) { + return false; + } + + const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(element.TaskIndex); + + if (!validDeviceIndex(DeviceIndex)) { + return false; + } + + // Need to send the 'next' peek file position info, since it will reflect the last completed position. + int peekFileNr = 0; + const int peekReadPos = ControllerCache.getPeekFilePos(peekFileNr); + + String eventvalues; + reserve_special(eventvalues, 64); // Enough for most use cases, prevent lots of memory allocations. + eventvalues = strformat( + F("%d,%d,%d,%d"), + element.unixTime, + element.valueCount, + peekFileNr, + peekReadPos); + + for (uint8_t varNr = 0; varNr < element.valueCount; ++varNr) { + eventvalues += ','; + uint8_t nrDecimals = 0; + + if (Device[DeviceIndex].configurableDecimals()) { + nrDecimals = Cache.getTaskDeviceValueDecimals(element.TaskIndex, varNr); + } + + eventvalues += element.values.getAsString(varNr, element.sensorType, nrDecimals); + } + eventQueue.add(element.TaskIndex, F("AllCache"), eventvalues); + return true; +} + bool P146_data_struct::setPeekFilePos(int peekFileNr, int peekReadPos) { { @@ -427,9 +494,7 @@ bool P146_data_struct::setPeekFilePos(int peekFileNr, int peekReadPos) return true; } -void P146_data_struct::flush() { - C016_flush(); -} +void P146_data_struct::flush() { C016_flush(); } bool P146_data_struct::getPeekFilePos(int& peekFileNr, int& peekReadPos, int& peekFileSize) const { peekFileNr = 0; diff --git a/src/src/PluginStructs/P146_data_struct.h b/src/src/PluginStructs/P146_data_struct.h index 5761bae49..413c96ea5 100644 --- a/src/src/PluginStructs/P146_data_struct.h +++ b/src/src/PluginStructs/P146_data_struct.h @@ -15,10 +15,10 @@ # define P146_TASKVALUE_FILENR UserVar.getFloat(event->TaskIndex, 0) -# define P146_SET_TASKVALUE_FILENR(x) UserVar.setFloat(event->TaskIndex, 0, x) +# define P146_SET_TASKVALUE_FILENR(x) UserVar.setFloat(event->TaskIndex, 0, x) # define P146_TASKVALUE_FILEPOS UserVar.getFloat(event->TaskIndex, 1) -# define P146_SET_TASKVALUE_FILEPOS(x) UserVar.setFloat(event->TaskIndex, 1, x) +# define P146_SET_TASKVALUE_FILEPOS(x) UserVar.setFloat(event->TaskIndex, 1, x) # define P146_GET_SEND_BINARY bitRead(PCONFIG(0), 0) # define P146_SET_SEND_BINARY(X) bitWrite(PCONFIG(0), 0, X) @@ -30,7 +30,7 @@ # define P146_SET_SEND_TIMESTAMP(X) bitWrite(PCONFIG(0), 2, X) # define P146_GET_SEND_READ_POS bitRead(PCONFIG(0), 3) -# define P146_SET_SEND_READ_POS(X) bitWrite(PCONFIG(0), 3, X) +# define P146_SET_SEND_READ_POS(X) bitWrite(PCONFIG(0), 3, X) # define P146_GET_JOIN_TIMESTAMP bitRead(PCONFIG(0), 4) # define P146_SET_JOIN_TIMESTAMP(X) bitWrite(PCONFIG(0), 4, X) @@ -41,6 +41,12 @@ # define P146_GET_ERASE_BINFILES bitRead(PCONFIG(0), 6) # define P146_SET_ERASE_BINFILES(X) bitWrite(PCONFIG(0), 6, X) +# define P146_GET_SEND_VIA_ORIG_TASK bitRead(PCONFIG(0), 7) +# define P146_SET_SEND_VIA_ORIG_TASK(X) bitWrite(PCONFIG(0), 7, X) + +# define P146_GET_SEND_VIA_EVENT bitRead(PCONFIG(0), 8) +# define P146_SET_SEND_VIA_EVENT(X) bitWrite(PCONFIG(0), 8, X) + # define P146_SEPARATOR_CHARACTER PCONFIG(1) @@ -75,6 +81,11 @@ public: static bool sendViaOriginalTask(taskIndex_t P146_TaskIndex, bool sendTimestamp); + // Send as event using the following format: + // TaskName#AllCache,,,,,,..., + static bool sendViaEvent_AllCache(taskIndex_t P146_TaskIndex, + bool sendTimestamp); + static bool setPeekFilePos(int peekFileNr, int peekReadPos); @@ -94,6 +105,7 @@ private: ESPEasyControllerCache_CSV_dumper *dumper = nullptr; std::listlines; + }; diff --git a/src/src/PluginStructs/P164_data_struct.cpp b/src/src/PluginStructs/P164_data_struct.cpp index ee30716c1..5a51105b6 100644 --- a/src/src/PluginStructs/P164_data_struct.cpp +++ b/src/src/PluginStructs/P164_data_struct.cpp @@ -141,13 +141,13 @@ bool P164_data_struct::begin() { // Start connecting the device over I2C if (!start(i2cAddress)) { - addLogMove(LOG_LEVEL_ERROR, F("P164: device initialization FAILED")); + addLog(LOG_LEVEL_ERROR, F("P164: device initialization FAILED")); return false; } setMode(ENS160_OPMODE_STD); // For now we only support the standard acquisition mode #ifdef P164_ENS160_DEBUG - addLogMove(LOG_LEVEL_DEBUG, F("P164: begin(): success")); + addLog(LOG_LEVEL_DEBUG, F("P164: begin(): success")); #endif return true; } @@ -730,7 +730,7 @@ bool P164_data_struct::getFirmware() { } if (! IS_NEWGPR(this->_statusReg)) { // Check if firmware could be read result = false; - addLogMove(LOG_LEVEL_ERROR, F("P164: Could not read firmware version")); + addLog(LOG_LEVEL_ERROR, F("P164: Could not read firmware version")); } if (result) { diff --git a/src/src/PluginStructs/P169_data_struct.cpp b/src/src/PluginStructs/P169_data_struct.cpp index 9f29c7efb..5e2b3a628 100644 --- a/src/src/PluginStructs/P169_data_struct.cpp +++ b/src/src/PluginStructs/P169_data_struct.cpp @@ -20,7 +20,7 @@ P169_data_struct::P169_data_struct(struct EventStruct *event) : _irqPin(P169_IRQ_PIN) { // Do not try to construct the sensor if not needed as it will set the pinmode of the pin - if (_irqPin >= 0 && Settings.TaskDeviceDataFeed[event->TaskIndex] == 0) { + if ((_irqPin >= 0) && (Settings.TaskDeviceDataFeed[event->TaskIndex] == 0)) { _sensor = new (std::nothrow) AS3935I2C(P169_I2C_ADDRESS, P169_IRQ_PIN); } } @@ -61,7 +61,8 @@ bool P169_data_struct::loop(struct EventStruct *event) } // query the interrupt source from the AS3935 - switch (_sensor->readInterruptSource()) { + switch (_sensor->readInterruptSource()) + { case AS3935MI::AS3935_INT_NH: // Noise floor too high @@ -186,10 +187,13 @@ void P169_data_struct::html_show_sensor_info(struct EventStruct *event) addEnabled(false); } - addRowLabel(F("Error % per cap")); + # if FEATURE_CHART_JS + addRowColspan(2); addCalibrationChart(event); + addHtml(F("")); # else // if FEATURE_CHART_JS + addRowLabel(F("Error % per cap")); for (uint8_t i = 0; i < 16; ++i) { const int32_t freq = _sensor->getAntCapFrequency(i); @@ -248,6 +252,7 @@ bool P169_data_struct::plugin_init(struct EventStruct *event) calibrate(event); # ifdef ESP32 +# ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { @@ -284,6 +289,7 @@ bool P169_data_struct::plugin_init(struct EventStruct *event) addLogMove(LOG_LEVEL_DEBUG, log); } } + # endif // ifndef BUILD_NO_DEBUG # endif // ifdef ESP32 @@ -317,6 +323,7 @@ enum class P169_subcmd_e : int8_t { setnf, // Set noise floor setwd, // Set Watchdog Threshold setsrej // Set Spike Rejection + }; /***************************************************** @@ -342,7 +349,8 @@ bool P169_data_struct::plugin_write(struct EventStruct *event, uint32_t value{}; const bool hasValue = validUIntFromString(parseString(string, 3), value); - switch (subcmd) { + switch (subcmd) + { case P169_subcmd_e::invalid: break; case P169_subcmd_e::clearstats: @@ -400,8 +408,8 @@ enum class P169_get_config_e : int8_t { watchdog, // [#watchdog] srej, // [#srej] = current spike rejection gain // [#gain] -}; +}; /***************************************************** * plugin_get_config_value @@ -438,10 +446,7 @@ bool P169_data_struct::plugin_get_config_value(struct EventStruct *event, return true; } -float P169_data_struct::getDistance() -{ - return computeDistanceFromEnergy(getEnergy(), -1.0f); -} +float P169_data_struct::getDistance() { return computeDistanceFromEnergy(getEnergy(), -1.0f); } uint32_t P169_data_struct::getEnergy() { @@ -492,10 +497,7 @@ void P169_data_struct::clearStatistics() } } -float P169_data_struct::computeDeviationPct(uint32_t LCO_freq) -{ - return (LCO_freq / 5000.0f) - 100.0f; -} +float P169_data_struct::computeDeviationPct(uint32_t LCO_freq) { return (LCO_freq / 5000.0f) - 100.0f; } float P169_data_struct::computeDistanceFromEnergy(uint32_t energy, float errorValue) { @@ -791,15 +793,24 @@ float P169_data_struct::regValue_AFE_gain_toFloat(uint8_t gain) switch (gain) { - case 10: afeGain = 0.30f; break; - case 11: afeGain = 0.40f; break; - case 12: afeGain = 0.55f; break; - case 13: afeGain = 0.74f; break; - case 14: afeGain = 1.00f; break; // Datasheet: "Outdoor" - case 15: afeGain = 1.35f; break; - case 16: afeGain = 1.83f; break; - case 17: afeGain = 2.47f; break; - case 18: afeGain = 3.34f; break; // Datasheet: "Indoor" + case 10: afeGain = 0.30f; + break; + case 11: afeGain = 0.40f; + break; + case 12: afeGain = 0.55f; + break; + case 13: afeGain = 0.74f; + break; + case 14: afeGain = 1.00f; + break; // Datasheet: "Outdoor" + case 15: afeGain = 1.35f; + break; + case 16: afeGain = 1.83f; + break; + case 17: afeGain = 2.47f; + break; + case 18: afeGain = 3.34f; + break; // Datasheet: "Indoor" } return afeGain; } @@ -879,6 +890,7 @@ void P169_data_struct::sendChangeEvent(struct EventStruct *event) } # if FEATURE_CHART_JS + void P169_data_struct::addCalibrationChart(struct EventStruct *event) { if (_sensor == nullptr) { @@ -901,40 +913,46 @@ void P169_data_struct::addCalibrationChart(struct EventStruct *event) } } - String axisOptions; { ChartJS_options_scales scales; scales.add({ F("x"), F("Antenna capacitor") }); scales.add({ F("y"), F("Error (%)") }); - axisOptions = scales.toString(); + auto chart = + add_ChartJS_chart_header( + F("line"), + F("lcoCapErrorCurve"), + { F("LCO Resonance Frequency") }, + scales); + + if (chart) { + auto data = chart->createChild(F("data")); + + if (data) { + add_ChartJS_chart_labels( + *data, + actualValueCount, + xAxisValues); + + auto datasets = data->createChildArray(F("datasets")); + + if (datasets) { + const ChartJS_dataset_config config( + F("Error %"), + F("rgb(255, 99, 132)")); + String options; + + add_ChartJS_dataset( + *datasets, + config, + values, + actualValueCount, + 2, + options); + } + } + } } - - add_ChartJS_chart_header( - F("line"), - F("lcoCapErrorCurve"), - { F("LCO Resonance Frequency") }, - 500, - 500, - axisOptions); - - add_ChartJS_chart_labels( - actualValueCount, - xAxisValues); - - { - const ChartJS_dataset_config config( - F("Error %"), - F("rgb(255, 99, 132)")); - - - add_ChartJS_dataset( - config, - values, - actualValueCount, - 2); - } - add_ChartJS_chart_footer(); } # endif // if FEATURE_CHART_JS diff --git a/src/src/PluginStructs/P180_data_struct.cpp b/src/src/PluginStructs/P180_data_struct.cpp index 83a3a1455..eabc55737 100644 --- a/src/src/PluginStructs/P180_data_struct.cpp +++ b/src/src/PluginStructs/P180_data_struct.cpp @@ -145,11 +145,14 @@ bool P180_data_struct::plugin_write(struct EventStruct *event, cacheName = parseString(string, 5); } + BusCmd_CommandSource_e old = busCmd_Helper->getCommandSource(); + busCmd_Helper->setCommandSource(BusCmd_CommandSource_e::PluginWrite); cmds = busCmd_Helper->parseBusCmdCommands(cacheName, par3, !cacheName.isEmpty()); + busCmd_Helper->setCommandSource(old); } else if (equals(sub, F("exec")) && hasPar3 && hasBusCmd) { // genI2c,exec,[,] cmds = busCmd_Helper->parseBusCmdCommands(par3, EMPTY_STRING); // Fetch commands from cache by name } else if (equals(sub, F("log")) && hasPar3) { // genI2c,log,<1|0> - _showLog = event->Par3 != 0; + _showLog = event->Par2 != 0; P180_LOG_DEBUG = _showLog ? 1 : 0; if (hasBusCmd) { diff --git a/src/src/Static/WebStaticData.h b/src/src/Static/WebStaticData.h index 9053f6d3c..a0995dbed 100644 --- a/src/src/Static/WebStaticData.h +++ b/src/src/Static/WebStaticData.h @@ -85,7 +85,7 @@ static const char favicon_8b_ico[] PROGMEM = { 0x09, 0x35, 0x8e, 0xf2, 0xa6, 0xea, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; -static const unsigned int favicon_8b_ico_len = 390; +static constexpr unsigned int favicon_8b_ico_len = sizeof(favicon_8b_ico); #endif @@ -103,7 +103,7 @@ static const char DATA_GITHUB_CLIPBOARD_JS[] PROGMEM = { "{var o='copyText_'+n,t=document.getElementById(o);if(t){var a='|';n%2==0&&(a+='\\n'),e+=t.innerHTML.replace(/<[Bb][Rr]\\s*\\/?>/gim,'\\n')+a}else n=101}" "e=(e=e.replace(/<\\/[Dd][Ii][Vv]\\s*\\/?>/gim,'\\n')).replace(/<[^>]*>/gim,'');" "var i=document.createElement('textarea');i.style='position: absolute;left: -1000px; top: -1000px',i.innerHTML=e,document.body.appendChild(i),i.select(),document.execCommand('copy')," - "document.body.removeChild(i),alert('Copied: '+e+' to clipboard!');}" + "document.body.removeChild(i),alert('Copied: \"'+e+'\" to clipboard!');}" }; #endif @@ -154,7 +154,7 @@ static const char DATA_ESPEASY_DEFAULT_MIN_CSS[] PROGMEM = { "losebtn{cursor:pointer;font-size:22px;line-height:20px;margin-left:15px;transition:.3s}.closebtn:hover{color:#000}section{overflow-x" ":auto;padding:10px 0}footer{padding:0 5px;word-break:break-all}.closebtn,.div_r{float:right;color:var(--c6)}a,h1,h6{color:var(--c5)}" ".button,.container input:checked~.checkmark:not(.disabled),.container2 input:checked~.dotmark{background-color:var(--bg10)}canvas{fi" -"lter:invert(var(--iv)) hue-rotate(var(--ro))}@media screen and (max-width:780px){.showmenulabel{display:none}a.menu{width:12%}}@medi" +"lter:invert(var(--iv)) hue-rotate(var(--ro))}@media screen and (max-width:830px){.showmenulabel{display:none}a.menu{width:11%}}@medi" "a screen and (max-width:450px){.normal{min-width:300px}input.wide:focus{left:4px;position:absolute;z-index:1}}" }; #else // ifndef EMBED_ESPEASY_DEFAULT_MIN_CSS_USE_GZ @@ -327,7 +327,7 @@ static const char jsToastMessageEnd[] PROGMEM = { " },2000);" "} " }; - +#ifdef WEBSERVER_GITHUB_COPY static const char jsClipboardCopyPart1[] PROGMEM = { "" }; +#endif #ifdef WEBSERVER_INCLUDE_JS // Manually minified js diff --git a/src/src/WebServer/404.cpp b/src/src/WebServer/404.cpp index 6f53d7965..96195bd5a 100644 --- a/src/src/WebServer/404.cpp +++ b/src/src/WebServer/404.cpp @@ -8,7 +8,7 @@ #include "../Globals/Services.h" #include "../Globals/Settings.h" -#include "../Globals/ESPEasyWiFiEvent.h" +#include "../../ESPEasy/net/Globals/ESPEasyWiFiEvent.h" // ******************************************************************************** // Web Interface handle other requests @@ -29,7 +29,8 @@ void handleNotFound() { } // if Wifi setup, launch setup wizard if AP_DONT_FORCE_SETUP is not set. - if (WiFiEventData.wifiSetup && !Settings.ApDontForceSetup()) + if (//WiFiEventData.wifiSetup && + Settings.ApCaptivePortal()) { web_server.send_P(200, (PGM_P)F("text/html"), (PGM_P)F("")); return; diff --git a/src/src/WebServer/AccessControl.cpp b/src/src/WebServer/AccessControl.cpp index 56c6eda03..6dd5c821f 100644 --- a/src/src/WebServer/AccessControl.cpp +++ b/src/src/WebServer/AccessControl.cpp @@ -3,8 +3,9 @@ #include "../DataTypes/FormSelectorOptions.h" #include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyWifi.h" +#include "../../ESPEasy/net/ESPEasyNetwork.h" +#include "../../ESPEasy/net/wifi/ESPEasyWifi.h" + #include "../Globals/SecuritySettings.h" #include "../Globals/Services.h" @@ -14,92 +15,23 @@ #include "../WebServer/Markup.h" -// ******************************************************************************** -// Allowed IP range check -// ******************************************************************************** +#include "../../ESPEasy/net/Helpers/NWAccessControl.h" -boolean ipLessEqual(const IPAddress& ip, const IPAddress& high) -{ - // FIXME TD-er: Must check whether both are of same type and check full range IPv6 - for (int i = 0; i < 4; ++i) { - if (ip[i] != high[i]) { - return ip[i] < high[i]; - } - } - // Is equal - return true; + + +bool clientIPinSubnetDefaultNetwork() { + return NWPlugin::ipInRange( + web_server.client().remoteIP(), + ESPEasy::net::NetworkID(), + ESPEasy::net::NetworkBroadcast()); } -boolean ipInRange(const IPAddress& ip, const IPAddress& low, const IPAddress& high) -{ - return ipLessEqual(low, ip) && ipLessEqual(ip, high); -} - -String describeAllowedIPrange() { - String reply; - - switch (SecuritySettings.IPblockLevel) { - case ALL_ALLOWED: - reply += F("All Allowed"); - break; - default: - { - IPAddress low, high; - getIPallowedRange(low, high); - reply += formatIP(low); - reply += F(" - "); - reply += formatIP(high); - } - } - return reply; -} - -bool getIPallowedRange(IPAddress& low, IPAddress& high) -{ - switch (SecuritySettings.IPblockLevel) { - case LOCAL_SUBNET_ALLOWED: - - if (WifiIsAP(WiFi.getMode())) { - // WiFi is active as accesspoint, do not check. - return false; - } - return getSubnetRange(low, high); - case ONLY_IP_RANGE_ALLOWED: - low = IPAddress(SecuritySettings.AllowedIPrangeLow); - high = IPAddress(SecuritySettings.AllowedIPrangeHigh); - break; - default: - low = IPAddress(0, 0, 0, 0); - high = IPAddress(255, 255, 255, 255); - return false; - } - return true; -} - -bool clientIPinSubnet() { - IPAddress low, high; - - if (!getSubnetRange(low, high)) { - // Could not determine subnet. - return false; - } - return ipInRange(web_server.client().remoteIP(), low, high); -} - -boolean clientIPallowed() +bool clientIPallowed() { #if ESP_IDF_VERSION_MAJOR>=5 // FIXME TD-er: remoteIP() is reporting incorrect value // return true; #endif - // TD-er Must implement "safe time after boot" - IPAddress low, high; - - if (!getIPallowedRange(low, high)) - { - // No subnet range determined, cannot filter on IP - return true; - } const IPAddress remoteIP = web_server.client().remoteIP(); if (remoteIP == IPAddress(0, 0, 0, 0) #if ESP_IDF_VERSION_MAJOR>=5 @@ -111,24 +43,20 @@ boolean clientIPallowed() // FIXME TD-er: Must see what's going on here, why the client doesn't send remote IP for some reason return true; } - - if (ipInRange(remoteIP, low, high)) { + if (ESPEasy::net::ipInAllowedSubnet(remoteIP)) { return true; } - if (WifiIsAP(WiFi.getMode())) { + if ( ESPEasy::net::wifi::WifiIsAP(WiFi.getMode())) { // @TD-er Fixme: Should match subnet of SoftAP. return true; } - String response = F("IP blocked: "); - response += formatIP(remoteIP); + String response = concat(F("IP blocked: "), formatIP(remoteIP)); web_server.send(403, F("text/html"), response); if (loglevelActiveFor(LOG_LEVEL_ERROR)) { response += F(" Allowed: "); - response += formatIP(low); - response += F(" - "); - response += formatIP(high); + response += ESPEasy::net::describeAllowedIPrange(); addLogMove(LOG_LEVEL_ERROR, response); } return false; diff --git a/src/src/WebServer/AccessControl.h b/src/src/WebServer/AccessControl.h index bbb5c2466..abb997552 100644 --- a/src/src/WebServer/AccessControl.h +++ b/src/src/WebServer/AccessControl.h @@ -8,21 +8,9 @@ // ******************************************************************************** // Allowed IP range check // ******************************************************************************** -#define ALL_ALLOWED 0 -#define LOCAL_SUBNET_ALLOWED 1 -#define ONLY_IP_RANGE_ALLOWED 2 +bool clientIPinSubnetDefaultNetwork(); -boolean ipLessEqual(const IPAddress& ip, const IPAddress& high); - -boolean ipInRange(const IPAddress& ip, const IPAddress& low, const IPAddress& high); - -String describeAllowedIPrange(); - -bool getIPallowedRange(IPAddress& low, IPAddress& high); - -bool clientIPinSubnet(); - -boolean clientIPallowed(); +bool clientIPallowed(); void clearAccessBlock(); diff --git a/src/src/WebServer/AdvancedConfigPage.cpp b/src/src/WebServer/AdvancedConfigPage.cpp index 4e22f5852..6d48d9a08 100644 --- a/src/src/WebServer/AdvancedConfigPage.cpp +++ b/src/src/WebServer/AdvancedConfigPage.cpp @@ -8,7 +8,8 @@ #include "../WebServer/Markup_Forms.h" #include "../WebServer/ESPEasy_WebServer.h" -#include "../ESPEasyCore/ESPEasyWifi.h" +#include "../../ESPEasy/net/wifi/ESPEasyWifi.h" + #include "../Globals/ESPEasy_time.h" #include "../Globals/Settings.h" @@ -24,7 +25,7 @@ #include "../Helpers/I2C_access.h" #endif -void setLogLevelFor(uint8_t destination, LabelType::Enum label) { +void setLogLevelFor(LogDestination destination, LabelType::Enum label) { setLogLevelFor(destination, getFormItemInt(getInternalLabel(label))); } @@ -74,9 +75,13 @@ void handle_advanced() { #endif #endif +#if FEATURE_SYSLOG setLogLevelFor(LOG_TO_SYSLOG, LabelType::SYSLOG_LOG_LEVEL); +#endif setLogLevelFor(LOG_TO_SERIAL, LabelType::SERIAL_LOG_LEVEL); +# ifdef WEBSERVER_LOG setLogLevelFor(LOG_TO_WEBLOG, LabelType::WEB_LOG_LEVEL); +#endif #if FEATURE_SD setLogLevelFor(LOG_TO_SDCARD, LabelType::SD_LOG_LEVEL); #endif // if FEATURE_SD @@ -114,20 +119,7 @@ void handle_advanced() { Settings.TolerantLastArgParse(isFormItemChecked(F("tolerantargparse"))); Settings.SendToHttp_ack(isFormItemChecked(F("sendtohttp_ack"))); Settings.SendToHTTP_follow_redirects(isFormItemChecked(F("sendtohttp_redir"))); - Settings.ForceWiFi_bg_mode(isFormItemChecked(LabelType::FORCE_WIFI_BG)); - Settings.WiFiRestart_connection_lost(isFormItemChecked(LabelType::RESTART_WIFI_LOST_CONN)); Settings.EcoPowerMode(isFormItemChecked(LabelType::CPU_ECO_MODE)); - Settings.WifiNoneSleep(isFormItemChecked(LabelType::FORCE_WIFI_NOSLEEP)); -#ifdef SUPPORT_ARP - Settings.gratuitousARP(isFormItemChecked(LabelType::PERIODICAL_GRAT_ARP)); -#endif // ifdef SUPPORT_ARP -#if FEATURE_SET_WIFI_TX_PWR - Settings.setWiFi_TX_power(getFormItemFloat(LabelType::WIFI_TX_MAX_PWR)); - Settings.WiFi_sensitivity_margin = getFormItemInt(LabelType::WIFI_SENS_MARGIN); - Settings.UseMaxTXpowerForSending(isFormItemChecked(LabelType::WIFI_SEND_AT_MAX_TX_PWR)); -#endif - Settings.NumberExtraWiFiScans = getFormItemInt(LabelType::WIFI_NR_EXTRA_SCANS); - Settings.UseLastWiFiFromRTC(isFormItemChecked(LabelType::WIFI_USE_LAST_CONN_FROM_RTC)); Settings.JSONBoolWithoutQuotes(isFormItemChecked(LabelType::JSON_BOOL_QUOTES)); #if FEATURE_TIMING_STATS Settings.EnableTimingStats(isFormItemChecked(LabelType::ENABLE_TIMING_STATISTICS)); @@ -139,20 +131,13 @@ void handle_advanced() { #if FEATURE_I2C_DEVICE_CHECK Settings.CheckI2Cdevice(isFormItemChecked(LabelType::ENABLE_I2C_DEVICE_CHECK)); #endif // if FEATURE_I2C_DEVICE_CHECK -#ifndef ESP32 - Settings.WaitWiFiConnect(isFormItemChecked(LabelType::WAIT_WIFI_CONNECT)); -#endif - Settings.HiddenSSID_SlowConnectPerBSSID(isFormItemChecked(LabelType::HIDDEN_SSID_SLOW_CONNECT)); - Settings.SDK_WiFi_autoreconnect(isFormItemChecked(LabelType::SDK_WIFI_AUTORECONNECT)); -#ifdef ESP32 - Settings.PassiveWiFiScan(isFormItemChecked(LabelType::WIFI_PASSIVE_SCAN)); -#endif #if FEATURE_USE_IPV6 Settings.EnableIPv6(isFormItemChecked(LabelType::ENABLE_IPV6)); #endif + #ifndef BUILD_NO_RAM_TRACKER Settings.EnableRAMTracking(isFormItemChecked(LabelType::ENABLE_RAM_TRACKING)); #endif @@ -179,6 +164,9 @@ void handle_advanced() { #if FEATURE_TASKVALUE_UNIT_OF_MEASURE Settings.ShowUnitOfMeasureOnDevicesPage(isFormItemChecked(LabelType::SHOW_UOM_ON_DEVICES_PAGE)); #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE + #if FEATURE_MQTT_CONNECT_BACKGROUND + Settings.MQTTConnectInBackground(isFormItemChecked(LabelType::MQTT_CONNECT_IN_BACKGROUND)); + #endif // if FEATURE_MQTT_CONNECT_BACKGROUND addHtmlError(SaveSettings()); @@ -198,8 +186,8 @@ void handle_advanced() { #ifdef WEBSERVER_NEW_RULES addFormCheckBox(F("Old Engine"), F("oldrulesengine"), Settings.OldRulesEngine()); #endif // WEBSERVER_NEW_RULES - addFormCheckBox(LabelType::ENABLE_RULES_CACHING, Settings.EnableRulesCaching()); -// addFormCheckBox(LabelType::ENABLE_RULES_EVENT_REORDER, Settings.EnableRulesEventReorder()); // TD-er: Disabled for now + addFormCheckBox(LabelType::ENABLE_RULES_CACHING); +// addFormCheckBox(LabelType::ENABLE_RULES_EVENT_REORDER); // TD-er: Disabled for now addFormCheckBox(F("Tolerant last parameter"), F("tolerantargparse"), Settings.TolerantLastArgParse()); addFormNote(F("Perform less strict parsing on last argument of some commands (e.g. publish and sendToHttp)")); @@ -256,11 +244,14 @@ void handle_advanced() { addFormIPBox(F("Syslog IP"), F("syslogip"), Settings.Syslog_IP); addFormNumericBox(F("Syslog UDP port"), F("syslogport"), Settings.SyslogPort, 0, 65535); - +#if FEATURE_SYSLOG addFormLogLevelSelect(LabelType::SYSLOG_LOG_LEVEL, Settings.SyslogLevel); addFormLogFacilitySelect(F("Syslog Facility"), F("syslogfacility"), Settings.SyslogFacility); +#endif addFormLogLevelSelect(LabelType::SERIAL_LOG_LEVEL, Settings.SerialLogLevel); +# ifdef WEBSERVER_LOG addFormLogLevelSelect(LabelType::WEB_LOG_LEVEL, Settings.WebLogLevel); +#endif #if FEATURE_SD addFormLogLevelSelect(LabelType::SD_LOG_LEVEL, Settings.SDLogLevel); @@ -270,7 +261,7 @@ void handle_advanced() { addFormSubHeader(F("Serial Console Settings")); - addFormCheckBox(LabelType::ENABLE_SERIAL_PORT_CONSOLE, Settings.UseSerial); + addFormCheckBox(LabelType::ENABLE_SERIAL_PORT_CONSOLE); addFormNumericBox(F("Baud Rate"), F("baudrate"), Settings.BaudRate, 0, 1000000); #if FEATURE_DEFINE_SERIAL_CONSOLE_PORT @@ -294,7 +285,7 @@ void handle_advanced() { html_add_script(F("document.getElementById('serPort').onchange();"), false); #if USES_ESPEASY_CONSOLE_FALLBACK_PORT - addFormCheckBox(LabelType::CONSOLE_FALLBACK_TO_SERIAL0, Settings.console_serial0_fallback); + addFormCheckBox(LabelType::CONSOLE_FALLBACK_TO_SERIAL0); #endif #endif @@ -334,94 +325,84 @@ void handle_advanced() { #if defined(ESP32) addFormCheckBox_disabled(F("Enable RTOS Multitasking"), F("usertosmultitasking"), Settings.UseRTOSMultitasking); #endif // if defined(ESP32) + { + LabelType::Enum labels[]{ - addFormCheckBox(LabelType::JSON_BOOL_QUOTES, Settings.JSONBoolWithoutQuotes()); -#if FEATURE_TIMING_STATS - addFormCheckBox(LabelType::ENABLE_TIMING_STATISTICS, Settings.EnableTimingStats()); -#endif // if FEATURE_TIMING_STATS -#ifndef BUILD_NO_RAM_TRACKER - addFormCheckBox(LabelType::ENABLE_RAM_TRACKING, Settings.EnableRAMTracking()); -#endif + LabelType::JSON_BOOL_QUOTES + #if FEATURE_TIMING_STATS + ,LabelType::ENABLE_TIMING_STATISTICS + #endif // if FEATURE_TIMING_STATS + #ifndef BUILD_NO_RAM_TRACKER + ,LabelType::ENABLE_RAM_TRACKING + #endif - addFormCheckBox(LabelType::TASKVALUESET_ALL_PLUGINS, Settings.AllowTaskValueSetAllPlugins()); -#if FEATURE_CLEAR_I2C_STUCK - addFormCheckBox(LabelType::ENABLE_CLEAR_HUNG_I2C_BUS, Settings.EnableClearHangingI2Cbus()); -#endif - #if FEATURE_I2C_DEVICE_CHECK - addFormCheckBox(LabelType::ENABLE_I2C_DEVICE_CHECK, Settings.CheckI2Cdevice()); - #endif // if FEATURE_I2C_DEVICE_CHECK + ,LabelType::TASKVALUESET_ALL_PLUGINS + #if FEATURE_CLEAR_I2C_STUCK + ,LabelType::ENABLE_CLEAR_HUNG_I2C_BUS + #endif + #if FEATURE_I2C_DEVICE_CHECK + ,LabelType::ENABLE_I2C_DEVICE_CHECK + #endif // if FEATURE_I2C_DEVICE_CHECK - #if FEATURE_TASKVALUE_UNIT_OF_MEASURE - addFormCheckBox(LabelType::SHOW_UOM_ON_DEVICES_PAGE, Settings.ShowUnitOfMeasureOnDevicesPage()); - #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE + #if FEATURE_TASKVALUE_UNIT_OF_MEASURE + ,LabelType::SHOW_UOM_ON_DEVICES_PAGE + #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE - # ifndef NO_HTTP_UPDATER - addFormCheckBox(LabelType::ALLOW_OTA_UNLIMITED, Settings.AllowOTAUnlimited()); - # endif // ifndef NO_HTTP_UPDATER + #if FEATURE_MQTT_CONNECT_BACKGROUND + ,LabelType::MQTT_CONNECT_IN_BACKGROUND + #endif // if FEATURE_MQTT_CONNECT_BACKGROUND + + # ifndef NO_HTTP_UPDATER + ,LabelType::ALLOW_OTA_UNLIMITED + # endif // ifndef NO_HTTP_UPDATER + }; + + addFormCheckBoxes(labels, NR_ELEMENTS(labels)); + } #if FEATURE_AUTO_DARK_MODE - const __FlashStringHelper * cssModeNames[] = { - F("Auto"), - F("Light"), - F("Dark"), - }; - //const int cssModeOptions[] = { 0, 1, 2}; - constexpr int nrCssModeOptions = NR_ELEMENTS(cssModeNames); - const FormSelectorOptions selector( - nrCssModeOptions, - cssModeNames/*, - cssModeOptions*/); - selector.addFormSelector( - getLabel(LabelType::ENABLE_AUTO_DARK_MODE), - getInternalLabel(LabelType::ENABLE_AUTO_DARK_MODE), - Settings.getCssMode()); + { + const __FlashStringHelper * cssModeNames[] = { + F("Auto"), + F("Light"), + F("Dark"), + }; + //const int cssModeOptions[] = { 0, 1, 2}; + constexpr int nrCssModeOptions = NR_ELEMENTS(cssModeNames); + const FormSelectorOptions selector( + nrCssModeOptions, + cssModeNames/*, + cssModeOptions*/); + selector.addFormSelector( + getLabel(LabelType::ENABLE_AUTO_DARK_MODE), + getInternalLabel(LabelType::ENABLE_AUTO_DARK_MODE), + Settings.getCssMode()); + } #endif // FEATURE_AUTO_DARK_MODE +{ + LabelType::Enum labels[]{ + LabelType::CPU_ECO_MODE + #ifdef ESP8266 + ,LabelType::DEEP_SLEEP_ALTERNATIVE_CALL + #endif #if FEATURE_RULES_EASY_COLOR_CODE - addFormCheckBox(LabelType::DISABLE_RULES_AUTOCOMPLETE, Settings.DisableRulesCodeCompletion()); + ,LabelType::DISABLE_RULES_AUTOCOMPLETE #endif // if FEATURE_RULES_EASY_COLOR_CODE #if FEATURE_TARSTREAM_SUPPORT - addFormCheckBox(LabelType::DISABLE_SAVE_CONFIG_AS_TAR, Settings.DisableSaveConfigAsTar()); + ,LabelType::DISABLE_SAVE_CONFIG_AS_TAR #endif // if FEATURE_TARSTREAM_SUPPORT - #ifdef ESP8266 - addFormCheckBox(LabelType::DEEP_SLEEP_ALTERNATIVE_CALL, Settings.UseAlternativeDeepSleep()); - #endif - - +#if FEATURE_USE_IPV6 + ,LabelType::ENABLE_IPV6 +#endif + }; + addFormCheckBoxes(labels, NR_ELEMENTS(labels)); +} #if FEATURE_SSDP addFormCheckBox_disabled(F("Use SSDP"), F("usessdp"), Settings.UseSSDP); #endif // if FEATURE_SSDP - addFormNumericBox(LabelType::CONNECTION_FAIL_THRESH, Settings.ConnectionFailuresThreshold, 0, 100); - addFormCheckBox(LabelType::FORCE_WIFI_BG, Settings.ForceWiFi_bg_mode()); - - addFormCheckBox(LabelType::RESTART_WIFI_LOST_CONN, Settings.WiFiRestart_connection_lost()); - addFormCheckBox(LabelType::FORCE_WIFI_NOSLEEP, Settings.WifiNoneSleep()); -#ifdef SUPPORT_ARP - addFormCheckBox(LabelType::PERIODICAL_GRAT_ARP, Settings.gratuitousARP()); -#endif // ifdef SUPPORT_ARP - addFormCheckBox(LabelType::CPU_ECO_MODE, Settings.EcoPowerMode()); -#if FEATURE_SET_WIFI_TX_PWR - addFormFloatNumberBox(LabelType::WIFI_TX_MAX_PWR, Settings.getWiFi_TX_power(), 0.0f, MAX_TX_PWR_DBM_11b, 2, 0.25f); - addFormNumericBox(LabelType::WIFI_SENS_MARGIN, Settings.WiFi_sensitivity_margin, -20, 30); - addFormCheckBox(LabelType::WIFI_SEND_AT_MAX_TX_PWR, Settings.UseMaxTXpowerForSending()); -#endif - { - addFormNumericBox(LabelType::WIFI_NR_EXTRA_SCANS, Settings.NumberExtraWiFiScans, 0, 5); - } - addFormCheckBox(LabelType::WIFI_USE_LAST_CONN_FROM_RTC, Settings.UseLastWiFiFromRTC()); - -#ifndef ESP32 - addFormCheckBox(LabelType::WAIT_WIFI_CONNECT, Settings.WaitWiFiConnect()); -#endif - addFormCheckBox(LabelType::SDK_WIFI_AUTORECONNECT, Settings.SDK_WiFi_autoreconnect()); - addFormCheckBox(LabelType::HIDDEN_SSID_SLOW_CONNECT, Settings.HiddenSSID_SlowConnectPerBSSID()); -#ifdef ESP32 - addFormCheckBox(LabelType::WIFI_PASSIVE_SCAN, Settings.PassiveWiFiScan()); -#endif -#if FEATURE_USE_IPV6 - addFormCheckBox(LabelType::ENABLE_IPV6, Settings.EnableIPv6()); -#endif + addFormNumericBox(LabelType::CONNECTION_FAIL_THRESH, 0, 100); @@ -511,19 +492,10 @@ void addFormLogLevelSelect(LabelType::Enum label, int choice) #endif addRowLabel(getLabel(label)); - const __FlashStringHelper * options[LOG_LEVEL_NRELEMENTS + 1]; - int optionValues[LOG_LEVEL_NRELEMENTS + 1] = { 0 }; - - options[0] = getLogLevelDisplayString(0); - - for (int i = 0; i < LOG_LEVEL_NRELEMENTS; ++i) { - options[i + 1] = getLogLevelDisplayStringFromIndex(i, optionValues[i + 1]); - } - const FormSelectorOptions selector(LOG_LEVEL_NRELEMENTS + 1, options, optionValues); - selector.addSelector(getInternalLabel(label), choice); - + addLogLevelFormSelectorOptions(getInternalLabel(label), choice); } +#if FEATURE_SYSLOG void addFormLogFacilitySelect(const __FlashStringHelper * label, const __FlashStringHelper * id, int choice) { addRowLabel(label); @@ -535,5 +507,6 @@ void addFormLogFacilitySelect(const __FlashStringHelper * label, const __FlashSt const FormSelectorOptions selector(NR_ELEMENTS(options), options, optionValues); selector.addSelector(id, choice); } +#endif #endif // ifdef WEBSERVER_ADVANCED diff --git a/src/src/WebServer/AdvancedConfigPage.h b/src/src/WebServer/AdvancedConfigPage.h index e1b26c23c..b5988c882 100644 --- a/src/src/WebServer/AdvancedConfigPage.h +++ b/src/src/WebServer/AdvancedConfigPage.h @@ -19,7 +19,9 @@ void addFormExtTimeSourceSelect(const __FlashStringHelper * label, const __Flash void addFormLogLevelSelect(LabelType::Enum label, int choice); +#if FEATURE_SYSLOG void addFormLogFacilitySelect(const __FlashStringHelper * label, const __FlashStringHelper * id, int choice); +#endif #endif // ifdef WEBSERVER_ADVANCED diff --git a/src/src/WebServer/CacheControllerPages.cpp b/src/src/WebServer/CacheControllerPages.cpp index 926ea6d6d..655931c7e 100644 --- a/src/src/WebServer/CacheControllerPages.cpp +++ b/src/src/WebServer/CacheControllerPages.cpp @@ -19,7 +19,6 @@ # include "../Helpers/ESPEasy_time_calc.h" # include "../Helpers/Misc.h" - // ******************************************************************************** // URLs needed for C016_CacheController // to help dump the content of the binary log files @@ -73,9 +72,9 @@ void handle_dumpcache() { ESPEasyControllerCache_CSV_dumper dumper( - joinTimestamp, - onlySetTasks, - separator, + joinTimestamp, + onlySetTasks, + separator, ESPEasyControllerCache_CSV_dumper::Target::CSV_file); dumper.generateCSVHeader(true); @@ -94,64 +93,66 @@ void handle_cache_json() { C016_flush(); TXBuffer.startJsonStream(); - addHtml(F("{\"columns\": [")); + { + KeyValueWriter_JSON mainLevelWriter(true); + { + auto writer = mainLevelWriter.createChildArray(F("columns")); - // addHtml(F("UNIX timestamp;contr. idx;sensortype;taskindex;value count")); - addHtml(to_json_value(F("UNIX timestamp"))); - addHtml(','); - addHtml(to_json_value(F("UTC timestamp"))); - addHtml(','); - addHtml(to_json_value(F("task index"))); + if (writer) { + // addHtml(F("UNIX timestamp;contr. idx;sensortype;taskindex;value count")); + writer->write({ EMPTY_STRING, F("UNIX timestamp") }); + writer->write({ EMPTY_STRING, F("UTC timestamp") }); + writer->write({ EMPTY_STRING, F("task index") }); - if (hasArg(F("pluginID"))) { - addHtml(','); - addHtml(to_json_value(F("plugin ID"))); - } + if (hasArg(F("pluginID"))) { + writer->write({ EMPTY_STRING, F("plugin ID") }); + } - for (taskIndex_t i = 0; i < TASKS_MAX; ++i) { - for (int j = 0; j < VARS_PER_TASK; ++j) { - String label = getTaskDeviceName(i); - label += '#'; - label += getTaskValueName(i, j); - addHtml(','); - addHtml(to_json_value(label)); - } - } - addHtml(F("],\n")); - addHtml(F("\"files\": [")); - bool islast = false; - int filenr = 0; - int fileCount = 0; - - while (!islast) { - const String currentFile = C016_getCacheFileName(filenr, islast); - ++filenr; - - if (currentFile.length() > 0) { - if (fileCount != 0) { - addHtml(','); + for (taskIndex_t i = 0; i < TASKS_MAX; ++i) { + for (int j = 0; j < VARS_PER_TASK; ++j) { + writer->write({ EMPTY_STRING, strformat( + F("%s#%s"), + getTaskDeviceName(i).c_str(), + getTaskValueName(i, j).c_str()) }); + } + } } - addHtml(to_json_value(currentFile)); - ++fileCount; } - } - addHtml(F("],\n")); - addHtml(F("\"pluginID\": [")); + int fileCount = 0; + { + auto writer = mainLevelWriter.createChildArray(F("files")); - for (taskIndex_t taskIndex = 0; validTaskIndex(taskIndex); ++taskIndex) { - if (taskIndex != 0) { - addHtml(','); + if (writer) { + bool islast = false; + int filenr = 0; + + while (!islast) { + const String currentFile = C016_getCacheFileName(filenr, islast); + ++filenr; + + if (currentFile.length() > 0) { + ++fileCount; + writer->write({ EMPTY_STRING, currentFile }); + } + } + } } - addHtmlInt(getPluginID_from_TaskIndex(taskIndex).value); + { + auto writer = mainLevelWriter.createChildArray(F("pluginID")); + + if (writer) { + for (taskIndex_t taskIndex = 0; validTaskIndex(taskIndex); ++taskIndex) { + writer->write({ EMPTY_STRING, getPluginID_from_TaskIndex(taskIndex).value }); + } + } + } + mainLevelWriter.write({ F("separator"), F(";") }); + mainLevelWriter.write({ F("nrfiles"), fileCount }); } - addHtml(F("],\n")); - stream_next_json_object_value(F("separator"), F(";")); - stream_last_json_object_value(F("nrfiles"), fileCount); - addHtml('\n'); TXBuffer.endStream(); } -void handle_cache_csv() { +void handle_cache_csv() { if (!isLoggedIn()) { return; } } diff --git a/src/src/WebServer/Chart_JS.cpp b/src/src/WebServer/Chart_JS.cpp index 0990f7228..a3bd845fc 100644 --- a/src/src/WebServer/Chart_JS.cpp +++ b/src/src/WebServer/Chart_JS.cpp @@ -2,242 +2,240 @@ #if FEATURE_CHART_JS +# include "../Helpers/KeyValueWriter_JSON.h" # include "../Helpers/StringConverter.h" # include "../WebServer/HTML_wrappers.h" # include "../WebServer/JSON.h" -void add_ChartJS_array(int valueCount, - const String array[]) +void add_ChartJS_array(KeyValueWriter& parent, + int valueCount, + const String array[]) { for (int i = 0; i < valueCount; ++i) { - if (i != 0) { - addHtml(',', '\n'); - } - addHtml(to_json_value(array[i])); + parent.write({ EMPTY_STRING, array[i] }); } } -void add_ChartJS_array(int valueCount, - const float array[], - unsigned int nrDecimals) +void add_ChartJS_array(KeyValueWriter& parent, + int valueCount, + const float array[], + uint8_t nrDecimals) { for (int i = 0; i < valueCount; ++i) { - if (i != 0) { - addHtml(',', '\n'); - } - addHtmlFloat_NaN_toNull(array[i], nrDecimals); + parent.write({ EMPTY_STRING, array[i], nrDecimals }); } } -void add_ChartJS_array(int valueCount, - const int array[]) +void add_ChartJS_array(KeyValueWriter& parent, + int valueCount, + const int array[]) { for (int i = 0; i < valueCount; ++i) { - if (i != 0) { - addHtml(',', '\n'); - } - addHtmlInt(array[i]); + parent.write({ EMPTY_STRING, array[i] }); } } -void add_ChartJS_chart_header( +UP_KeyValueWriter add_ChartJS_chart_header( const __FlashStringHelper *chartType, const __FlashStringHelper *id, const ChartJS_title & chartTitle, - int width, - int height, - const String & options, + ChartJS_options_scales & options, bool enableZoom, size_t nrSamples, bool onlyJSON) { - add_ChartJS_chart_header( + return add_ChartJS_chart_header( chartType, String(id), chartTitle, - width, - height, options, enableZoom, nrSamples, onlyJSON); } -void add_ChartJS_chart_header( +UP_KeyValueWriter add_ChartJS_chart_header( const __FlashStringHelper *chartType, const String & id, const ChartJS_title & chartTitle, - int width, - int height, - const String & options, + ChartJS_options_scales & options, bool enableZoom, size_t nrSamples, bool onlyJSON) { if (!onlyJSON) { + addHtml(F("
")); addHtml(F("")); + addHtml(F("
")); const char *id_c_str = id.c_str(); addHtml(strformat( F("")); + } + + add_ChartJS_chart_JSON_header( + *chartJSON, + chartType, + chartTitle, + options, + nrSamples, + enableZoom); } - add_ChartJS_chart_JSON_header( - chartType, - plugins, - chartTitle, - options, - nrSamples); + + return std::move(chartJSON); } void add_ChartJS_chart_JSON_header( + KeyValueWriter_JSON & parent, const __FlashStringHelper *chartType, - const String & plugins, const ChartJS_title & chartTitle, - const String & options, - size_t nrSamples) + ChartJS_options_scales & options, + size_t nrSamples, + bool enableZoom) { - addHtml(F("{\"type\":\"")); - addHtml(chartType); - addHtml(F("\",\"options\":{" - "\"responsive\":false,\"plugins\":{")); - addHtml(F("\"legend\":{" - "\"position\":\"top\"" - "},\"title\":")); - addHtml(chartTitle.toString()); + parent.write({ F("type"), chartType }); + auto optionsArr = parent.createChild(F("options")); - if (plugins.length() > 0) { - addHtml(','); + if (optionsArr) { + const bool b_true(true); + const bool b_false(false); + optionsArr->write({ F("responsive"), b_true }); + optionsArr->write({ F("maintainAspectRatio"), b_false }); + { + auto plugins = optionsArr->createChild(F("plugins")); + + if (plugins) { + { + auto legend = plugins->createChild(F("legend")); + + if (legend) { + legend->write({ F("position"), F("top") }); + } + } + plugins->write({ F("title"), chartTitle.toString() }); + + if (enableZoom) { + plugins->write({ + F("zoom"), + F("{\"limits\":{" + "\"x\":{\"min\":\"original\",\"max\":\"original\",\"minRange\":1000}}," + "\"pan\":{\"enabled\":true,\"mode\":\"x\",\"modifierKey\":\"ctrl\"}," + "\"zoom\":{" + "\"wheel\":{\"enabled\":true}," + "\"drag\":{\"enabled\":true}," + "\"pinch\":{\"enabled\":true}," + "\"mode\":\"x\"}}" + ) }); + } + + } + } + + if (nrSamples >= 60) { + // Default point radius = 3 + // Typically when having > 64 samples, these points become really cluttered + // Thus it is best to reduce their radius. + const float radius = (enableZoom) ? 2.5f : 2.0f; + optionsArr->write({ F("elements"), + strformat( + F("{\"point\":{\"radius\":%.1f}}"), + radius) + }); + } + options.toString(*optionsArr); } - addHtml(plugins); - addHtml('}'); // end plugins - - if (nrSamples >= 60) { - // Default point radius = 3 - // Typically when having > 64 samples, these points become really cluttered - // Thus it is best to reduce their radius. - const float radius = (plugins.length() > 0) ? 2.5f : 2.0f; - addHtml(strformat( - F(",\"elements\":{\"point\":{\"radius\":%.1f}}"), - radius)); - } - - if (!options.isEmpty()) { - addHtml(',', '\n'); - addHtml(options); - } - - addHtml(F("}," // end options - "\n\"data\":{")); } void add_ChartJS_chart_labels( - int valueCount, - const int labels[]) + KeyValueWriter& parent, + int valueCount, + const int labels[]) { - addHtml(F("\n\"labels\":[")); - add_ChartJS_array(valueCount, labels); - addHtml(F("],\n\"datasets\":[")); + auto labelsWriter = parent.createChildArray(F("labels")); + + if (labelsWriter) { + add_ChartJS_array(*labelsWriter, valueCount, labels); + } } void add_ChartJS_chart_labels( - int valueCount, - const String labels[]) + KeyValueWriter& parent, + int valueCount, + const String labels[]) { - addHtml(F("\n\"labels\":[")); - add_ChartJS_array(valueCount, labels); - addHtml(F("],\n\"datasets\":[")); + auto labelsWriter = parent.createChildArray(F("labels")); + + if (labelsWriter) { + add_ChartJS_array(*labelsWriter, valueCount, labels); + } } -void add_ChartJS_scatter_data_point(float x, float y, int nrDecimals) +void add_ChartJS_scatter_data_point( + KeyValueWriter& parent, + float x, float y, uint8_t nrDecimalsX, uint8_t nrDecimalsY) { - addHtml(F("{\"x\":")); - addHtmlFloat_NaN_toNull(x, nrDecimals); - addHtml(F(",\"y\":")); - addHtmlFloat_NaN_toNull(y, nrDecimals); - addHtml('}', ','); + auto element = parent.createChild(); + + if (element) { + element->write({ F("x"), x, nrDecimalsX }); + element->write({ F("y"), y, nrDecimalsY }); + } } void add_ChartJS_dataset( + KeyValueWriter & datasets, const ChartJS_dataset_config& config, const float values[], int valueCount, - unsigned int nrDecimals, + uint8_t nrDecimals, const String & options) { - add_ChartJS_dataset_header(config); - add_ChartJS_array(valueCount, values, nrDecimals); - add_ChartJS_dataset_footer(options); + auto dataset = datasets.createChild(); + + if (dataset) { + auto data = add_ChartJS_dataset_header(*dataset, config); + + if (data) { + add_ChartJS_array(*data, valueCount, values, nrDecimals); + } + } } -void add_ChartJS_dataset_header(const ChartJS_dataset_config& config) +UP_KeyValueWriter add_ChartJS_dataset_header(KeyValueWriter& dataset, const ChartJS_dataset_config& config) { - addHtml('{'); - if (!config.label.isEmpty()) { - stream_to_json_object_value(F("label"), config.label); - addHtml(','); + dataset.write({ F("label"), config.label }); } if (!config.color.isEmpty()) { - stream_to_json_object_value(F("backgroundColor"), config.color); - addHtml(','); - stream_to_json_object_value(F("borderColor"), config.color); - addHtml(','); + dataset.write({ F("backgroundColor"), config.color }); + dataset.write({ F("borderColor"), config.color }); } if (!config.axisID.isEmpty()) { - stream_to_json_object_value(F("yAxisID"), config.axisID); - addHtml(','); + dataset.write({ F("yAxisID"), config.axisID }); } if (config.hidden || config.displayConfig.showHidden()) { - addHtml(F("\"hidden\":true,")); + dataset.write({ F("hidden"), true }); } - addHtml(F("\n\"data\":[\n")); -} - -void add_ChartJS_dataset_footer(const String& options) { - addHtml(']'); - - if (!options.isEmpty()) { - addHtml(',', '\n'); - addHtml(options); - } - - addHtml('}', '\n'); -} - -void add_ChartJS_chart_footer(bool onlyJSON) { - addHtml(F("]}}")); - - if (!onlyJSON) { - addHtml(F(");")); - } + return dataset.createChildArray(F("data")); } #endif // if FEATURE_CHART_JS diff --git a/src/src/WebServer/Chart_JS.h b/src/src/WebServer/Chart_JS.h index 73ae441b4..3cdf2902e 100644 --- a/src/src/WebServer/Chart_JS.h +++ b/src/src/WebServer/Chart_JS.h @@ -10,7 +10,32 @@ // - add_ChartJS_chart_header // - add_ChartJS_chart_labels // - add_ChartJS_dataset (1x or more) -// - add_ChartJS_chart_footer +// +// Typical JSON layout: +// { +// "type": "line", +// "options": { +// ... +// }, +// "data": { +// "labels": [ +// ... +// ], +// "datasets": [ +// { +// "label": "Error %", +// "backgroundColor": "rgb(255, 99, 132)", +// "borderColor": "rgb(255, 99, 132)", +// "data": [ +// ... +// ] +// } +// ] +// } +// } +// +// Make sure the KeyValueWriter objects are in their own scope, +// since the closing braces are written from their destructor. // // Split into several parts so a long array of // values can also be served directly @@ -19,64 +44,70 @@ #if FEATURE_CHART_JS +# include "../Helpers/KeyValueWriter.h" +# include "../Helpers/KeyValueWriter_JSON.h" # include "../WebServer/Chart_JS_scale.h" # include "../DataStructs/ChartJS_dataset_config.h" -void add_ChartJS_chart_header( +UP_KeyValueWriter add_ChartJS_chart_header( const __FlashStringHelper *chartType, const __FlashStringHelper *id, const ChartJS_title & chartTitle, - int width, - int height, - const String & options = EMPTY_STRING, + ChartJS_options_scales & options, bool enableZoom = false, - size_t nrSamples = 0, - bool onlyJSON = false); + size_t nrSamples = 0, + bool onlyJSON = false); -void add_ChartJS_chart_header( +UP_KeyValueWriter add_ChartJS_chart_header( const __FlashStringHelper *chartType, const String & id, const ChartJS_title & chartTitle, - int width, - int height, - const String & options = EMPTY_STRING, + ChartJS_options_scales & options, bool enableZoom = false, - size_t nrSamples = 0, - bool onlyJSON = false); + size_t nrSamples = 0, + bool onlyJSON = false); void add_ChartJS_chart_JSON_header( + KeyValueWriter_JSON & parent, const __FlashStringHelper *chartType, - const String & plugins, const ChartJS_title & chartTitle, - const String & options, - size_t nrSamples); + ChartJS_options_scales & options, + size_t nrSamples, + bool enableZoom + ); void add_ChartJS_chart_labels( - int valueCount, - const int labels[]); + KeyValueWriter& parent, + int valueCount, + const int labels[]); void add_ChartJS_chart_labels( - int valueCount, - const String labels[]); + KeyValueWriter& parent, + int valueCount, + const String labels[]); -void add_ChartJS_scatter_data_point(float x, - float y, - int nrDecimals); +void add_ChartJS_scatter_data_point( + KeyValueWriter& parent, + float x, + float y, + uint8_t nrDecimalsX, + uint8_t nrDecimalsY); void add_ChartJS_dataset( + KeyValueWriter & datasets, const ChartJS_dataset_config& config, const float values[], int valueCount, - unsigned int nrDecimals = 3, + uint8_t nrDecimals = 3, const String & options = EMPTY_STRING); -void add_ChartJS_dataset_header(const ChartJS_dataset_config& config); -void add_ChartJS_dataset_footer(const String& options = EMPTY_STRING); +UP_KeyValueWriter add_ChartJS_dataset_header( + KeyValueWriter & dataset, + const ChartJS_dataset_config& config); -void add_ChartJS_chart_footer(bool onlyJSON = false); #endif // if FEATURE_CHART_JS #endif // ifndef WEBSERVER_CHART_JS_H diff --git a/src/src/WebServer/Chart_JS_scale.cpp b/src/src/WebServer/Chart_JS_scale.cpp index 010b74abe..5f940c42f 100644 --- a/src/src/WebServer/Chart_JS_scale.cpp +++ b/src/src/WebServer/Chart_JS_scale.cpp @@ -32,7 +32,7 @@ ChartJS_options_scale::ChartJS_options_scale(const PluginStats_Config_t& config, display = Display::Auto; } -String ChartJS_options_scale::toString() const +void ChartJS_options_scale::toString(KeyValueWriter& parent) const { if (!axisID.isEmpty()) { // In JSON, boolean values do not need quotes @@ -46,34 +46,44 @@ String ChartJS_options_scale::toString() const String positionStr; - switch (position) { - case Position::Top: positionStr = F("top"); break; - case Position::Bottom: positionStr = F("bottom"); break; - case Position::Right: positionStr = F("right"); break; - case Position::Center: positionStr = F("center"); break; - case Position::Left: positionStr = F("left"); break; + switch (position) + { + case Position::Top: positionStr = F("top"); + break; + case Position::Bottom: positionStr = F("bottom"); + break; + case Position::Right: positionStr = F("right"); + break; + case Position::Center: positionStr = F("center"); + break; + case Position::Left: positionStr = F("left"); + break; } String extraOptions; + if (typeStr.equalsIgnoreCase(F("time")) || typeStr.equalsIgnoreCase(F("timeseries"))) { // Make sure to use 24h time notation. - extraOptions += F(",\"time\":{\"displayFormats\":{\"millisecond\":\"HH:mm:ss.SSS\",\"second\":\"HH:mm:ss\",\"minute\":\"HH:mm:ss\",\"hour\":\"HH:mm\",\"day\":\"dd-MMM\",\"month\":\"MMM-yyyy\",\"year\":\"yyyy\"},\"tooltipFormat\":\"yyyy-MM-dd HH:mm:ss\"}"); + extraOptions += + F( + ",\"time\":{\"displayFormats\":{\"millisecond\":\"HH:mm:ss.SSS\",\"second\":\"HH:mm:ss\",\"minute\":\"HH:mm:ss\",\"hour\":\"HH:mm\",\"day\":\"dd-MMM\",\"month\":\"MMM-yyyy\",\"year\":\"yyyy\"},\"tooltipFormat\":\"yyyy-MM-dd HH:mm:ss\"}"); } if (tickCount > 0) { extraOptions += strformat(F(",\"ticks\":{\"count\":%d}"), tickCount); } - return strformat( - F("\"%s\":{\"display\":%s,\"type\":\"%s\",\"position\":\"%s\",\"title\":%s,\"weight\":%d%s}"), - axisID.c_str(), + parent.write({ + axisID, + strformat( + F("{\"display\":%s,\"type\":\"%s\",\"position\":\"%s\",\"title\":%s,\"weight\":%d%s}"), displayStr.c_str(), typeStr.c_str(), positionStr.c_str(), axisTitle.toString().c_str(), weight, - extraOptions.c_str()); + extraOptions.c_str()) + }); } - return EMPTY_STRING; } bool ChartJS_options_scale::is_Y_axis() const @@ -116,27 +126,16 @@ void ChartJS_options_scales::update_Yaxis_TickCount() } } -String ChartJS_options_scales::toString() const +void ChartJS_options_scales::toString(KeyValueWriter& parent) const { - if (_scales.empty()) { return EMPTY_STRING; } + if (_scales.empty()) { return; } + auto scales = parent.createChild(F("scales")); - String res = F("\"scales\":{"); - bool first = true; - - for (auto it = _scales.begin(); it != _scales.end(); ++it) { - const String scale_str = it->toString(); - - if (!scale_str.isEmpty()) { - if (!first) { - res += ','; - } - first = false; - res += '\n'; - res += scale_str; + if (scales) { + for (auto it = _scales.begin(); it != _scales.end(); ++it) { + it->toString(*scales); } } - res += '}'; - return res; } size_t ChartJS_options_scales::nr_Y_scales() const diff --git a/src/src/WebServer/Chart_JS_scale.h b/src/src/WebServer/Chart_JS_scale.h index e19c90ccb..45564711b 100644 --- a/src/src/WebServer/Chart_JS_scale.h +++ b/src/src/WebServer/Chart_JS_scale.h @@ -7,6 +7,7 @@ #if FEATURE_CHART_JS # include "../DataStructs/PluginStats_Config.h" +# include "../Helpers/KeyValueWriter.h" # include "../WebServer/Chart_JS_title.h" # include @@ -45,7 +46,7 @@ struct ChartJS_options_scale { int tickCount{}; int weight{}; - String toString() const; + void toString(KeyValueWriter& parent) const; bool is_Y_axis() const; }; @@ -57,7 +58,7 @@ struct ChartJS_options_scales { void update_Yaxis_TickCount(); - String toString() const; + void toString(KeyValueWriter & parent) const; size_t nr_Y_scales() const; diff --git a/src/src/WebServer/ConfigPage.cpp b/src/src/WebServer/ConfigPage.cpp index d02a1d23e..d7e73ce1f 100644 --- a/src/src/WebServer/ConfigPage.cpp +++ b/src/src/WebServer/ConfigPage.cpp @@ -15,7 +15,8 @@ #endif #include "../ESPEasyCore/Controller.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../../ESPEasy/net/ESPEasyNetwork.h" +#include "../../ESPEasy/net/Helpers/NWAccessControl.h" #include "../Globals/MQTT.h" #include "../Globals/Nodes.h" @@ -52,7 +53,7 @@ void handle_config() { Settings.Unit = getFormItemInt(F("unit"), Settings.Unit); if (strcmp(Settings.Name, name.c_str()) != 0) { - #ifndef BUILD_MINIMAL_OTA + #ifndef LIMIT_BUILD_SIZE addLog(LOG_LEVEL_INFO, F("Unit Name changed.")); #endif @@ -73,53 +74,17 @@ void handle_config() { // Password copyFormPassword(F("password"), SecuritySettings.Password, sizeof(SecuritySettings.Password)); - // SSID 1 - safe_strncpy(SecuritySettings.WifiSSID, webArg(F("ssid")).c_str(), sizeof(SecuritySettings.WifiSSID)); - copyFormPassword(F("key"), SecuritySettings.WifiKey, sizeof(SecuritySettings.WifiKey)); - - // SSID 2 - strncpy_webserver_arg(SecuritySettings.WifiSSID2, F("ssid2")); - copyFormPassword(F("key2"), SecuritySettings.WifiKey2, sizeof(SecuritySettings.WifiKey2)); - - // Hidden SSID - Settings.IncludeHiddenSSID(isFormItemChecked(LabelType::CONNECT_HIDDEN_SSID)); - Settings.HiddenSSID_SlowConnectPerBSSID(isFormItemChecked(LabelType::HIDDEN_SSID_SLOW_CONNECT)); - -#ifdef ESP32 - Settings.PassiveWiFiScan(isFormItemChecked(LabelType::WIFI_PASSIVE_SCAN)); -#endif - - // Access point password. - copyFormPassword(F("apkey"), SecuritySettings.WifiAPKey, sizeof(SecuritySettings.WifiAPKey)); - - // When set you can use the Sensor in AP-Mode without being forced to /setup - Settings.ApDontForceSetup(isFormItemChecked(F("ApDontForceSetup"))); - - // Usually the AP will be started when no WiFi is defined, or the defined one cannot be found. This flag may prevent it. - Settings.DoNotStartAP(isFormItemChecked(F("DoNotStartAP"))); - - // TD-er Read access control from form. SecuritySettings.IPblockLevel = getFormItemInt(F("ipblocklevel")); - switch (SecuritySettings.IPblockLevel) { - case LOCAL_SUBNET_ALLOWED: - { - IPAddress low, high; - getSubnetRange(low, high); - - for (uint8_t i = 0; i < 4; ++i) { - SecuritySettings.AllowedIPrangeLow[i] = low[i]; - SecuritySettings.AllowedIPrangeHigh[i] = high[i]; - } - break; - } - case ONLY_IP_RANGE_ALLOWED: - case ALL_ALLOWED: - + if (SecuritySettings.IPblockLevel == ONLY_IP_RANGE_ALLOWED) { webArg2ip(F("iprangelow"), SecuritySettings.AllowedIPrangeLow); webArg2ip(F("iprangehigh"), SecuritySettings.AllowedIPrangeHigh); - break; + } else { + for (size_t i = 0; i < 4; ++i) { + SecuritySettings.AllowedIPrangeLow[i] = 0; + SecuritySettings.AllowedIPrangeHigh[i] = 255; + } } #ifdef USES_ESPEASY_NOW @@ -143,16 +108,6 @@ void handle_config() { #endif Settings.deepSleepOnFail = isFormItemChecked(F("deepsleeponfail")); - webArg2ip(F("espip"), Settings.IP); - webArg2ip(F("espgateway"), Settings.Gateway); - webArg2ip(F("espsubnet"), Settings.Subnet); - webArg2ip(F("espdns"), Settings.DNS); -#if FEATURE_ETHERNET - webArg2ip(F("espethip"), Settings.ETH_IP); - webArg2ip(F("espethgateway"), Settings.ETH_Gateway); - webArg2ip(F("espethsubnet"), Settings.ETH_Subnet); - webArg2ip(F("espethdns"), Settings.ETH_DNS); -#endif // if FEATURE_ETHERNET #if FEATURE_ALTERNATIVE_CDN_URL set_CDN_url_custom(webArg(F("alturl"))); #endif // if FEATURE_ALTERNATIVE_CDN_URL @@ -167,77 +122,22 @@ void handle_config() { Settings.Name[25] = 0; SecuritySettings.Password[25] = 0; addFormTextBox(F("Unit Name"), F("name"), Settings.Name, 25); - addFormNote(concat(F("Hostname: "), NetworkCreateRFCCompliantHostname())); + addFormNote(concat(F("Hostname: "), ESPEasy::net::NetworkCreateRFCCompliantHostname())); addFormNumericBox(F("Unit Number"), F("unit"), Settings.Unit, 0, UNIT_NUMBER_MAX); addFormCheckBox(F("Append Unit Number to hostname"), F("appendunittohostname"), Settings.appendUnitToHostname()); addFormPasswordBox(F("Admin Password"), F("password"), SecuritySettings.Password, 25); - addFormSubHeader(F("Wifi Settings")); - - addFormTextBox(getLabel(LabelType::SSID), F("ssid"), SecuritySettings.WifiSSID, 31); - addFormPasswordBox(F("WPA Key"), F("key"), SecuritySettings.WifiKey, 63); - addFormTextBox(F("Fallback SSID"), F("ssid2"), SecuritySettings.WifiSSID2, 31); - addFormPasswordBox(F("Fallback WPA Key"), F("key2"), SecuritySettings.WifiKey2, 63); - addFormNote(F("WPA Key must be at least 8 characters long")); - - addFormCheckBox(LabelType::CONNECT_HIDDEN_SSID, Settings.IncludeHiddenSSID()); - -#ifdef ESP32 - addFormCheckBox(LabelType::WIFI_PASSIVE_SCAN, Settings.PassiveWiFiScan()); -#endif - - addFormCheckBox(LabelType::HIDDEN_SSID_SLOW_CONNECT, Settings.HiddenSSID_SlowConnectPerBSSID()); - - addFormSeparator(2); - addFormPasswordBox(F("WPA AP Mode Key"), F("apkey"), SecuritySettings.WifiAPKey, 63); - addFormNote(F("WPA Key must be at least 8 characters long")); - - addFormCheckBox(F("Don't force /setup in AP-Mode"), F("ApDontForceSetup"), Settings.ApDontForceSetup()); - addFormNote(F("When set you can use the Sensor in AP-Mode without being forced to /setup. /setup can still be called.")); - - addFormCheckBox(F("Do Not Start AP"), F("DoNotStartAP"), Settings.DoNotStartAP()); - #if FEATURE_ETHERNET - addFormNote(F("Do not allow to start an AP when unable to connect to configured LAN/WiFi")); - #else // if FEATURE_ETHERNET - addFormNote(F("Do not allow to start an AP when configured WiFi cannot be found")); - #endif // if FEATURE_ETHERNET - - // TD-er add IP access box F("ipblocklevel") addFormSubHeader(F("Client IP filtering")); { - IPAddress low, high; - getIPallowedRange(low, high); - uint8_t iplow[4]; - uint8_t iphigh[4]; - - for (uint8_t i = 0; i < 4; ++i) { - iplow[i] = low[i]; - iphigh[i] = high[i]; - } addFormIPaccessControlSelect(F("Client IP block level"), F("ipblocklevel"), SecuritySettings.IPblockLevel); - addFormIPBox(F("Access IP lower range"), F("iprangelow"), iplow); - addFormIPBox(F("Access IP upper range"), F("iprangehigh"), iphigh); + + if (SecuritySettings.IPblockLevel == ONLY_IP_RANGE_ALLOWED) { + addFormIPBox(F("Access IP lower range"), F("iprangelow"), SecuritySettings.AllowedIPrangeLow); + addFormIPBox(F("Access IP upper range"), F("iprangehigh"), SecuritySettings.AllowedIPrangeHigh); + } } - addFormSubHeader(F("WiFi IP Settings")); - - addFormIPBox(F("ESP WiFi IP"), F("espip"), Settings.IP); - addFormIPBox(F("ESP WiFi Gateway"), F("espgateway"), Settings.Gateway); - addFormIPBox(F("ESP WiFi Subnetmask"), F("espsubnet"), Settings.Subnet); - addFormIPBox(F("ESP WiFi DNS"), F("espdns"), Settings.DNS); - addFormNote(F("Leave empty for DHCP")); - -#if FEATURE_ETHERNET - addFormSubHeader(F("Ethernet IP Settings")); - - addFormIPBox(F("ESP Ethernet IP"), F("espethip"), Settings.ETH_IP); - addFormIPBox(F("ESP Ethernet Gateway"), F("espethgateway"), Settings.ETH_Gateway); - addFormIPBox(F("ESP Ethernet Subnetmask"), F("espethsubnet"), Settings.ETH_Subnet); - addFormIPBox(F("ESP Ethernet DNS"), F("espethdns"), Settings.ETH_DNS); - addFormNote(F("Leave empty for DHCP")); -#endif // if FEATURE_ETHERNET - #ifdef USES_ESPEASY_NOW addFormSubHeader(F("ESPEasy-NOW")); for (int peer = 0; peer < ESPEASY_NOW_PEER_MAX; ++peer) { diff --git a/src/src/WebServer/ControllerPage.cpp b/src/src/WebServer/ControllerPage.cpp index 8f0ad00cb..3a58ef9dd 100644 --- a/src/src/WebServer/ControllerPage.cpp +++ b/src/src/WebServer/ControllerPage.cpp @@ -29,7 +29,6 @@ # include "../Helpers/_CPlugin_Helper_webform.h" # include "../Helpers/_Plugin_SensorTypeHelper.h" # include "../Helpers/ESPEasy_Storage.h" -# include "../Helpers/StringConverter.h" // ******************************************************************************** // Web Interface controller page @@ -45,14 +44,14 @@ void handle_controllers() { sendHeadandTail_stdtemplate(_HEAD); // 'index' value in the URL - uint8_t controllerindex = getFormItemInt(F("index"), 0); - boolean controllerNotSet = controllerindex == 0; + uint8_t controllerindex = getFormItemInt(F("index"), 0); + const bool controllerIndexSet = controllerindex != 0 && validControllerIndex(controllerindex - 1); --controllerindex; // Index in URL is starting from 1, but starting from 0 in the array. const int protocol_webarg_value = getFormItemInt(F("protocol"), -1); // submitted data - if ((protocol_webarg_value != -1) && !controllerNotSet) + if ((protocol_webarg_value != -1) && controllerIndexSet) { const protocolIndex_t protocolIndex = protocol_webarg_value; bool mustInit = false; @@ -124,28 +123,17 @@ void handle_controllers() { if (mustInit) { // Init controller plugin using the new settings. - protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(controllerindex); - - if (validProtocolIndex(ProtocolIndex)) { - struct EventStruct TempEvent; - TempEvent.ControllerIndex = controllerindex; - String dummy; - CPlugin::Function cfunction = - Settings.ControllerEnabled[controllerindex] ? CPlugin::Function::CPLUGIN_INIT : CPlugin::Function::CPLUGIN_EXIT; - CPluginCall(ProtocolIndex, cfunction, &TempEvent, dummy); - } + CPlugin_Exit_Init(controllerindex); } } html_add_form(); - if (controllerNotSet) - { - handle_controllers_ShowAllControllersTable(); - } - else + if (controllerIndexSet) { handle_controllers_ControllerSettingsPage(controllerindex); + } else { + handle_controllers_ShowAllControllersTable(); } sendHeadandTail_stdtemplate(_TAIL); @@ -176,13 +164,14 @@ void handle_controllers_clearLoadDefaults(uint8_t controllerindex, ControllerSet // Load some templates from the controller. struct EventStruct TempEvent; + TempEvent.ControllerIndex = controllerindex; // Hand over the controller settings in the Data pointer, so the controller can set some defaults. TempEvent.Data = (uint8_t *)(&ControllerSettings); if (proto.usesTemplate) { String dummy; - CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE, &TempEvent, dummy); + CPluginCall(CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE, &TempEvent, dummy); } safe_strncpy(ControllerSettings.Subscribe, TempEvent.String1.c_str(), sizeof(ControllerSettings.Subscribe)); safe_strncpy(ControllerSettings.Publish, TempEvent.String2.c_str(), sizeof(ControllerSettings.Publish)); @@ -221,7 +210,7 @@ void handle_controllers_CopySubmittedSettings_CPluginCall(uint8_t controllerinde // Call controller plugin to save CustomControllerSettings String dummy; - CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_WEBFORM_SAVE, &TempEvent, dummy); + CPluginCall(CPlugin::Function::CPLUGIN_WEBFORM_SAVE, &TempEvent, dummy); } } @@ -250,25 +239,7 @@ void handle_controllers_ShowAllControllersTable() LoadControllerSettings(x, *ControllerSettings); html_TR_TD(); - if (cplugin_set && !supportedCPluginID(Settings.Protocol[x])) { - html_add_button_prefix(F("red"), true); - } else { - html_add_button_prefix(); - } - { - addHtml(F("controllers?index=")); - addHtmlInt(x + 1); - addHtml(F("'>")); - - if (cplugin_set) { - addHtml(F("Edit")); - } else { - addHtml(F("Add")); - } - addHtml(F("")); - addHtml(getControllerSymbol(x)); - } - html_TD(); + addPlugin_Add_Edit_Button(F("controllers"), x, cplugin_set, supportedCPluginID(Settings.Protocol[x]), getControllerSymbol(x)); if (cplugin_set) { @@ -277,10 +248,11 @@ void handle_controllers_ShowAllControllersTable() html_TD(); addHtml(getCPluginNameFromCPluginID(Settings.Protocol[x])); html_TD(); - const protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(x); { String hostDescription; - CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG, 0, hostDescription); + EventStruct TempEvent; + TempEvent.ControllerIndex = x; + CPluginCall(CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG, &TempEvent, hostDescription); if (!hostDescription.isEmpty()) { addHtml(hostDescription); @@ -290,7 +262,8 @@ void handle_controllers_ShowAllControllersTable() } html_TD(); - const ProtocolStruct& proto = getProtocolStruct(ProtocolIndex); + const protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(x); + const ProtocolStruct& proto = getProtocolStruct(ProtocolIndex); if ((INVALID_PROTOCOL_INDEX == ProtocolIndex) || proto.usesPort) { addHtmlInt(13 == Settings.Protocol[x] ? Settings.UDPPort : ControllerSettings->Port); // P2P/C013 exception @@ -348,6 +321,10 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex if (Settings.Protocol[controllerindex]) { + // Separate enabled checkbox as it doesn't need to use the ControllerSettings. + // So ControllerSettings object can be destructed before controller specific settings are loaded. + addControllerEnabledForm(controllerindex); + addFormSeparator(2); { MakeControllerSettings(ControllerSettings); // -V522 @@ -374,13 +351,17 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex if (proto.usesPort) { addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_PORT); } - # if FEATURE_MQTT_TLS + # if FEATURE_MQTT_TLS || FEATURE_HTTP_TLS - if (proto.usesMQTT && proto.usesTLS) { + if (proto.usesTLS) { addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_MQTT_TLS_TYPE); - addFormNote(F("Default ports: MQTT: 1883 / MQTT TLS: 8883")); + if (proto.usesMQTT) { + addFormNote(F("Default ports: MQTT: 1883 / MQTT TLS: 8883")); + } else { + addFormNote(F("Default ports: HTTP: 80 / HTTPS: 443")); + } } - # endif // if FEATURE_MQTT_TLS + # endif // if FEATURE_MQTT_TLS || FEATURE_HTTP_TLS # ifdef USES_ESPEASY_NOW if (proto.usesMQTT) { @@ -485,7 +466,8 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_AUTO_DISCOVERY_TRIGGER); addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_AUTO_DISCOVERY_TOPIC); addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_AUTO_DISCOVERY_CONFIG); - addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_RETAINED_DISCOVERY_OPTION); + addControllerParameterForm(*ControllerSettings, controllerindex, + ControllerSettingsStruct::CONTROLLER_RETAINED_DISCOVERY_OPTION); } # endif // if FEATURE_MQTT_DISCOVER } @@ -501,19 +483,31 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex TempEvent.ControllerIndex = controllerindex; String webformLoadString; - CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_WEBFORM_LOAD, &TempEvent, webformLoadString); + CPluginCall(CPlugin::Function::CPLUGIN_WEBFORM_LOAD, &TempEvent, webformLoadString); if (webformLoadString.length() > 0) { addHtmlError(F("Bug in CPlugin::Function::CPLUGIN_WEBFORM_LOAD, should not append to string, use addHtml() instead")); } } - { + # if FEATURE_MQTT - if (proto.usesMQTT) { - addFormSubHeader(F("Connection Status")); - addRowLabel(F("MQTT Client Connected")); - addEnabled(MQTTclient_connected); + if (proto.usesMQTT && Settings.ControllerEnabled[controllerindex]) { + addFormSubHeader(F("Connection Status")); + addRowLabel(F("MQTT Client Connected")); + addEnabled(MQTTclient_connected); + + if (MQTTclient_connected) { + auto conn_duration = MQTTclient_connected_stats.getLastOnDuration_ms(); + + if (conn_duration > 0) { + addRowLabel(F("Connection Duration")); + addHtml(format_msec_duration_HMS(conn_duration)); + addRowLabel(F("Number of Reconnects")); + addHtmlInt(MQTTclient_connected_stats.getCycleCount()); + } + + } # if FEATURE_MQTT_TLS @@ -522,6 +516,99 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex addHtmlInt(mqtt_tls_last_error); addHtml(F(": ")); addHtml(mqtt_tls_last_errorstr); +/* + getLastError codes as documented in lib\lib_ssl\bearssl-esp8266\src\t_bearssl_ssl.h + SSL-level error codes + | Receive Fatal Alert + | | Send Fatal Alert + | | | + 0 : 256 : 512 : BR_ERR_OK + 1 : 257 : 513 : BR_ERR_BAD_PARAM - caller-provided parameter is incorrect + 2 : 258 : 514 : BR_ERR_BAD_STATE - operation requested by the caller cannot be applied with the current context state (e.g. reading data while outgoing data is waiting to be sent) + 3 : 259 : 515 : BR_ERR_UNSUPPORTED_VERSION - incoming protocol or record version is unsupported + 4 : 260 : 516 : BR_ERR_BAD_VERSION - incoming record version does not match the expected version + 5 : 261 : 517 : BR_ERR_BAD_LENGTH - incoming record length is invalid + 6 : 262 : 518 : BR_ERR_TOO_LARGE - incoming record is too large to be processed, or buffer is too small for the handshake message to send + 7 : 263 : 519 : BR_ERR_BAD_MAC - decryption found an invalid padding, or the record MAC is not correct + 8 : 264 : 520 : BR_ERR_NO_RANDOM - no initial entropy was provided, and none can be obtained from the OS + 9 : 265 : 521 : BR_ERR_UNKNOWN_TYPE - incoming record type is unknown + 10 : 266 : 522 : BR_ERR_UNEXPECTED - incoming record or message has wrong type with regards to the current engine state + 12 : 268 : 524 : BR_ERR_BAD_CCS - ChangeCipherSpec message from the peer has invalid contents + 13 : 269 : 525 : BR_ERR_BAD_ALERT - alert message from the peer has invalid contents (odd length) + 14 : 270 : 526 : BR_ERR_BAD_HANDSHAKE - incoming handshake message decoding failed + 15 : 271 : 527 : BR_ERR_OVERSIZED_ID - ServerHello contains a session ID which is larger than 32 bytes + 16 : 272 : 528 : BR_ERR_BAD_CIPHER_SUITE - server wants to use a cipher suite that we did not claim to support. This is also reported if we tried to advertise a cipher suite that we do not support + 17 : 273 : 529 : BR_ERR_BAD_COMPRESSION - server wants to use a compression that we did not claim to support + 18 : 274 : 530 : BR_ERR_BAD_FRAGLEN - server's max fragment length does not match client's + 19 : 275 : 531 : BR_ERR_BAD_SECRENEG - secure renegotiation failed + 20 : 276 : 532 : BR_ERR_EXTRA_EXTENSION - server sent an extension type that we did not announce, or used the same extension type several times in a single ServerHello + 21 : 277 : 533 : BR_ERR_BAD_SNI - invalid Server Name Indication contents (when used by the server, this extension shall be empty) + 22 : 278 : 534 : BR_ERR_BAD_HELLO_DONE - invalid ServerHelloDone from the server (length is not 0) + 23 : 279 : 535 : BR_ERR_LIMIT_EXCEEDED - internal limit exceeded (e.g. server's public key is too large) + 24 : 280 : 536 : BR_ERR_BAD_FINISHED - Finished message from peer does not match the expected value + 25 : 281 : 537 : BR_ERR_RESUME_MISMATCH - session resumption attempt with distinct version or cipher suite + 26 : 282 : 538 : BR_ERR_INVALID_ALGORITHM - unsupported or invalid algorithm (ECDHE curve, signature algorithm, hash function) + 27 : 283 : 539 : BR_ERR_BAD_SIGNATURE - invalid signature (on ServerKeyExchange from server, or in CertificateVerify from client) + 28 : 284 : 540 : BR_ERR_WRONG_KEY_USAGE - peer's public key does not have the proper type or is not allowed for requested operation + 29 : 285 : 541 : BR_ERR_NO_CLIENT_AUTH - client did not send a certificate upon request, or the client certificate could not be validated + 31 : 287 : 543 : BR_ERR_IO - I/O error or premature close on underlying transport stream. This error code is set only by the simplified I/O API ("br_sslio_*") + + getLastError codes as documented in lib\lib_ssl\bearssl-esp8266\src\t_bearssl_x509.h + 32 : BR_ERR_X509_OK - validation was successful; this is not actually an error + 33 : BR_ERR_X509_INVALID_VALUE - invalid value in an ASN.1 structure + 34 : BR_ERR_X509_TRUNCATED - truncated certificate + 35 : BR_ERR_X509_EMPTY_CHAIN - empty certificate chain (no certificate at all) + 36 : BR_ERR_X509_INNER_TRUNC - decoding error: inner element extends beyond outer element size + 37 : BR_ERR_X509_BAD_TAG_CLASS - decoding error: unsupported tag class (application or private) + 38 : BR_ERR_X509_BAD_TAG_VALUE - decoding error: unsupported tag value + 39 : BR_ERR_X509_INDEFINITE_LENGTH - decoding error: indefinite length + 40 : BR_ERR_X509_EXTRA_ELEMENT - decoding error: extraneous element + 41 : BR_ERR_X509_UNEXPECTED - decoding error: unexpected element + 42 : BR_ERR_X509_NOT_CONSTRUCTED - decoding error: expected constructed element, but is primitive + 43 : BR_ERR_X509_NOT_PRIMITIVE - decoding error: expected primitive element, but is constructed + 44 : BR_ERR_X509_PARTIAL_BYTE - decoding error: BIT STRING length is not multiple of 8 + 45 : BR_ERR_X509_BAD_BOOLEAN - decoding error: BOOLEAN value has invalid length + 46 : BR_ERR_X509_OVERFLOW - decoding error: value is off-limits + 47 : BR_ERR_X509_BAD_DN - invalid distinguished name + 48 : BR_ERR_X509_BAD_TIME - invalid date/time representation + 49 : BR_ERR_X509_UNSUPPORTED - certificate contains unsupported features that cannot be ignored + 50 : BR_ERR_X509_LIMIT_EXCEEDED - key or signature size exceeds internal limits + 51 : BR_ERR_X509_WRONG_KEY_TYPE - key type does not match that which was expected + 52 : BR_ERR_X509_BAD_SIGNATURE - signature is invalid + 53 : BR_ERR_X509_TIME_UNKNOWN - validation time is unknown + 54 : BR_ERR_X509_EXPIRED - certificate is expired or not yet valid + 55 : BR_ERR_X509_DN_MISMATCH - issuer/subject DN mismatch in the chain + 56 : BR_ERR_X509_BAD_SERVER_NAME - expected server name was not found in the chain + 57 : BR_ERR_X509_CRITICAL_EXTENSION - unknown critical extension in certificate + 58 : BR_ERR_X509_NOT_CA - not a CA, or path length constraint violation + 59 : BR_ERR_X509_FORBIDDEN_KEY_USAGE - Key Usage extension prohibits intended usage + 60 : BR_ERR_X509_WEAK_PUBLIC_KEY - public key found in certificate is too small + 62 : BR_ERR_X509_NOT_TRUSTED - chain could not be linked to a trust anchor + + getLastError codes as documented in lib\lib_ssl\bearssl-esp8266\src\t_bearssl_ssl.h + 10 : 266 : BR_ALERT_UNEXPECTED_MESSAGE + 20 : 276 : BR_ALERT_BAD_RECORD_MAC + 22 : 278 : BR_ALERT_RECORD_OVERFLOW + 30 : 286 : BR_ALERT_DECOMPRESSION_FAILURE + 40 : 296 : BR_ALERT_HANDSHAKE_FAILURE + 42 : 298 : BR_ALERT_BAD_CERTIFICATE + 43 : 299 : BR_ALERT_UNSUPPORTED_CERTIFICATE + 44 : 300 : BR_ALERT_CERTIFICATE_REVOKED + 45 : 301 : BR_ALERT_CERTIFICATE_EXPIRED + 46 : 302 : BR_ALERT_CERTIFICATE_UNKNOWN + 47 : 303 : BR_ALERT_ILLEGAL_PARAMETER + 48 : 304 : BR_ALERT_UNKNOWN_CA + 49 : 305 : BR_ALERT_ACCESS_DENIED + 50 : 306 : BR_ALERT_DECODE_ERROR + 51 : 307 : BR_ALERT_DECRYPT_ERROR + 70 : 326 : BR_ALERT_PROTOCOL_VERSION + 71 : 327 : BR_ALERT_INSUFFICIENT_SECURITY + 80 : 336 : BR_ALERT_INTERNAL_ERROR + 90 : 346 : BR_ALERT_USER_CANCELED + 100 : 356 : BR_ALERT_NO_RENEGOTIATION + 110 : 366 : BR_ALERT_UNSUPPORTED_EXTENSION + 120 : 376 : BR_ALERT_NO_APPLICATION_PROTOCOL +*/ # ifdef ESP32 @@ -626,13 +713,9 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex # endif // ifdef ESP32 } # endif // if FEATURE_MQTT_TLS - } -# endif // if FEATURE_MQTT } - - // Separate enabled checkbox as it doesn't need to use the ControllerSettings. - // So ControllerSettings object can be destructed before controller specific settings are loaded. - addControllerEnabledForm(controllerindex); +# endif // if FEATURE_MQTT + } addFormSeparator(2); diff --git a/src/src/WebServer/DevicesPage.cpp b/src/src/WebServer/DevicesPage.cpp index 1253bc6a5..5f1f5283c 100644 --- a/src/src/WebServer/DevicesPage.cpp +++ b/src/src/WebServer/DevicesPage.cpp @@ -38,6 +38,9 @@ # include +#if FEATURE_TASKVALUE_UNIT_OF_MEASURE +#include "../Helpers/ESPEasy_UnitOfMeasure.h" +#endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE void handle_devices() { # ifndef BUILD_NO_RAM_TRACKER @@ -180,12 +183,13 @@ void handle_devices() { struct EventStruct TempEvent(taskIndex); String dummy; + // May need to call PLUGIN_INIT, however we must make sure it is exited first + PluginCall(PLUGIN_EXIT, &TempEvent, dummy); + if (Settings.TaskDeviceEnabled[taskIndex]) { if (PluginCall(PLUGIN_INIT, &TempEvent, dummy)) { PluginCall(PLUGIN_READ, &TempEvent, dummy); } - } else { - PluginCall(PLUGIN_EXIT, &TempEvent, dummy); } } } @@ -280,22 +284,23 @@ void handle_devices_CopySubmittedSettings(taskIndex_t taskIndex, pluginID_t task if (device.Type == DEVICE_TYPE_I2C) { uint8_t flags = 0; bitWrite(flags, I2C_FLAGS_SLOW_SPEED, isFormItemChecked(F("taskdeviceflags0"))); -#if FEATURE_I2C_MULTIPLE || FEATURE_I2CMULTIPLEXER +# if FEATURE_I2C_MULTIPLE || FEATURE_I2CMULTIPLEXER uint8_t i2cBus = 0; -#endif +# endif -#if FEATURE_I2C_MULTIPLE - if ((getI2CBusCount() > 1) && (Settings.isI2CEnabled(1) - #if FEATURE_I2C_INTERFACE_3 - || Settings.isI2CEnabled(2) - #endif +# if FEATURE_I2C_MULTIPLE + + if ((getI2CBusCount() > 1) && (Settings.isI2CEnabled(1) + # if FEATURE_I2C_INTERFACE_3 + || Settings.isI2CEnabled(2) + # endif ) - && !Device[DeviceIndex].I2CNoBusSelection - ) { + && !Device[DeviceIndex].I2CNoBusSelection + ) { i2cBus = getFormItemInt(F("pi2cbus")); set3BitToUL(flags, I2C_FLAGS_BUS_NUMBER, i2cBus); } -#endif // if FEATURE_I2C_MULTIPLE +# endif // if FEATURE_I2C_MULTIPLE # if FEATURE_I2CMULTIPLEXER @@ -328,7 +333,8 @@ void handle_devices_CopySubmittedSettings(taskIndex_t taskIndex, pluginID_t task struct EventStruct TempEvent(taskIndex); // Save selected output type. - switch (device.OutputDataType) { + switch (device.OutputDataType) + { case Output_Data_type_t::Default: { String dummy; @@ -389,28 +395,34 @@ void handle_devices_CopySubmittedSettings(taskIndex_t taskIndex, pluginID_t task Settings.TaskDevicePort[taskIndex] = getFormItemInt(F("TDP"), 0); update_whenset_FormItemInt(F("remoteFeed"), Settings.TaskDeviceDataFeed[taskIndex]); Settings.CombineTaskValues_SingleEvent(taskIndex, isFormItemChecked(F("TVSE"))); - #if FEATURE_STRING_VARIABLES - Settings.ShowDerivedTaskValues(taskIndex, isFormItemChecked(F("TSDV"))); - Settings.EventAndLogDerivedTaskValues(taskIndex, isFormItemChecked(F("TELD"))); - #endif // if FEATURE_STRING_VARIABLES - + # if FEATURE_STRING_VARIABLES + + if (!device.HideDerivedValues) { + Settings.ShowDerivedTaskValues(taskIndex, isFormItemChecked(F("TSDV"))); + Settings.EventAndLogDerivedTaskValues(taskIndex, isFormItemChecked(F("TELD"))); + } + # endif // if FEATURE_STRING_VARIABLES + for (controllerIndex_t controllerNr = 0; controllerNr < CONTROLLER_MAX; controllerNr++) { Settings.TaskDeviceID[controllerNr][taskIndex] = getFormItemInt(getPluginCustomArgName(F("TDID"), controllerNr)); Settings.TaskDeviceSendData[controllerNr][taskIndex] = isFormItemChecked(getPluginCustomArgName(F("TDSD"), controllerNr)); # if FEATURE_MQTT_DISCOVER - + if (isFormItemChecked(getPluginCustomArgName(F("TDDSC"), controllerNr)) && Settings.TaskDeviceSendData[controllerNr][taskIndex]) { discoverController = controllerNr; } # endif // if FEATURE_MQTT_DISCOVER - #if FEATURE_STRING_VARIABLES - Settings.SendDerivedTaskValues(taskIndex, controllerNr, isFormItemChecked(getPluginCustomArgName(F("TSND"), controllerNr))); - #endif // if FEATURE_STRING_VARIABLES - #if FEATURE_MQTT && FEATURE_MQTT_DISCOVER + # if FEATURE_STRING_VARIABLES + + if (!device.HideDerivedValues) { + Settings.SendDerivedTaskValues(taskIndex, controllerNr, isFormItemChecked(getPluginCustomArgName(F("TSND"), controllerNr))); + } + # endif // if FEATURE_STRING_VARIABLES + # if FEATURE_MQTT && FEATURE_MQTT_DISCOVER Settings.SendRetainedTaskValues(taskIndex, controllerNr, isFormItemChecked(getPluginCustomArgName(F("TSRT"), controllerNr))); - #endif // if FEATURE_MQTT && FEATURE_MQTT_DISCOVER + # endif // if FEATURE_MQTT && FEATURE_MQTT_DISCOVER } if (device.PullUpOption) { @@ -453,17 +465,17 @@ void handle_devices_CopySubmittedSettings(taskIndex_t taskIndex, pluginID_t task ExtraTaskSettings.setPluginStatsConfig(varNr, pluginStats_Config); # endif // if FEATURE_PLUGIN_STATS - #if FEATURE_TASKVALUE_UNIT_OF_MEASURE + # if FEATURE_TASKVALUE_UNIT_OF_MEASURE ExtraTaskSettings.setTaskVarUnitOfMeasure(varNr, getFormItemInt(getPluginCustomArgName(F("TUOM"), varNr))); - #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE + # endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE - #if FEATURE_CUSTOM_TASKVAR_VTYPE + # if FEATURE_CUSTOM_TASKVAR_VTYPE ExtraTaskSettings.setTaskVarCustomVType(varNr, getFormItemInt(getPluginCustomArgName(F("TDTV"), varNr))); - #endif // if FEATURE_CUSTOM_TASKVAR_VTYPE + # endif // if FEATURE_CUSTOM_TASKVAR_VTYPE - #if FEATURE_MQTT_STATE_CLASS + # if FEATURE_MQTT_STATE_CLASS ExtraTaskSettings.setTaskVarStateClass(varNr, getFormItemInt(getPluginCustomArgName(F("TDSC"), varNr))); - #endif // if FEATURE_MQTT_STATE_CLASS + # endif // if FEATURE_MQTT_STATE_CLASS } ExtraTaskSettings.clearUnusedValueNames(valueCount); @@ -483,7 +495,7 @@ void handle_devices_CopySubmittedSettings(taskIndex_t taskIndex, pluginID_t task if (device.ErrorStateValues) { // FIXME TD-er: Must collect these from the web page. - PluginCall(DeviceIndex, PLUGIN_INIT_VALUE_RANGES, &TempEvent, dummy); + PluginCall(PLUGIN_INIT_VALUE_RANGES, &TempEvent, dummy); } // Make sure the task needs to reload using the new settings. @@ -529,9 +541,8 @@ void handle_devices_CopySubmittedSettings(taskIndex_t taskIndex, pluginID_t task if (Settings.TaskDeviceSendData[TempEvent.ControllerIndex][TempEvent.TaskIndex] && Settings.ControllerEnabled[TempEvent.ControllerIndex] && Settings.Protocol[TempEvent.ControllerIndex]) { - protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(TempEvent.ControllerIndex); String dummy; - CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_TASK_CHANGE_NOTIFICATION, &TempEvent, dummy); + CPluginCall(CPlugin::Function::CPLUGIN_TASK_CHANGE_NOTIFICATION, &TempEvent, dummy); } } @@ -703,7 +714,8 @@ void handle_devicess_ShowAllTasksTable(uint8_t page) bool showpin2 = false; bool showpin3 = false; - switch (device.Type) { + switch (device.Type) + { case DEVICE_TYPE_I2C: { format_I2C_pin_description(x); @@ -731,11 +743,11 @@ void handle_devicess_ShowAllTasksTable(uint8_t page) # endif // if FEATURE_ADC_VCC # endif // ifdef ESP8266 # ifdef ESP32 - #if SOC_ADC_SUPPORTED + # if SOC_ADC_SUPPORTED showpin1 = true; addHtml(formatGpioName_ADC(Settings.TaskDevicePin1[x])); html_BR(); - #endif + # endif // if SOC_ADC_SUPPORTED # endif // ifdef ESP32 break; @@ -825,6 +837,9 @@ void handle_devicess_ShowAllTasksTable(uint8_t page) html_TD(); if (validDeviceIndex(DeviceIndex)) { + # if FEATURE_STRING_VARIABLES + const DeviceStruct& device = Device[DeviceIndex]; + # endif // #if FEATURE_STRING_VARIABLES String customValuesString; const bool customValues = PluginCall(PLUGIN_WEBFORM_SHOW_VALUES, &TempEvent, customValuesString); @@ -836,79 +851,95 @@ void handle_devicess_ShowAllTasksTable(uint8_t page) { if (validPluginID_fullcheck(Settings.getPluginID_for_task(x))) { - #if FEATURE_TASKVALUE_UNIT_OF_MEASURE + # if FEATURE_TASKVALUE_UNIT_OF_MEASURE const uint8_t uomIndex = Cache.getTaskVarUnitOfMeasure(x, varNr); String uom; + if ((uomIndex != 0) && Settings.ShowUnitOfMeasureOnDevicesPage()) { uom = concat(F(" "), toUnitOfMeasureName(uomIndex)); } - #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE + # endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE const String value = formatUserVarNoCheck(&TempEvent, varNr); - #if FEATURE_STRING_VARIABLES - bool hasPresentation = false; - const String presentation = formatUserVarForPresentation(&TempEvent, varNr, hasPresentation, value, DeviceIndex); - #endif // if FEATURE_STRING_VARIABLES + # if FEATURE_STRING_VARIABLES + bool hasPresentation = false; + String presentation; + + if (!device.HideDerivedValues) { + presentation = formatUserVarForPresentation(&TempEvent, varNr, hasPresentation, value, DeviceIndex); + } + # endif // if FEATURE_STRING_VARIABLES pluginWebformShowValue( x, varNr, Cache.getTaskDeviceValueName(x, varNr), - #if FEATURE_STRING_VARIABLES + # if FEATURE_STRING_VARIABLES hasPresentation ? presentation : - #endif // if FEATURE_STRING_VARIABLES - #if FEATURE_TASKVALUE_UNIT_OF_MEASURE + # endif // if FEATURE_STRING_VARIABLES + # if FEATURE_TASKVALUE_UNIT_OF_MEASURE concat(value, uom) - #else // if FEATURE_TASKVALUE_UNIT_OF_MEASURE + # else // if FEATURE_TASKVALUE_UNIT_OF_MEASURE value - #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE - ); + # endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE + ); } } - #if FEATURE_STRING_VARIABLES - int varNr = VARS_PER_TASK; - if (Settings.ShowDerivedTaskValues(x)) { - String taskName = getTaskDeviceName(x); - taskName.toLowerCase(); - String postfix; - const String search = getDerivedValueSearchAndPostfix(taskName, postfix); + # if FEATURE_STRING_VARIABLES - auto it = customStringVar.begin(); - while (it != customStringVar.end()) { - if (it->first.startsWith(search) && it->first.endsWith(postfix)) { - String valueName = it->first.substring(search.length(), it->first.indexOf('-')); - String uom; - String vType; - const String vname2 = getDerivedValueNameUomAndVType(taskName, valueName, uom, vType); - if (!vname2.isEmpty()) { - valueName = vname2; - } - if (!it->second.isEmpty()) { - String value(it->second); - value = parseTemplateAndCalculate(value); - String presentation = getCustomStringVar(strformat(F(TASK_VALUE_PRESENTATION_PREFIX_TEMPLATE), taskName.c_str(), valueName.c_str())); - if (!uom.isEmpty()) { - value = strformat(F("%s %s"), value.c_str(), uom.c_str()); + if (!device.HideDerivedValues) { + int varNr = VARS_PER_TASK; + + if (Settings.ShowDerivedTaskValues(x)) { + String taskName = getTaskDeviceName(x); + taskName.toLowerCase(); + String postfix; + const String search = getDerivedValueSearchAndPostfix(taskName, postfix); + + auto it = customStringVar.begin(); + + while (it != customStringVar.end()) { + if (it->first.startsWith(search) && it->first.endsWith(postfix)) { + String valueName = it->first.substring(search.length(), it->first.indexOf('-')); + String uom; + String vType; + const String vname2 = getDerivedValueNameUomAndVType(taskName, valueName, uom, vType); + + if (!vname2.isEmpty()) { + valueName = vname2; } - if (!presentation.isEmpty()) { - stripEscapeCharacters(presentation); - presentation.replace(F("%value%"), value); - value = parseTemplate(presentation); + + if (!it->second.isEmpty()) { + String value(it->second); + value = parseTemplateAndCalculate(value); + String presentation = getCustomStringVar(strformat(F(TASK_VALUE_PRESENTATION_PREFIX_TEMPLATE), + taskName.c_str(), + valueName.c_str())); + + if (!uom.isEmpty()) { + value = strformat(F("%s %s"), value.c_str(), uom.c_str()); + } + + if (!presentation.isEmpty()) { + stripEscapeCharacters(presentation); + presentation.replace(F("%value%"), value); + value = parseTemplate(presentation); + } + pluginWebformShowValue( + x, + varNr, + valueName, + value); + ++varNr; } - pluginWebformShowValue( - x, - varNr, - valueName, - value); - ++varNr; } + else if (it->first.substring(0, search.length()).compareTo(search) > 0) { + break; + } + ++it; } - else if (it->first.substring(0, search.length()).compareTo(search) > 0) { - break; - } - ++it; } } - #endif // if FEATURE_STRING_VARIABLES + # endif // if FEATURE_STRING_VARIABLES } } } @@ -921,6 +952,7 @@ void handle_devicess_ShowAllTasksTable(uint8_t page) } # if FEATURE_ESPEASY_P2P + void format_originating_node(uint8_t remoteUnit) { addHtml(F("Unit ")); addHtmlInt(remoteUnit); @@ -951,17 +983,18 @@ void format_I2C_port_description(taskIndex_t x) } # endif // if FEATURE_I2C_GET_ADDRESS # if FEATURE_I2CMULTIPLEXER - #if FEATURE_I2C_MULTIPLE + # if FEATURE_I2C_MULTIPLE const uint8_t i2cBus = Settings.getI2CInterface(x); + if ((i2cBus > 0) || Settings.isI2CEnabled(1) || Settings.isI2CEnabled(2)) { html_BR(); addHtml(F("I2C Bus")); addHtml(' '); addHtmlInt(i2cBus); } - #else + # else // if FEATURE_I2C_MULTIPLE const uint8_t i2cBus = 0; - #endif // if FEATURE_I2C_MULTIPLE + # endif // if FEATURE_I2C_MULTIPLE if (isI2CMultiplexerEnabled(i2cBus) && I2CMultiplexerPortSelectedForTask(x)) { String mux; @@ -1001,11 +1034,12 @@ void format_SPI_port_description(int8_t spi_gpios[3]) void format_I2C_pin_description(taskIndex_t x) { - #if FEATURE_I2C_MULTIPLE + # if FEATURE_I2C_MULTIPLE const uint8_t i2cBus = Settings.getI2CInterface(x); - #else + # else const uint8_t i2cBus = 0; - #endif // if FEATURE_I2C_MULTIPLE + # endif // if FEATURE_I2C_MULTIPLE + if (checkI2CConfigValid_toHtml(x)) { Label_Gpio_toHtml(F("SDA"), formatGpioLabel(Settings.getI2CSdaPin(i2cBus), false)); html_BR(); @@ -1018,7 +1052,7 @@ void format_SPI_pin_description(int8_t spi_gpios[3], taskIndex_t x, bool showCSp if (Settings.InitSPI > static_cast(SPI_Options_e::None)) { const __FlashStringHelper*labels[] = { F("CLK"), F("MISO"), F("MOSI") }; - for (int i = 0; i < 3; ++i) { + for (size_t i = 0; i < NR_ELEMENTS(labels); ++i) { if (i != 0) { html_BR(); } @@ -1075,7 +1109,8 @@ void handle_devices_TaskSettingsPage(taskIndex_t taskIndex, uint8_t page) addHtml(getPluginNameFromDeviceIndex(DeviceIndex)); const uint8_t pid = Settings.getPluginID_for_task(taskIndex).value; - if (pid <= 101) { // Up to P101 seem to be listed in the old Wiki, so lets keep pointing there too + + if (pid <= 79) { // Up to P079 seem to be listed in the old Wiki (and a few incomplete pages), so lets keep pointing there too addHelpButton(concat(F("Plugin"), Settings.getPluginID_for_task(taskIndex).value)); } addRTDPluginButton(Settings.getPluginID_for_task(taskIndex)); @@ -1087,6 +1122,7 @@ void handle_devices_TaskSettingsPage(taskIndex_t taskIndex, uint8_t page) // Settings.TaskDeviceEnabled[taskIndex].enabled, Settings.isTaskEnableReadonly(taskIndex)); // ="taskdeviceenabled" + addFormSeparator(2); # if FEATURE_PLUGIN_PRIORITY @@ -1113,21 +1149,22 @@ void handle_devices_TaskSettingsPage(taskIndex_t taskIndex, uint8_t page) # if FEATURE_ESPEASY_P2P const controllerIndex_t p2p_controllerIndex = findFirstEnabledControllerWithId(13); - const uint8_t remoteUnit = + const uint8_t remoteUnit = (p2p_controllerIndex != INVALID_CONTROLLER_INDEX) ? Settings.TaskDeviceDataFeed[taskIndex] : 0; - #else + # else // if FEATURE_ESPEASY_P2P const uint8_t remoteUnit = 0; - #endif + # endif // if FEATURE_ESPEASY_P2P # if FEATURE_ESPEASY_P2P - if (device.SendDataOption && p2p_controllerIndex != INVALID_CONTROLLER_INDEX) + + if (device.SendDataOption && (p2p_controllerIndex != INVALID_CONTROLLER_INDEX)) { // Show remote feed information. addFormSubHeader(F("Data Source")); addFormNumericBox(F("Remote Unit"), F("remoteFeed"), remoteUnit, 0, 255); - if (remoteUnit != 0 && remoteUnit != 255) { + if ((remoteUnit != 0) && (remoteUnit != 255)) { const NodeStruct*node = Nodes.getNode(remoteUnit); if (node != nullptr) { @@ -1189,7 +1226,7 @@ void handle_devices_TaskSettingsPage(taskIndex_t taskIndex, uint8_t page) // add plugins content - if ((DEVICE_TYPE_DUMMY != device.Type) && (remoteUnit == 0)) { + if ((!device.NoDeviceSettings) && (remoteUnit == 0)) { addFormSubHeader(F("Device Settings")); } @@ -1346,6 +1383,7 @@ void devicePage_show_pin_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex } # ifdef PLUGIN_USES_SERIAL + void devicePage_show_serial_config(taskIndex_t taskIndex) { struct EventStruct TempEvent(taskIndex); @@ -1368,13 +1406,13 @@ void devicePage_show_I2C_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex addFormSubHeader(F("I2C options")); if (!Settings.isI2CEnabled(0) - #if FEATURE_I2C_MULTIPLE - && (getI2CBusCount() > 1 && !Settings.isI2CEnabled(1)) - #if FEATURE_I2C_INTERFACE_3 - && (getI2CBusCount() > 2 && !Settings.isI2CEnabled(2)) - #endif // if FEATURE_I2C_INTERFACE_3 - #endif // if FEATURE_I2C_MULTIPLE - ) { + # if FEATURE_I2C_MULTIPLE + && (getI2CBusCount() > 1 && !Settings.isI2CEnabled(1)) + # if FEATURE_I2C_INTERFACE_3 + && (getI2CBusCount() > 2 && !Settings.isI2CEnabled(2)) + # endif // if FEATURE_I2C_INTERFACE_3 + # endif // if FEATURE_I2C_MULTIPLE + ) { addFormNote(F("I2C Bus is not configured yet (Hardware page).")); } @@ -1386,19 +1424,20 @@ void devicePage_show_I2C_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex if (Device[DeviceIndex].I2CMax100kHz) { addFormNote(F("This device is specified for max. 100 kHz operation!")); } -#if FEATURE_I2C_MULTIPLE || FEATURE_I2CMULTIPLEXER +# if FEATURE_I2C_MULTIPLE || FEATURE_I2CMULTIPLEXER uint8_t i2cBus = 0; -#endif +# endif + + # if FEATURE_I2C_MULTIPLE - #if FEATURE_I2C_MULTIPLE if (!Device[DeviceIndex].I2CNoBusSelection) { // If the device doesn't disallow bus selection i2cBus = Settings.getI2CInterface(taskIndex); I2CInterfaceSelector(F("I2C Bus"), - F("pi2cbus"), - i2cBus, - true); + F("pi2cbus"), + i2cBus, + true); } - #endif // if FEATURE_I2C_MULTIPLE + # endif // if FEATURE_I2C_MULTIPLE # if FEATURE_I2CMULTIPLEXER // Show selector for an I2C multiplexer port if a multiplexer is configured @@ -1458,7 +1497,7 @@ void devicePage_show_I2C_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex } if (taskDeviceI2CMuxPort >= static_cast(mux_max)) { taskDeviceI2CMuxPort = -1; } // Reset if out of range - const FormSelectorOptions selector( + const FormSelectorOptions selector( mux_max + 1, i2c_mux_portoptions, i2c_mux_portchoices); @@ -1476,7 +1515,8 @@ void devicePage_show_output_data_type(taskIndex_t taskIndex, deviceIndex_t Devic struct EventStruct TempEvent(taskIndex); int pconfigIndex = checkDeviceVTypeForTask(&TempEvent); - switch (Device[DeviceIndex].OutputDataType) { + switch (Device[DeviceIndex].OutputDataType) + { case Output_Data_type_t::Default: return; case Output_Data_type_t::Simple: @@ -1501,6 +1541,7 @@ void devicePage_show_output_data_type(taskIndex_t taskIndex, deviceIndex_t Devic } # if FEATURE_PLUGIN_STATS + void devicePage_show_task_statistics(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) { if (Device[DeviceIndex].PluginStats) @@ -1514,8 +1555,11 @@ void devicePage_show_task_statistics(taskIndex_t taskIndex, deviceIndex_t Device # if FEATURE_CHART_JS if (taskData->nrSamplesPresent() > 0) { - addRowLabel(F("Historic data")); + addRowColspan(2); + + // addRowLabel(F("Historic data")); taskData->plot_ChartJS(); + addHtml(F("")); } # endif // if FEATURE_CHART_JS @@ -1540,7 +1584,6 @@ void devicePage_show_task_statistics(taskIndex_t taskIndex, deviceIndex_t Device # endif // if FEATURE_PLUGIN_STATS - void devicePage_show_controller_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) { if (!validDeviceIndex(DeviceIndex)) { return; } @@ -1563,10 +1606,13 @@ void devicePage_show_controller_config(taskIndex_t taskIndex, deviceIndex_t Devi F("Unchecked: Send event per value. Checked: Send single event (%s#All) containing all values"), getTaskDeviceName(taskIndex).c_str())); - #if FEATURE_STRING_VARIABLES - addFormCheckBox(F("Show derived values"), F("TSDV"), Settings.ShowDerivedTaskValues(taskIndex)); - addFormCheckBox(F("Event & Log derived values"), F("TELD"), Settings.EventAndLogDerivedTaskValues(taskIndex)); - #endif // if FEATURE_STRING_VARIABLES + # if FEATURE_STRING_VARIABLES + + if (!device.HideDerivedValues) { + addFormCheckBox(F("Show derived values"), F("TSDV"), Settings.ShowDerivedTaskValues(taskIndex)); + addFormCheckBox(F("Event & Log derived values"), F("TELD"), Settings.EventAndLogDerivedTaskValues(taskIndex)); + } + # endif // if FEATURE_STRING_VARIABLES bool separatorAdded = false; @@ -1577,12 +1623,24 @@ void devicePage_show_controller_config(taskIndex_t taskIndex, deviceIndex_t Devi if (!separatorAdded) { addFormSeparator(2); } + protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(controllerNr); + const bool showControllerIDX = validProtocolIndex(ProtocolIndex) && + getProtocolStruct(ProtocolIndex).usesID && + (Settings.Protocol[controllerNr] != 0); + # if FEATURE_MQTT_DISCOVER + const bool showMqttGroup = (validProtocolIndex(ProtocolIndex) && + getProtocolStruct(ProtocolIndex).mqttAutoDiscover); + # endif // if FEATURE_MQTT_DISCOVER separatorAdded = true; html_TR_TD(); addHtml(F("Send to Controller ")); addHtml(getControllerSymbol(controllerNr)); addHtmlDiv(F("note"), wrap_braces(getCPluginNameFromCPluginID(Settings.Protocol[controllerNr]) + F(", ") + // Most compact code... - (Settings.ControllerEnabled[controllerNr] ? F("enabled") : F("disabled")))); + (Settings.ControllerEnabled[controllerNr] ? F("enabled") : F("disabled")) + #if FEATURE_MQTT_DISCOVER + + (showMqttGroup ? F(", Auto Discovery") : F("")) + #endif // if FEATURE_MQTT_DISCOVER + )); html_TD(); addHtml(F("")); // remove left padding 2x to align vertically with other inputs @@ -1591,16 +1649,9 @@ void devicePage_show_controller_config(taskIndex_t taskIndex, deviceIndex_t Devi getPluginCustomArgName(F("TDSD"), controllerNr), // ="taskdevicesenddata" Settings.TaskDeviceSendData[controllerNr][taskIndex]); - protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(controllerNr); - const bool showControllerIDX = validProtocolIndex(ProtocolIndex) && - getProtocolStruct(ProtocolIndex).usesID && - (Settings.Protocol[controllerNr] != 0); - # if FEATURE_MQTT_DISCOVER - const bool showMqttGroup = (validProtocolIndex(ProtocolIndex) && - getProtocolStruct(ProtocolIndex).mqttAutoDiscover); - # endif // if FEATURE_MQTT_DISCOVER # if FEATURE_STRING_VARIABLES - const bool allowSendDerived = (validProtocolIndex(ProtocolIndex) && + const bool allowSendDerived = !device.HideDerivedValues && + (validProtocolIndex(ProtocolIndex) && getProtocolStruct(ProtocolIndex).allowSendDerived); # endif // if FEATURE_STRING_VARIABLES @@ -1620,7 +1671,8 @@ void devicePage_show_controller_config(taskIndex_t taskIndex, deviceIndex_t Devi getPluginCustomArgName(F("TDID"), controllerNr), // ="taskdeviceid" Settings.TaskDeviceID[controllerNr][taskIndex], 0, DOMOTICZ_MAX_IDX); } - #if FEATURE_MQTT && FEATURE_MQTT_DISCOVER + # if FEATURE_MQTT && FEATURE_MQTT_DISCOVER + if (showMqttGroup) { html_TD(); addHtml(F("Retained:")); @@ -1631,8 +1683,9 @@ void devicePage_show_controller_config(taskIndex_t taskIndex, deviceIndex_t Devi # endif // if FEATURE_TOOLTIPS ); } - #endif // if FEATURE_MQTT && FEATURE_MQTT_DISCOVER + # endif // if FEATURE_MQTT && FEATURE_MQTT_DISCOVER # if FEATURE_STRING_VARIABLES + if (allowSendDerived) { html_TD(); addHtml(F("Send derived:")); @@ -1641,7 +1694,7 @@ void devicePage_show_controller_config(taskIndex_t taskIndex, deviceIndex_t Devi # if FEATURE_TOOLTIPS , F("Send derived values") # endif // if FEATURE_TOOLTIPS - ); + ); } # endif // if FEATURE_STRING_VARIABLES @@ -1727,45 +1780,106 @@ void devicePage_show_task_values(taskIndex_t taskIndex, deviceIndex_t DeviceInde } # endif // if FEATURE_PLUGIN_STATS - #if FEATURE_TASKVALUE_UNIT_OF_MEASURE + # if FEATURE_TASKVALUE_UNIT_OF_MEASURE html_table_header(F("Unit of Measure"), 300); ++colCount; + EventStruct uomEvent(taskIndex); + String uomDummy; + bool limitedUom = PluginCall(PLUGIN_GET_UOM_GROUPS, &uomEvent, uomDummy); + if (!limitedUom) { + PluginCall(PLUGIN_GET_DEVICEVTYPE, &uomEvent, uomDummy); // Get Sensor_VType + limitedUom = getDefaultUoMforSensorVType(&uomEvent); // Populate UoM groups for known Sensor_VTypes + #if FEATURE_MQTT_DISCOVER && FEATURE_CUSTOM_TASKVAR_VTYPE + if (!limitedUom) { + // Fill in standard Unit of measurement and Value Type, if possible + std::vector discoveryItems; + MQTT_DiscoveryGetDeviceVType(taskIndex, discoveryItems, getValueCountForTask(taskIndex), uomDummy); + + for (uint8_t varNr = 0; varNr < VARS_PER_TASK; ++varNr) { + // Match varNr with the DiscoveryItems to find the Sensor_VType for the value + for (uint8_t j = 0; j < discoveryItems.size(); ++j) { + for (uint8_t k = 0; k < discoveryItems[j].valueCount; ++k) { // Can have multiple values for 1 VType + if (varNr == discoveryItems[j].varIndex + k) { + const String uom = getValueType2DefaultHAUoM(discoveryItems[j].VType); + const uint16_t uomGroup = getUoMGroupForUoM(uom); + const uint16_t uomGroup2 = getUoMGroupForUoM(Cache.getTaskVarUnitOfMeasure(taskIndex, varNr)); + + if (uomGroup > 0) { + uomEvent.Par64N[varNr] = (uint64_t)(1ULL << (uomGroup - 1024)) | (uomGroup2 > 0 ? (uint64_t)(1ULL << (uomGroup2 - 1024)) : 0ULL); + limitedUom = true; + } else { + uomEvent.Par64N[varNr] = UOM_GROUP_ALL; + } + } + } + } + } + } + #endif // if FEATURE_MQTT_DISCOVER && FEATURE_CUSTOM_TASKVAR_VTYPE + } #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE - #if FEATURE_CUSTOM_TASKVAR_VTYPE + # if FEATURE_CUSTOM_TASKVAR_VTYPE + if (device.CustomVTypeVar) { - html_table_header(F("Value Type"), 100); + html_table_header(F("Value Type"), 100); ++colCount; } - #endif // if FEATURE_CUSTOM_TASKVAR_VTYPE + # endif // if FEATURE_CUSTOM_TASKVAR_VTYPE + + # if FEATURE_MQTT_STATE_CLASS - #if FEATURE_MQTT_STATE_CLASS if (device.MqttStateClass) { html_table_header(F("MQTT State Class"), 350); ++colCount; } - #endif // if FEATURE_MQTT_STATE_CLASS + # endif // if FEATURE_MQTT_STATE_CLASS // placeholder header html_table_header(F("")); ++colCount; - #if FEATURE_CUSTOM_TASKVAR_VTYPE + # if FEATURE_CUSTOM_TASKVAR_VTYPE std::vector singleOptions; - EventStruct TempEvent(taskIndex); if (device.CustomVTypeVar) { // Build a list of all single-value available value VTypes from PR #5199 - constexpr uint8_t maxVType = static_cast(Sensor_VType::SENSOR_TYPE_NOT_SET); + constexpr uint8_t maxVType = static_cast(Sensor_VType::SENSOR_TYPE_NOT_SET); singleOptions.push_back(0); // Empty/None value + for (uint8_t i = 0; i < maxVType; ++i) { if (getValueCountFromSensorType(static_cast(i), false) == 1) { singleOptions.push_back(i); } } } - #endif // if FEATURE_CUSTOM_TASKVAR_VTYPE + # endif // if FEATURE_CUSTOM_TASKVAR_VTYPE + + # if FEATURE_PLUGIN_STATS + const __FlashStringHelper *chartAxis[] = { + F("L1"), + F("L2"), + F("L3"), + F("L4"), + F("R1"), + F("R2"), + F("R3"), + F("R4") + }; + constexpr size_t chartAxisCount = NR_ELEMENTS(chartAxis); + #endif // if FEATURE_PLUGIN_STATS + + #if FEATURE_MQTT_STATE_CLASS + const __FlashStringHelper *stateClasses[] = { + MQTT_sensor_StateClass(0), + MQTT_sensor_StateClass(1), + MQTT_sensor_StateClass(2), + MQTT_sensor_StateClass(3), + MQTT_sensor_StateClass(4), + }; + constexpr size_t stateCount = NR_ELEMENTS(stateClasses); + #endif // if FEATURE_MQTT_STATE_CLASS // table body for (uint8_t varNr = 0; varNr < valueCount; varNr++) @@ -1809,36 +1923,27 @@ void devicePage_show_task_values(taskIndex_t taskIndex, deviceIndex_t DeviceInde html_TD(); - const __FlashStringHelper *chartAxis[] = { - F("L1"), - F("L2"), - F("L3"), - F("L4"), - F("R1"), - F("R2"), - F("R3"), - F("R4") - }; - int selected = cachedConfig.getAxisIndex(); if (!cachedConfig.isLeft()) { selected += 4; } - const FormSelectorOptions selector(NR_ELEMENTS(chartAxis), chartAxis); + const FormSelectorOptions selector(chartAxisCount, chartAxis); selector.addSelector( getPluginCustomArgName(F("TDSA"), varNr), selected); } # endif // if FEATURE_PLUGIN_STATS - #if FEATURE_TASKVALUE_UNIT_OF_MEASURE + # if FEATURE_TASKVALUE_UNIT_OF_MEASURE html_TD(); - addUnitOfMeasureSelector(getPluginCustomArgName(F("TUOM"), varNr), Cache.getTaskVarUnitOfMeasure(taskIndex, varNr)); + const uint64_t uomGroups = limitedUom ? uomEvent.Par64N[varNr] : UOM_GROUP_ALL; + addUnitOfMeasureSelector(getPluginCustomArgName(F("TUOM"), varNr), Cache.getTaskVarUnitOfMeasure(taskIndex, varNr), uomGroups); #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE - #if FEATURE_CUSTOM_TASKVAR_VTYPE + # if FEATURE_CUSTOM_TASKVAR_VTYPE + if (device.CustomVTypeVar) { html_TD(); sensorTypeCategoriesHelper_Selector( @@ -1847,26 +1952,19 @@ void devicePage_show_task_values(taskIndex_t taskIndex, deviceIndex_t DeviceInde &singleOptions[0], static_cast(Cache.getTaskVarCustomVType(taskIndex, varNr))); } - #endif // if FEATURE_CUSTOM_TASKVAR_VTYPE + # endif // if FEATURE_CUSTOM_TASKVAR_VTYPE + + # if FEATURE_MQTT_STATE_CLASS - #if FEATURE_MQTT_STATE_CLASS if (device.MqttStateClass) { html_TD(); - const __FlashStringHelper *stateClasses[] = { - MQTT_sensor_StateClass(0), - MQTT_sensor_StateClass(1), - MQTT_sensor_StateClass(4), - MQTT_sensor_StateClass(2), - MQTT_sensor_StateClass(3), - }; - constexpr size_t stateCount = NR_ELEMENTS(stateClasses); const FormSelectorOptions selectorSC(stateCount, stateClasses); selectorSC.addSelector( getPluginCustomArgName(F("TDSC"), varNr), Cache.getTaskVarStateClass(taskIndex, varNr)); } - #endif // if FEATURE_MQTT_STATE_CLASS + # endif // if FEATURE_MQTT_STATE_CLASS } addFormSeparator(colCount); } diff --git a/src/src/WebServer/DownloadPage.cpp b/src/src/WebServer/DownloadPage.cpp index 5eb3b4b08..4bd1d0e76 100644 --- a/src/src/WebServer/DownloadPage.cpp +++ b/src/src/WebServer/DownloadPage.cpp @@ -84,6 +84,9 @@ void handle_config_download(bool fullBackup, if (fullBackup && (nullptr != tarStream)) { const String security_dat = getFileName(FileType::SECURITY_DAT); +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + const String device_security_dat = getFileName(FileType::DEV_SECURITY_DAT); +#endif # if defined(ESP8266) fs::Dir dir = ESPEASY_FS.openDir(""); @@ -92,7 +95,13 @@ void handle_config_download(bool fullBackup, fs::File file = dir.openFile("r"); if (file) { - if (!noCreds || (noCreds && (0 != strncasecmp(file.name(), security_dat.c_str(), security_dat.length())))) { + if (!noCreds || + (noCreds + && (0 != strncasecmp(file.name(), security_dat.c_str(), security_dat.length())) +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + && (0 != strncasecmp(file.name(), device_security_dat.c_str(), device_security_dat.length())) +#endif + )) { tarStream->addFile(file.name(), file.size()); } file.close(); @@ -105,8 +114,14 @@ void handle_config_download(bool fullBackup, while (file) { if (!file.isDirectory()) { - if (!noCreds || (noCreds && (0 != strncasecmp(file.name(), security_dat.c_str(), security_dat.length())))) { - tarStream->addFile(file.name(), file.size()); + if (!noCreds || + (noCreds + && (0 != strncasecmp(file.name(), security_dat.c_str(), security_dat.length())) +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + && (0 != strncasecmp(file.name(), device_security_dat.c_str(), device_security_dat.length())) +#endif + )) { + tarStream->addFileIfExists(file.name()); } } file = root.openNextFile(); @@ -130,6 +145,10 @@ void handle_config_download(bool fullBackup, if (!noCreds) { tarStream->addFileIfExists(getFileName(FileType::SECURITY_DAT)); +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + // TODO TD-er: Must add separate flag for this +// tarStream->addFileIfExists(getFileName(FileType::DEV_SECURITY_DAT)); +#endif } // rules.txt files diff --git a/src/src/WebServer/ESPEasy_WebServer.cpp b/src/src/WebServer/ESPEasy_WebServer.cpp index 5ba648ab3..e97dcb819 100644 --- a/src/src/WebServer/ESPEasy_WebServer.cpp +++ b/src/src/WebServer/ESPEasy_WebServer.cpp @@ -23,7 +23,9 @@ #include "../WebServer/Markup.h" #include "../WebServer/Markup_Buttons.h" #include "../WebServer/Markup_Forms.h" +#include "../WebServer/NetworkPage.h" #include "../WebServer/NotificationPage.h" +#include "../WebServer/PluginListPage.h" #include "../WebServer/PinStates.h" #include "../WebServer/RootPage.h" #include "../WebServer/Rules.h" @@ -50,13 +52,14 @@ #include "../DataTypes/SettingsType.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../../ESPEasy/net/ESPEasyNetwork.h" #include "../ESPEasyCore/ESPEasyRules.h" -#include "../ESPEasyCore/ESPEasyWifi.h" +#include "../../ESPEasy/net/wifi/ESPEasyWifi.h" + #include "../Globals/CPlugins.h" #include "../Globals/Device.h" -#include "../Globals/NetworkState.h" +#include "../../ESPEasy/net/Globals/NetworkState.h" #include "../Globals/SecuritySettings.h" #include "../Globals/Settings.h" @@ -127,7 +130,7 @@ void sendHeadandTail_stdtemplate(bool Tail, bool rebooting) { sendHeadandTail(F("TmplStd"), Tail, rebooting); if (!Tail) { - if (!clientIPinSubnet() && WifiIsAP(WiFi.getMode()) && (WiFi.softAPgetStationNum() > 0)) { + if (!clientIPinSubnetDefaultNetwork() && ESPEasy::net::wifi::wifiAPmodeActivelyUsed()) { addHtmlError(F("Warning: Connected via AP")); } @@ -173,12 +176,14 @@ bool captivePortal() { const IPAddress client_localIP = web_server.client().localIP(); const bool fromAP = client_localIP == apIP; const bool hasWiFiCredentials = SecuritySettings.hasWiFiCredentials(); - +#ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, concat(F("CaptivePortal: hostHeader: "), web_server.hostHeader())); +#endif if (hasWiFiCredentials || !fromAP) { return false; } - if (!isIP(web_server.hostHeader()) && (web_server.hostHeader() != (NetworkGetHostname() + F(".local")))) { + if (!isIP(web_server.hostHeader()) && (web_server.hostHeader() != (ESPEasy::net::NetworkGetHostname() + F(".local")))) { String redirectURL = concat(F("http://"), formatIP(client_localIP)); #ifdef WEBSERVER_SETUP @@ -213,7 +218,14 @@ void WebServerInit() // Entries for several captive portal URLs. // Maybe not needed. Might be handled by notFound handler. web_server.on(UriGlob("/generate_204*"), handle_root); // Android captive portal. Handle "/generate_204_"-like requests. +//web_server.on(F("/generate_204"), handle_root); // android captive portal redirect web_server.on(F("/fwlink"), handle_root); // Microsoft captive portal. + web_server.on(F("/redirect"), handle_root); // microsoft redirect + web_server.on(F("/hotspot-detect.html"), handle_root); // apple call home + web_server.on(F("/canonical.html"), handle_root); // firefox captive portal call home + web_server.on(F("/success.txt"), handle_root); // firefox captive portal call home + web_server.on(F("/ncsi.txt"), handle_root); // windows call home + #endif // ifdef WEBSERVER_ROOT #ifdef WEBSERVER_ADVANCED web_server.on(F("/advanced"), handle_advanced); @@ -225,6 +237,9 @@ void WebServerInit() #ifdef WEBSERVER_CONFIG web_server.on(F("/config"), handle_config); #endif // ifdef WEBSERVER_CONFIG + #ifdef WEBSERVER_NETWORK + web_server.on(F("/network"), handle_networks); + #endif // ifdef WEBSERVER_NETWORK #ifdef WEBSERVER_CONTROL web_server.on(F("/control"), handle_control); #endif // ifdef WEBSERVER_CONTROL @@ -260,7 +275,9 @@ void WebServerInit() #ifdef WEBSERVER_I2C_SCANNER web_server.on(F("/i2cscanner"), handle_i2cscanner); #endif // ifdef WEBSERVER_I2C_SCANNER + #ifdef WEBSERVER_JSON web_server.on(F("/json"), handle_json); // Also part of WEBSERVER_NEW_UI + #endif #ifdef WEBSERVER_CSVVAL web_server.on(F("/csv"), handle_csvval); #endif @@ -299,6 +316,9 @@ void WebServerInit() #ifdef WEBSERVER_SYSVARS web_server.on(F("/sysvars"), handle_sysvars); #endif // WEBSERVER_SYSVARS +#if FEATURE_PLUGIN_LIST + web_server.on(F("/pluginlist"), handle_pluginlist); +#endif // if FEATURE_PLUGIN_LIST #ifdef WEBSERVER_TIMINGSTATS web_server.on(F("/timingstats"), handle_timingstats); #endif // WEBSERVER_TIMINGSTATS @@ -384,22 +404,24 @@ void setWebserverRunning(bool state) { if (webserverRunning == state) { return; } + ESPEasy::net::processNetworkEvents(); if (state) { WebServerInit(); web_server.begin(Settings.WebserverPort); - #ifndef BUILD_MINIMAL_OTA + #ifndef LIMIT_BUILD_SIZE addLog(LOG_LEVEL_INFO, F("Webserver: start")); #endif } else { web_server.client().stop(); web_server.stop(); - #ifndef BUILD_MINIMAL_OTA + + #ifndef LIMIT_BUILD_SIZE addLog(LOG_LEVEL_INFO, F("Webserver: stop")); #endif } webserverRunning = state; - CheckRunningServices(); // Uses webserverRunning state. + ESPEasy::net::CheckRunningServices(); // Uses webserverRunning state. } void getWebPageTemplateDefault(const String& tmplName, WebTemplateParser& parser) @@ -590,7 +612,7 @@ void writeDefaultCSS(void) // FIXME TD-er: replace stream_xxx_json_object* into this code. // N.B. handling of numerical values differs (string vs. no string) // ******************************************************************************** - +#ifdef WEBSERVER_NEW_UI int8_t level = 0; int8_t lastLevel = -1; @@ -610,9 +632,7 @@ void json_quote_name(const String& val) { } void json_quote_val(const String& val) { - addHtml('\"'); - addHtml(val); - addHtml('\"'); + addHtml(to_json_value(val)); } void json_open(bool arr) { @@ -673,59 +693,39 @@ void json_prop(const String& name, const String& value) { void json_prop(LabelType::Enum label) { json_prop(getInternalLabel(label, '-'), getValue(label)); } - +#endif // ******************************************************************************** // Add a task select dropdown list // This allows to select a task index based on the existing tasks. // ******************************************************************************** -void addTaskSelect(const String& name, taskIndex_t choice, const String& cssclass) +void addTaskSelect(const String& name, taskIndex_t choice) { String deviceName; - - addHtml(F("")); - html_TD(); + addHtml(F("")); + addHtml(F("\n")); } void html_TR_TD_height(int height) { @@ -93,7 +84,7 @@ void html_TD(int td_cnt) { html_TD(); } } - +#ifdef WEBSERVER_GITHUB_COPY int copyTextCounter = 0; void html_reset_copyTextCounter() { @@ -111,6 +102,7 @@ void html_copyText_TD() { void html_copyText_marker() { addHtml(F("⋄")); // ⋄ ⋄ ⋄ ⋄ ⋄ } +#endif void html_add_estimate_symbol() { addHtml(F(" ≙ ")); // ≙ ≙ ≙ @@ -267,11 +259,18 @@ void html_add_form() { addHtml(F("")); } +void html_add_JQuery_script(const __FlashStringHelper * url) +{ + addHtml(F("")); +} + void html_add_JQuery_script() { #ifndef CDN_URL_JQUERY #define CDN_URL_JQUERY "https://code.jquery.com/jquery-3.6.4.min.js" #endif // ifndef CDN_URL_JQUERY - addHtml(F("")); + html_add_JQuery_script(F(CDN_URL_JQUERY)); } #if FEATURE_CHART_JS @@ -281,7 +280,7 @@ void html_add_ChartJS_script() { // - Select the chart.js file (may be called chart.umd.min.js) and copy the url // - Replace the url in below script src element, keeping the quotes #ifndef CDN_URL_CHART_JS - #define CDN_URL_CHART_JS "https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js" + #define CDN_URL_CHART_JS "https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js" #endif // ifndef CDN_URL_CHART_JS #ifndef CDN_URL_CHART_JS_ADAPTER_DATE @@ -296,11 +295,23 @@ void html_add_ChartJS_script() { #define CDN_URL_CHART_JS_PLUGIN_ZOOM "https://cdn.jsdelivr.net/npm/chartjs-plugin-zoom@2.0.1/dist/chartjs-plugin-zoom.min.js" #endif + #ifndef CDN_URL_CHART_JS_LUXON + // Timestack internally uses Luxon for locale-aware time formatting. + #define CDN_URL_CHART_JS_LUXON "https://cdn.jsdelivr.net/npm/luxon@3.4.4/build/global/luxon.min.js" + #endif - addHtml(F("")); - addHtml(F("")); - addHtml(F("")); - addHtml(F("")); + #ifndef CDN_URL_CHART_JS_PLUGIN_SCALE_TIMESTACK + // Timestack tries hard to choose the ticks looking nice for humans, i.e. 14:00, 14:30, 15:00, 15:30 in hourly view and 1, 5, 10, 15, 25 days of the month in daily view. + // See: https://github.com/jkmnt/chartjs-scale-timestack + #define CDN_URL_CHART_JS_PLUGIN_SCALE_TIMESTACK "https://cdn.jsdelivr.net/npm/chartjs-scale-timestack/dist/chartjs-scale-timestack.min.js" + #endif + + html_add_JQuery_script(F(CDN_URL_CHART_JS)); + html_add_JQuery_script(F(CDN_URL_CHART_JS_ADAPTER_DATE)); + html_add_JQuery_script(F(CDN_URL_CHART_JS_HAMMERJS)); + html_add_JQuery_script(F(CDN_URL_CHART_JS_PLUGIN_ZOOM)); + html_add_JQuery_script(F(CDN_URL_CHART_JS_LUXON)); + html_add_JQuery_script(F(CDN_URL_CHART_JS_PLUGIN_SCALE_TIMESTACK)); } #endif // if FEATURE_CHART_JS @@ -313,14 +324,14 @@ void html_add_Easy_color_code_script() { #endif void html_add_autosubmit_form() { - addHtml(F("")); + "\n//-->"), false); } void html_add_script(const __FlashStringHelper * script, bool defer) { @@ -363,6 +374,7 @@ void addHtmlError(const __FlashStringHelper * error) { void addHtmlError(const String& error) { if (error.length() > 0) { + addLog(LOG_LEVEL_ERROR, error); addHtml(F("
")); + addHtml(html); + addHtml(F("")); +} + void addHtmlInt(int8_t int_val) { addHtml(String(int_val)); } @@ -437,11 +455,12 @@ void addHtmlInt(uint64_t int_val) { addHtml(ull2String(int_val)); } -void addHtmlFloat(const float& value, unsigned int nrDecimals) { +void addHtmlFloat(const float& value, uint8_t nrDecimals) { addHtml(toString(value, nrDecimals)); } -void addHtmlFloat_NaN_toNull(const float& value, unsigned int nrDecimals) { +/* +void addHtmlFloat_NaN_toNull(const float& value, uint8_t nrDecimals) { String res; if (toValidString(res, value, nrDecimals)) { addHtml(res); @@ -449,14 +468,15 @@ void addHtmlFloat_NaN_toNull(const float& value, unsigned int nrDecimals) { addHtml(F("null")); } } - +*/ #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE -void addHtmlFloat(const double& value, unsigned int nrDecimals) { +void addHtmlFloat(const double& value, uint8_t nrDecimals) { addHtml(doubleToString(value, nrDecimals)); } -void addHtmlFloat_NaN_toNull(const double& value, unsigned int nrDecimals) { +/* +void addHtmlFloat_NaN_toNull(const double& value, uint8_t nrDecimals) { String res; if (doubleToValidString(res, value, nrDecimals)) { addHtml(res); @@ -464,6 +484,7 @@ void addHtmlFloat_NaN_toNull(const double& value, unsigned int nrDecimals) { addHtml(F("null")); } } +*/ #endif @@ -502,11 +523,7 @@ void addHtmlAttribute(const __FlashStringHelper * label, float value) { } void addHtmlAttribute(const String& label, int value) { - addHtml(' '); - addHtml(label); - addHtml('='); - addHtmlInt(value); - addHtml(' '); + addHtml(strformat(F(" %s=%d "), label.c_str(), value)); } void addHtmlAttribute(const __FlashStringHelper * label, const __FlashStringHelper * value) { @@ -514,19 +531,11 @@ void addHtmlAttribute(const __FlashStringHelper * label, const __FlashStringHelp } void addHtmlAttribute(const __FlashStringHelper * label, const String& value) { - addHtml(' '); - addHtml(label); - addHtml('=', '\''); - addEncodedHtml(value); - addHtml('\'', ' '); + addHtmlAttribute(String(label), value); } void addHtmlAttribute(const String& label, const String& value) { - addHtml(' '); - addHtml(label); - addHtml('=', '\''); - addEncodedHtml(value); - addHtml('\'', ' '); + addHtml(strformat(F(" %s=\'%s\' "), label.c_str(), value.c_str())); } void addDisabled() { diff --git a/src/src/WebServer/HTML_wrappers.h b/src/src/WebServer/HTML_wrappers.h index 005393a84..3e4d63052 100644 --- a/src/src/WebServer/HTML_wrappers.h +++ b/src/src/WebServer/HTML_wrappers.h @@ -34,7 +34,7 @@ void html_TD(); void html_TD(const __FlashStringHelper * style); void html_TD(int td_cnt); - +#ifdef WEBSERVER_GITHUB_COPY extern int copyTextCounter; void html_reset_copyTextCounter(); @@ -43,6 +43,7 @@ void html_copyText_TD(); // Add some recognizable token to show which parts will be copied. void html_copyText_marker(); +#endif void html_add_estimate_symbol(); @@ -92,6 +93,7 @@ void html_add_wide_button_prefix(const String& classes, bool enabled); void html_add_form(); +void html_add_JQuery_script(const __FlashStringHelper * url); void html_add_JQuery_script(); #if FEATURE_CHART_JS void html_add_ChartJS_script(); @@ -121,6 +123,8 @@ void addHtml(const char& char1); void addHtml(const char& char1, const char& char2); void addHtml(const __FlashStringHelper * html); void addHtml(const String& html); +// Add text wrapped in
 ... 
(monospaced formatted) +void addHtml_pre(const String& html); void addHtml(String&& html); void addHtmlInt(int8_t int_val); void addHtmlInt(int16_t int_val); @@ -138,11 +142,11 @@ void addHtmlInt(int32_t int_val); void addHtmlInt(uint32_t int_val); void addHtmlInt(int64_t int_val); void addHtmlInt(uint64_t int_val); -void addHtmlFloat(const float& value, unsigned int nrDecimals = 2u); -void addHtmlFloat_NaN_toNull(const float& value, unsigned int nrDecimals = 2u); +void addHtmlFloat(const float& value, uint8_t nrDecimals = 2u); +//void addHtmlFloat_NaN_toNull(const float& value, uint8_t nrDecimals = 2u); #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE -void addHtmlFloat(const double& value, unsigned int nrDecimals = 2u); -void addHtmlFloat_NaN_toNull(const double& value, unsigned int nrDecimals = 2u); +void addHtmlFloat(const double& value, uint8_t nrDecimals = 2u); +//void addHtmlFloat_NaN_toNull(const double& value, uint8_t nrDecimals = 2u); #endif void addEncodedHtml(const __FlashStringHelper * html); diff --git a/src/src/WebServer/HardwarePage.cpp b/src/src/WebServer/HardwarePage.cpp index 32e4d1a58..7370cf812 100644 --- a/src/src/WebServer/HardwarePage.cpp +++ b/src/src/WebServer/HardwarePage.cpp @@ -121,17 +121,6 @@ void handle_hardware() { Settings.InitSPI = isFormItemChecked(F("initspi")); // SPI Init #endif Settings.Pin_sd_cs = getFormItemInt(F("sd")); - #if FEATURE_ETHERNET - Settings.ETH_Phy_Addr = getFormItemInt(F("ethphy")); - Settings.ETH_Pin_mdc_cs = getFormItemInt(F("ethmdc")); - Settings.ETH_Pin_mdio_irq = getFormItemInt(F("ethmdio")); - Settings.ETH_Pin_power_rst = getFormItemInt(F("ethpower")); - Settings.ETH_Phy_Type = static_cast(getFormItemInt(F("ethtype"))); -#if CONFIG_ETH_USE_ESP32_EMAC - Settings.ETH_Clock_Mode = static_cast(getFormItemInt(F("ethclock"))); -#endif - Settings.NetworkMedium = static_cast(getFormItemInt(F("ethwifi"))); - #endif // if FEATURE_ETHERNET int gpio = 0; while (gpio <= MAX_GPIO) { @@ -328,139 +317,6 @@ void handle_hardware() { addFormSubHeader(F("SD Card")); addFormPinSelect(PinSelectPurpose::SD_Card, formatGpioName_output(F("SD Card CS")), F("sd"), Settings.Pin_sd_cs); #endif // if FEATURE_SD - -#if FEATURE_ETHERNET - addFormSubHeader(F("Ethernet")); - addRowLabel_tr_id(F("Preferred network medium"), F("ethwifi")); - { - const __FlashStringHelper * ethWifiOptions[2] = { - toString(NetworkMedium_t::WIFI), - toString(NetworkMedium_t::Ethernet) - }; - const FormSelectorOptions selector(2, ethWifiOptions); - selector.addSelector(F("ethwifi"), static_cast(Settings.NetworkMedium)); - } - addFormNote(F("Change Switch between WiFi and Ethernet requires reboot to activate")); - { - const __FlashStringHelper * ethPhyTypes[] = { - toString(EthPhyType_t::notSet), - -# if CONFIG_ETH_USE_ESP32_EMAC - toString(EthPhyType_t::LAN8720), - toString(EthPhyType_t::TLK110), -#if ESP_IDF_VERSION_MAJOR > 3 - toString(EthPhyType_t::RTL8201), -#if ETH_TYPE_JL1101_SUPPORTED - toString(EthPhyType_t::JL1101), -#endif - toString(EthPhyType_t::DP83848), - toString(EthPhyType_t::KSZ8041), - toString(EthPhyType_t::KSZ8081), -#endif -# endif // if CONFIG_ETH_USE_ESP32_EMAC - -#if ESP_IDF_VERSION_MAJOR >= 5 -# if CONFIG_ETH_SPI_ETHERNET_DM9051 - toString(EthPhyType_t::DM9051), -# endif // if CONFIG_ETH_SPI_ETHERNET_DM9051 -# if CONFIG_ETH_SPI_ETHERNET_W5500 - toString(EthPhyType_t::W5500), -# endif // if CONFIG_ETH_SPI_ETHERNET_W5500 -# if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL - toString(EthPhyType_t::KSZ8851), -# endif // if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL -#endif - }; - const int ethPhyTypes_index[] = { - static_cast(EthPhyType_t::notSet), - -# if CONFIG_ETH_USE_ESP32_EMAC - static_cast(EthPhyType_t::LAN8720), - static_cast(EthPhyType_t::TLK110), -#if ESP_IDF_VERSION_MAJOR > 3 - static_cast(EthPhyType_t::RTL8201), -#if ETH_TYPE_JL1101_SUPPORTED - static_cast(EthPhyType_t::JL1101), -#endif - static_cast(EthPhyType_t::DP83848), - static_cast(EthPhyType_t::KSZ8041), - static_cast(EthPhyType_t::KSZ8081), -#endif -# endif // if CONFIG_ETH_USE_ESP32_EMAC - -#if ESP_IDF_VERSION_MAJOR >= 5 -# if CONFIG_ETH_SPI_ETHERNET_DM9051 - static_cast(EthPhyType_t::DM9051), -# endif // if CONFIG_ETH_SPI_ETHERNET_DM9051 -# if CONFIG_ETH_SPI_ETHERNET_W5500 - static_cast(EthPhyType_t::W5500), -# endif // if CONFIG_ETH_SPI_ETHERNET_W5500 -# if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL - static_cast(EthPhyType_t::KSZ8851), -# endif // if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL -#endif - }; - - constexpr unsigned nrItems = NR_ELEMENTS(ethPhyTypes_index); - - - const int choice = isValid(Settings.ETH_Phy_Type) - ? static_cast(Settings.ETH_Phy_Type) - : static_cast(EthPhyType_t::notSet); - - const FormSelectorOptions selector( - nrItems, - ethPhyTypes, - ethPhyTypes_index); - selector.addFormSelector( - F("Ethernet PHY type"), - F("ethtype"), - choice); - } - -#if CONFIG_ETH_USE_SPI_ETHERNET && CONFIG_ETH_USE_ESP32_EMAC -#define MDC_CS_PIN_DESCR "Ethernet MDC/CS pin" -#define MIO_IRQ_PIN_DESCR "Ethernet MDIO/IRQ pin" -#define PWR_RST_PIN_DESCR "Ethernet Power/RST pin" -#elif CONFIG_ETH_USE_SPI_ETHERNET -#define MDC_CS_PIN_DESCR "Ethernet CS pin" -#define MIO_IRQ_PIN_DESCR "Ethernet IRQ pin" -#define PWR_RST_PIN_DESCR "Ethernet RST pin" -#else // #elif CONFIG_ETH_USE_ESP32_EMAC -#define MDC_CS_PIN_DESCR "Ethernet MDC pin" -#define MIO_IRQ_PIN_DESCR "Ethernet MIO pin" -#define PWR_RST_PIN_DESCR "Ethernet Power pin" -#endif - - addFormNumericBox(F("Ethernet PHY Address"), F("ethphy"), Settings.ETH_Phy_Addr, -1, 127); - addFormNote(F("I²C-address of Ethernet PHY" -#if CONFIG_ETH_USE_ESP32_EMAC - " (0 or 1 for LAN8720, 31 for TLK110, -1 autodetect)" -#endif - )); - addFormPinSelect(PinSelectPurpose::Ethernet, formatGpioName_output( - F(MDC_CS_PIN_DESCR)), - F("ethmdc"), Settings.ETH_Pin_mdc_cs); - addFormPinSelect(PinSelectPurpose::Ethernet, formatGpioName_input( - F(MIO_IRQ_PIN_DESCR)), - F("ethmdio"), Settings.ETH_Pin_mdio_irq); - addFormPinSelect(PinSelectPurpose::Ethernet, formatGpioName_output( - F(PWR_RST_PIN_DESCR)), - F("ethpower"), Settings.ETH_Pin_power_rst); -#if CONFIG_ETH_USE_ESP32_EMAC - addRowLabel_tr_id(F("Ethernet Clock"), F("ethclock")); - { - const __FlashStringHelper * ethClockOptions[] = { - toString(EthClockMode_t::Ext_crystal_osc), - toString(EthClockMode_t::Int_50MHz_GPIO_0), - toString(EthClockMode_t::Int_50MHz_GPIO_16), - toString(EthClockMode_t::Int_50MHz_GPIO_17_inv) - }; - const FormSelectorOptions selector(NR_ELEMENTS(ethClockOptions), ethClockOptions); - selector.addSelector(F("ethclock"), static_cast(Settings.ETH_Clock_Mode)); - } -#endif -#endif // if FEATURE_ETHERNET addFormSubHeader(F("GPIO boot states")); diff --git a/src/src/WebServer/JSON.cpp b/src/src/WebServer/JSON.cpp index 93b4730fd..3e1e217fd 100644 --- a/src/src/WebServer/JSON.cpp +++ b/src/src/WebServer/JSON.cpp @@ -17,45 +17,51 @@ #include "../Helpers/_Plugin_init.h" #include "../Helpers/ESPEasyStatistics.h" #include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/ESPEasy_UnitOfMeasure.h" +#include "../Helpers/KeyValueWriter_JSON.h" #include "../Helpers/Numerical.h" #include "../Helpers/StringConverter.h" #include "../Helpers/StringProvider.h" #include "../Helpers/StringGenerator_System.h" +#include "../../ESPEasy/net/Helpers/NW_info_writer.h" + #include "../../_Plugin_Helper.h" #include "../../ESPEasy-Globals.h" -void stream_comma_newline() { - addHtml(',', '\n'); -} +void stream_comma_newline() { addHtml(',', '\n'); } // ******************************************************************************** // Web Interface get CSV value from task // ******************************************************************************** #ifdef WEBSERVER_CSVVAL + void handle_csvval() { TXBuffer.startJsonStream(); const int printHeader = getFormItemInt(F("header"), 1); bool printHeaderValid = true; - if (printHeader != 1 && printHeader != 0) + + if ((printHeader != 1) && (printHeader != 0)) { addHtml(F("ERROR: Header not valid!\n")); printHeaderValid = false; } - const taskIndex_t taskNr = getFormItemInt(F("tasknr"), INVALID_TASK_INDEX); - const bool taskValid = validTaskIndex(taskNr); + const taskIndex_t taskNr = getFormItemInt(F("tasknr"), INVALID_TASK_INDEX); + const bool taskValid = validTaskIndex(taskNr); + if (!taskValid) { addHtml(F("ERROR: TaskNr not valid!\n")); } const int INVALID_VALUE_NUM = INVALID_TASKVAR_INDEX + 1; - const taskVarIndex_t valNr = getFormItemInt(F("valnr"), INVALID_VALUE_NUM); - bool valueNumberValid = true; - if (valNr != INVALID_VALUE_NUM && !validTaskVarIndex(valNr)) + const taskVarIndex_t valNr = getFormItemInt(F("valnr"), INVALID_VALUE_NUM); + bool valueNumberValid = true; + + if ((valNr != INVALID_VALUE_NUM) && !validTaskVarIndex(valNr)) { addHtml(F("ERROR: ValueId not valid!\n")); valueNumberValid = false; @@ -73,9 +79,10 @@ void handle_csvval() { for (uint8_t x = 0; x < taskValCount; x++) { - if (valNr == INVALID_VALUE_NUM || valNr == x) + if ((valNr == INVALID_VALUE_NUM) || (valNr == x)) { addHtml(Cache.getTaskDeviceValueName(taskNr, x)); + if (x != taskValCount - 1) { addHtml(';'); @@ -104,8 +111,10 @@ void handle_csvval() } TXBuffer.endStream(); } -#endif +#endif // ifdef WEBSERVER_CSVVAL + +#ifdef WEBSERVER_JSON // ******************************************************************************** // Web Interface JSON page (no password!) // ******************************************************************************** @@ -117,557 +126,598 @@ void handle_json() bool showSystem = true; bool showWifi = true; - #if FEATURE_ETHERNET +#if FEATURE_ETHERNET bool showEthernet = true; - #endif // if FEATURE_ETHERNET +#endif // if FEATURE_ETHERNET bool showDataAcquisition = true; bool showTaskDetails = true; - #if FEATURE_ESPEASY_P2P - bool showNodes = true; - #endif - #if FEATURE_PLUGIN_STATS - bool showPluginStats = getFormItemInt(F("showpluginstats"), 0) != 0; - #endif +#if FEATURE_ESPEASY_P2P + bool showNodes = true; +#endif +#if FEATURE_PLUGIN_STATS && FEATURE_CHART_JS + bool showPluginStats = getFormItemInt(F("showpluginstats"), 0) != 0; +#endif if (equals(webArg(F("view")), F("sensorupdate"))) { showSystem = false; showWifi = false; - #if FEATURE_ETHERNET +#if FEATURE_ETHERNET showEthernet = false; - #endif // if FEATURE_ETHERNET +#endif // if FEATURE_ETHERNET showDataAcquisition = false; showTaskDetails = false; - #if FEATURE_ESPEASY_P2P - showNodes = false; - #endif - #if FEATURE_PLUGIN_STATS - showPluginStats = hasArg(F("showpluginstats")); - #endif +#if FEATURE_ESPEASY_P2P + showNodes = false; +#endif +#if FEATURE_PLUGIN_STATS && FEATURE_CHART_JS + showPluginStats = hasArg(F("showpluginstats")); +#endif } TXBuffer.startJsonStream(); - - if (!showSpecificTask) { - addHtml('{'); - if (showSystem) { - addHtml(F("\"System\":{\n")); + KeyValueWriter_JSON mainLevelWriter(true); - if (wdcounter > 0) - { - stream_next_json_object_value(LabelType::LOAD_PCT); - stream_next_json_object_value(LabelType::LOOP_COUNT); + if (!showSpecificTask) + { + if (showSystem) { + auto writer = mainLevelWriter.createChild(F("System")); + + if (writer) { + + if (wdcounter > 0) + { + static const LabelType::Enum wdlabels[] PROGMEM = + { + LabelType::LOAD_PCT, + LabelType::LOOP_COUNT, + + LabelType::MAX_LABEL + }; + writer->writeLabels(wdlabels); + + } + + + static const LabelType::Enum labels[] PROGMEM = + { + LabelType::BUILD_DESC, + LabelType::GIT_BUILD, + LabelType::SYSTEM_LIBRARIES, +#ifdef ESP32 + LabelType::ESP_IDF_SDK_VERSION, +#endif + LabelType::PLUGIN_COUNT, + LabelType::PLUGIN_DESCRIPTION, + LabelType::BUILD_TIME, + LabelType::BINARY_FILENAME, + LabelType::LOCAL_TIME, +#if FEATURE_EXT_RTC + LabelType::EXT_RTC_UTC_TIME, +#endif + LabelType::TIME_SOURCE, + LabelType::TIME_WANDER, + LabelType::ISNTP, + LabelType::UNIT_NR, + LabelType::UNIT_NAME, + LabelType::UPTIME, + LabelType::UPTIME_MS, +#if FEATURE_INTERNAL_TEMPERATURE + LabelType::INTERNAL_TEMPERATURE, +#endif + LabelType::BOOT_TYPE, + LabelType::RESET_REASON, + LabelType::CPU_ECO_MODE, + +#if defined(CORE_POST_2_5_0) || defined(ESP32) +# ifndef LIMIT_BUILD_SIZE + LabelType::HEAP_MAX_FREE_BLOCK, // 7654 +# endif +#endif // if defined(CORE_POST_2_5_0) || defined(ESP32) +#if defined(CORE_POST_2_5_0) +# ifndef LIMIT_BUILD_SIZE + LabelType::HEAP_FRAGMENTATION, // 12 +# endif +#endif // if defined(CORE_POST_2_5_0) + LabelType::FREE_MEM, +#ifdef USE_SECOND_HEAP + LabelType::FREE_HEAP_IRAM, +#endif + LabelType::FREE_STACK, + +#ifdef ESP32 + LabelType::HEAP_SIZE, + LabelType::HEAP_MIN_FREE, +# ifdef BOARD_HAS_PSRAM + LabelType::PSRAM_SIZE, + LabelType::PSRAM_FREE, + LabelType::PSRAM_MIN_FREE, + LabelType::PSRAM_MAX_FREE_BLOCK, +# endif // BOARD_HAS_PSRAM +#endif // ifdef ESP32 + LabelType::ESP_CHIP_MODEL, +#ifdef ESP32 + LabelType::ESP_CHIP_REVISION, +#endif // ifdef ESP32 + LabelType::FLASH_CHIP_ID, + LabelType::FLASH_CHIP_VENDOR, + LabelType::FLASH_CHIP_MODEL, + LabelType::FLASH_CHIP_REAL_SIZE, + LabelType::FLASH_CHIP_SPEED, + LabelType::FLASH_IDE_MODE, + LabelType::FS_SIZE, + + LabelType::SUNRISE, + LabelType::SUNSET, + LabelType::TIMEZONE_OFFSET, + LabelType::LATITUDE, + LabelType::LONGITUDE, +#if FEATURE_SYSLOG + LabelType::SYSLOG_LOG_LEVEL, +#endif + LabelType::SERIAL_LOG_LEVEL, +# ifdef WEBSERVER_LOG + LabelType::WEB_LOG_LEVEL, +#endif +#if FEATURE_SD + LabelType::SD_LOG_LEVEL, +#endif // if FEATURE_SD + + + LabelType::MAX_LABEL + }; + writer->writeLabels(labels); + } } - static const LabelType::Enum labels[] PROGMEM = - { - LabelType::BUILD_DESC, - LabelType::GIT_BUILD, - LabelType::SYSTEM_LIBRARIES, -#ifdef ESP32 - LabelType::ESP_IDF_SDK_VERSION, + if (showWifi) { + for (ESPEasy::net::networkIndex_t x = 0; x < NETWORK_MAX; ++x) + { + if (Settings.getNetworkEnabled(x)) { + auto pluginID = Settings.getNWPluginID_for_network(x); + + if (pluginID != ESPEasy::net::INVALID_NW_PLUGIN_ID) { + auto writer = mainLevelWriter.createChild(getNWPluginNameFromNWPluginID(pluginID)); + + if (writer) { + writer->write({ F("Network Index"), x + 1 }); +#ifdef WEBSERVER_NETWORK +# ifdef ESP32 + ESPEasy::net::write_NetworkAdapterFlags(x, writer->createChild(F("Interface")).get()); +#ifndef LIMIT_BUILD_SIZE + ESPEasy::net::write_NetworkAdapterPort(x, writer->createChild(F("Port")).get()); #endif - LabelType::PLUGIN_COUNT, - LabelType::PLUGIN_DESCRIPTION, - LabelType::BUILD_TIME, - LabelType::BINARY_FILENAME, - LabelType::LOCAL_TIME, - #if FEATURE_EXT_RTC - LabelType::EXT_RTC_UTC_TIME, - #endif - LabelType::TIME_SOURCE, - LabelType::TIME_WANDER, - LabelType::ISNTP, - LabelType::UNIT_NR, - LabelType::UNIT_NAME, - LabelType::UPTIME, - LabelType::UPTIME_MS, -#if FEATURE_INTERNAL_TEMPERATURE - LabelType::INTERNAL_TEMPERATURE, -#endif - LabelType::BOOT_TYPE, - LabelType::RESET_REASON, - LabelType::CPU_ECO_MODE, + ESPEasy::net::write_IP_config(x, writer->createChild(F("IP")).get()); +# endif // ifdef ESP32 +#endif // ifdef WEBSERVER_NETWORK + ESPEasy::net::write_NetworkConnectionInfo(x, writer->createChild(F("Connection")).get()); + } - #if defined(CORE_POST_2_5_0) || defined(ESP32) - #ifndef LIMIT_BUILD_SIZE - LabelType::HEAP_MAX_FREE_BLOCK, // 7654 - #endif - #endif // if defined(CORE_POST_2_5_0) || defined(ESP32) - #if defined(CORE_POST_2_5_0) - #ifndef LIMIT_BUILD_SIZE - LabelType::HEAP_FRAGMENTATION, // 12 - #endif - #endif // if defined(CORE_POST_2_5_0) - LabelType::FREE_MEM, - #ifdef USE_SECOND_HEAP - LabelType::FREE_HEAP_IRAM, - #endif - LabelType::FREE_STACK, - - #ifdef ESP32 - LabelType::HEAP_SIZE, - LabelType::HEAP_MIN_FREE, - #ifdef BOARD_HAS_PSRAM - LabelType::PSRAM_SIZE, - LabelType::PSRAM_FREE, - LabelType::PSRAM_MIN_FREE, - LabelType::PSRAM_MAX_FREE_BLOCK, - #endif // BOARD_HAS_PSRAM - #endif // ifdef ESP32 - LabelType::ESP_CHIP_MODEL, - #ifdef ESP32 - LabelType::ESP_CHIP_REVISION, - #endif // ifdef ESP32 - LabelType::FLASH_CHIP_ID, - LabelType::FLASH_CHIP_VENDOR, - LabelType::FLASH_CHIP_MODEL, - LabelType::FLASH_CHIP_REAL_SIZE, - LabelType::FLASH_CHIP_SPEED, - LabelType::FLASH_IDE_MODE, - LabelType::FS_SIZE, - - LabelType::SUNRISE, - LabelType::SUNSET, - LabelType::TIMEZONE_OFFSET, - LabelType::LATITUDE, - LabelType::LONGITUDE, - LabelType::SYSLOG_LOG_LEVEL, - LabelType::SERIAL_LOG_LEVEL, - LabelType::WEB_LOG_LEVEL, - #if FEATURE_SD - LabelType::SD_LOG_LEVEL, - #endif // if FEATURE_SD - - - LabelType::MAX_LABEL - }; - - stream_json_object_values(labels); - stream_comma_newline(); - } - - if (showWifi) { - addHtml(F("\"WiFi\":{\n")); - static const LabelType::Enum labels[] PROGMEM = - { - LabelType::HOST_NAME, - #if FEATURE_MDNS - LabelType::M_DNS, - #endif // if FEATURE_MDNS - LabelType::IP_CONFIG, - LabelType::IP_ADDRESS, -#if FEATURE_USE_IPV6 - LabelType::IP6_LOCAL, - LabelType::IP6_GLOBAL, - LabelType::ENABLE_IPV6, -#endif - LabelType::IP_SUBNET, - LabelType::GATEWAY, - LabelType::STA_MAC, - LabelType::DNS_1, - LabelType::DNS_2, - LabelType::SSID, - LabelType::BSSID, - LabelType::CHANNEL, - LabelType::ENCRYPTION_TYPE_STA, - LabelType::CONNECTED_MSEC, - LabelType::LAST_DISCONNECT_REASON, - LabelType::LAST_DISC_REASON_STR, - LabelType::NUMBER_RECONNECTS, - LabelType::WIFI_STORED_SSID1, - LabelType::WIFI_STORED_SSID2, - LabelType::FORCE_WIFI_BG, - LabelType::RESTART_WIFI_LOST_CONN, - LabelType::FORCE_WIFI_NOSLEEP, + if (x == 0) { + static const LabelType::Enum labels[] PROGMEM = + { + LabelType::HOST_NAME, +#if FEATURE_MDNS + LabelType::M_DNS, +#endif // if FEATURE_MDNS + // LabelType::IP_CONFIG, +#ifdef ESP8266 + LabelType::IP_ADDRESS, +# if FEATURE_USE_IPV6 + LabelType::IP6_LOCAL, + LabelType::IP6_GLOBAL, + LabelType::ENABLE_IPV6, +# endif // if FEATURE_USE_IPV6 + LabelType::IP_SUBNET, + LabelType::GATEWAY, + LabelType::STA_MAC, + LabelType::DNS_1, + LabelType::DNS_2, +#endif // ifdef ESP8266 + LabelType::SSID, + LabelType::BSSID, + LabelType::CHANNEL, + LabelType::ENCRYPTION_TYPE_STA, + LabelType::CONNECTED_MSEC, + LabelType::LAST_DISCONNECT_REASON, + LabelType::LAST_DISC_REASON_STR, + LabelType::NUMBER_RECONNECTS, + LabelType::WIFI_STORED_SSID1, + LabelType::WIFI_STORED_SSID2, + LabelType::FORCE_WIFI_BG, + LabelType::RESTART_WIFI_LOST_CONN, + LabelType::FORCE_WIFI_NOSLEEP, #ifdef SUPPORT_ARP - LabelType::PERIODICAL_GRAT_ARP, + LabelType::PERIODICAL_GRAT_ARP, #endif // ifdef SUPPORT_ARP #ifdef USES_ESPEASY_NOW - LabelType::USE_ESPEASY_NOW, - LabelType::FORCE_ESPEASY_NOW_CHANNEL, -#endif - LabelType::CONNECTION_FAIL_THRESH, + LabelType::USE_ESPEASY_NOW, + LabelType::FORCE_ESPEASY_NOW_CHANNEL, +#endif // ifdef USES_ESPEASY_NOW + LabelType::CONNECTION_FAIL_THRESH, #if FEATURE_SET_WIFI_TX_PWR - LabelType::WIFI_TX_MAX_PWR, - LabelType::WIFI_CUR_TX_PWR, - LabelType::WIFI_SENS_MARGIN, - LabelType::WIFI_SEND_AT_MAX_TX_PWR, -#endif - LabelType::WIFI_NR_EXTRA_SCANS, + LabelType::WIFI_TX_MAX_PWR, + LabelType::WIFI_CUR_TX_PWR, + LabelType::WIFI_SENS_MARGIN, + LabelType::WIFI_SEND_AT_MAX_TX_PWR, +#endif // if FEATURE_SET_WIFI_TX_PWR + LabelType::WIFI_NR_RECONNECT_ATTEMPTS, + LabelType::WIFI_MAX_UPTIME_AUTO_START_AP, + LabelType::WIFI_AP_MINIMAL_ON_TIME, #ifdef ESP32 - LabelType::WIFI_PASSIVE_SCAN, + LabelType::WIFI_PASSIVE_SCAN, #endif - LabelType::WIFI_USE_LAST_CONN_FROM_RTC, - LabelType::WIFI_RSSI, + LabelType::WIFI_USE_LAST_CONN_FROM_RTC, + LabelType::WIFI_RSSI, #ifndef ESP32 - LabelType::WAIT_WIFI_CONNECT, + LabelType::WAIT_WIFI_CONNECT, #endif - LabelType::HIDDEN_SSID_SLOW_CONNECT, - LabelType::CONNECT_HIDDEN_SSID, - LabelType::SDK_WIFI_AUTORECONNECT, + LabelType::HIDDEN_SSID_SLOW_CONNECT, + LabelType::CONNECT_HIDDEN_SSID, + LabelType::SDK_WIFI_AUTORECONNECT, - LabelType::MAX_LABEL - }; + LabelType::MAX_LABEL + }; + writer->writeLabels(labels); - stream_json_object_values(labels); - - // TODO: PKR: Add ETH Objects - stream_comma_newline(); - } - - #if FEATURE_ETHERNET - - if (showEthernet) { - addHtml(F("\"Ethernet\":{\n")); - static const LabelType::Enum labels[] PROGMEM = - { - LabelType::ETH_WIFI_MODE, - LabelType::ETH_CONNECTED, - LabelType::ETH_CHIP, - LabelType::ETH_DUPLEX, - LabelType::ETH_SPEED, - LabelType::ETH_STATE, - LabelType::ETH_SPEED_STATE, - - - LabelType::MAX_LABEL - }; - - stream_json_object_values(labels); - stream_comma_newline(); - } - #endif // if FEATURE_ETHERNET - - #if FEATURE_ESPEASY_P2P - if (showNodes) { - bool comma_between = false; - - for (auto it = Nodes.begin(); it != Nodes.end(); ++it) - { - if (it->second.ip[0] != 0) - { - if (comma_between) { - addHtml(','); - } else { - comma_between = true; - addHtml(F("\"nodes\":[\n")); // open json array if >0 nodes - } - - addHtml('{'); - stream_next_json_object_value(F("nr"), it->first); - stream_next_json_object_value(F("name"), - (it->first != Settings.Unit) ? it->second.getNodeName() : Settings.getName()); - - if (it->second.build) { - stream_next_json_object_value(F("build"), formatSystemBuildNr(it->second.build)); - } - - if (it->second.nodeType) { - stream_next_json_object_value(F("platform"), it->second.getNodeTypeDisplayString()); - } - const int8_t rssi = it->second.getRSSI(); - if (rssi < 0) { - stream_next_json_object_value(F("rssi"), rssi); - } - if (it->second.build >= 20107) { - stream_next_json_object_value(F("load"), toString(it->second.getLoad(), 2)); - if (it->second.webgui_portnumber != 80) { - stream_next_json_object_value(F("webport"), it->second.webgui_portnumber); + // TODO: PKR: Add ETH Objects + } } } - stream_next_json_object_value(F("ip"), formatIP(it->second.IP())); -#if FEATURE_USE_IPV6 - if (it->second.hasIPv6_mac_based_link_local) { - stream_next_json_object_value(F("ipv6local"), formatIP(it->second.IPv6_link_local(true), true)); - } - if (it->second.hasIPv6_mac_based_link_global) { - stream_next_json_object_value(F("ipv6global"), formatIP(it->second.IPv6_global())); - } -#endif - stream_last_json_object_value(F("age"), it->second.getAge()); - } // if node info exists - } // for loop - - if (comma_between) { - addHtml(F("],\n")); // close array if >0 nodes + } } + +#if FEATURE_ESPEASY_P2P + + if (showNodes) { + auto nodesWriter = mainLevelWriter.createChildArray(F("nodes")); + + if (nodesWriter) { + for (auto it = Nodes.begin(); it != Nodes.end(); ++it) + { + if (it->second.ip[0] != 0) + { + auto writer = nodesWriter->createChild(); + + if (writer) { + writer->write({ F("nr"), it->first }); + writer->write({ F("name"), + (it->first != Settings.Unit) ? it->second.getNodeName() : Settings.getName() }); + + if (it->second.build) { + writer->write({ F("build"), formatSystemBuildNr(it->second.build) }); + } + + if (it->second.nodeType) { + writer->write({ F("platform"), it->second.getNodeTypeDisplayString() }); + } + const int8_t rssi = it->second.getRSSI(); + + if (rssi < 0) { + writer->write({ F("rssi"), rssi }); + } + + if (it->second.build >= 20107) { + writer->write({ F("load"), toString(it->second.getLoad(), 2) }); + + if (it->second.webgui_portnumber != 80) { + writer->write({ F("webport"), it->second.webgui_portnumber }); + } + } + writer->write({ F("ip"), formatIP(it->second.IP()) }); +# if FEATURE_USE_IPV6 + + if (it->second.hasIPv6_mac_based_link_local) { + writer->write({ F("ipv6local"), formatIP(it->second.IPv6_link_local(true), true) }); + } + + if (it->second.hasIPv6_mac_based_link_global) { + writer->write({ F("ipv6global"), formatIP(it->second.IPv6_global()) }); + } +# endif // if FEATURE_USE_IPV6 + writer->write({ F("age"), it->second.getAge() }); + } // if node info exists + } // for loop + } + } + } +#endif // if FEATURE_ESPEASY_P2P } - #endif - } - taskIndex_t firstTaskIndex = 0; - taskIndex_t lastTaskIndex = TASKS_MAX - 1; + taskIndex_t firstTaskIndex = 0; + taskIndex_t lastTaskIndex = TASKS_MAX - 1; - if (showSpecificTask) - { - firstTaskIndex = taskNr - 1; - lastTaskIndex = taskNr - 1; - } - taskIndex_t lastActiveTaskIndex = 0; - - for (taskIndex_t TaskIndex = firstTaskIndex; TaskIndex <= lastTaskIndex; TaskIndex++) { - if (validPluginID_fullcheck(Settings.getPluginID_for_task(TaskIndex))) { - lastActiveTaskIndex = TaskIndex; - } - } - - if (!showSpecificTask) { - addHtml(F("\"Sensors\":[\n")); - } - - // Keep track of the lowest reported TTL and use that as refresh interval. - unsigned long lowest_ttl_json = 60; - - for (taskIndex_t TaskIndex = firstTaskIndex; TaskIndex <= lastActiveTaskIndex && validTaskIndex(TaskIndex); TaskIndex++) - { - const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(TaskIndex); - - if (validDeviceIndex(DeviceIndex)) + if (showSpecificTask) { - const unsigned long taskInterval = Settings.TaskDeviceTimer[TaskIndex]; - //LoadTaskSettings(TaskIndex); - addHtml('{', '\n'); + firstTaskIndex = taskNr - 1; + lastTaskIndex = taskNr - 1; + } + taskIndex_t lastActiveTaskIndex = 0; - unsigned long ttl_json = 60; // Default value - - // For simplicity, do the optional values first. - const uint8_t valueCount = getValueCountForTask(TaskIndex); - - if (valueCount != 0) { - if (Settings.TaskDeviceEnabled[TaskIndex]) { - if (taskInterval == 0) { - ttl_json = 1; - } else { - ttl_json = taskInterval; - } - - if (ttl_json < lowest_ttl_json) { - lowest_ttl_json = ttl_json; - } - } - addHtml(F("\"TaskValues\": [\n")); - - struct EventStruct TempEvent(TaskIndex); - - for (uint8_t x = 0; x < valueCount; x++) - { - uint8_t nrDecimals = Cache.getTaskDeviceValueDecimals(TaskIndex, x); - String value = formatUserVarNoCheck(&TempEvent, x); - #if FEATURE_STRING_VARIABLES - bool hasPresentation; - const String presentation = formatUserVarForPresentation(&TempEvent, x, hasPresentation, value, DeviceIndex); - #endif // if FEATURE_STRING_VARIABLES - - if (mustConsiderAsJSONString(value)) { - // Flag as not to treat as a float - nrDecimals = 255; - } - #if FEATURE_TASKVALUE_UNIT_OF_MEASURE - String uom; - const uint8_t uomIndex = Cache.getTaskVarUnitOfMeasure(TaskIndex, x); - if (uomIndex != 0) { - uom = toUnitOfMeasureName(uomIndex); - } - #else // if FEATURE_TASKVALUE_UNIT_OF_MEASURE - const String uom; - #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE - handle_json_stream_task_value_data(x + 1, - Cache.getTaskDeviceValueName(TaskIndex, x), - nrDecimals, - value, - #if FEATURE_STRING_VARIABLES - presentation, - #else // if FEATURE_STRING_VARIABLES - EMPTY_STRING, - #endif // if FEATURE_STRING_VARIABLES - uom, - x < (valueCount - 1)); - } - #if FEATURE_STRING_VARIABLES - if (Settings.ShowDerivedTaskValues(TaskIndex)) { - int varNr = VARS_PER_TASK; - String taskName = getTaskDeviceName(TaskIndex); - taskName.toLowerCase(); - String postfix; - const String search = getDerivedValueSearchAndPostfix(taskName, postfix); - - auto it = customStringVar.begin(); - while (it != customStringVar.end()) { - if (it->first.startsWith(search) && it->first.endsWith(postfix)) { - String valueName = it->first.substring(search.length(), it->first.indexOf('-')); - String uom; - String vType; - const String vname2 = getDerivedValueNameUomAndVType(taskName, valueName, uom, vType); - if (!vname2.isEmpty()) { - valueName = vname2; - } - if (!it->second.isEmpty()) { - String value(it->second); - stripEscapeCharacters(value); - value = parseTemplate(value); - uint8_t nrDecimals = 255; // FIXME Use the minimal number of decimals needed - bool hasPresentation; - const String presentation = formatUserVarForPresentation(&TempEvent, INVALID_TASKVAR_INDEX, hasPresentation, value, DeviceIndex, valueName); - - stream_comma_newline(); // Push out a comma and newline - handle_json_stream_task_value_data(varNr + 1, - valueName, - nrDecimals, - value, - presentation, - uom, - false); // No comma here - ++varNr; - } - } - else if (it->first.substring(0, search.length()).compareTo(search) > 0) { - break; - } - ++it; - } - } - #endif // if FEATURE_STRING_VARIABLES - addHtml(F("],\n")); + for (taskIndex_t TaskIndex = firstTaskIndex; TaskIndex <= lastTaskIndex; TaskIndex++) { + if (validPluginID_fullcheck(Settings.getPluginID_for_task(TaskIndex))) { + lastActiveTaskIndex = TaskIndex; } + } + + // Keep track of the lowest reported TTL and use that as refresh interval. + uint32_t lowest_ttl_json = 60; + { + auto sensorsWriter = showSpecificTask + ? mainLevelWriter.createNew() + : mainLevelWriter.createChildArray(F("Sensors")); + + if (sensorsWriter) { + sensorsWriter->setWriteWhenEmpty(); + for (taskIndex_t TaskIndex = firstTaskIndex; TaskIndex <= lastActiveTaskIndex && validTaskIndex(TaskIndex); TaskIndex++) + { + const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(TaskIndex); + + if (validDeviceIndex(DeviceIndex)) + { + const uint32_t taskInterval = Settings.TaskDeviceTimer[TaskIndex]; + + auto taskWriter = sensorsWriter->createChild(); + + if (taskWriter) { + + // LoadTaskSettings(TaskIndex); + + uint32_t ttl_json = 60; // Default value + + // For simplicity, do the optional values first. + const uint8_t valueCount = getValueCountForTask(TaskIndex); + + if (valueCount != 0) { + if (Settings.TaskDeviceEnabled[TaskIndex]) { + if (taskInterval == 0) { + ttl_json = 1; + } else { + ttl_json = taskInterval; + } + + if (ttl_json < lowest_ttl_json) { + lowest_ttl_json = ttl_json; + } + } + auto taskValueWriter = taskWriter->createChildArray(F("TaskValues")); + + if (taskValueWriter) { + struct EventStruct TempEvent(TaskIndex); + + for (uint8_t x = 0; x < valueCount; x++) + { + uint8_t nrDecimals = Cache.getTaskDeviceValueDecimals(TaskIndex, x); + String value = formatUserVarNoCheck(&TempEvent, x); +#if FEATURE_STRING_VARIABLES + bool hasPresentation; + const String presentation = formatUserVarForPresentation(&TempEvent, x, hasPresentation, value, DeviceIndex); +#endif // if FEATURE_STRING_VARIABLES + + if (mustConsiderAsJSONString(value)) { + // Flag as not to treat as a float + nrDecimals = 255; + } +#if FEATURE_TASKVALUE_UNIT_OF_MEASURE + String uom; + const uint8_t uomIndex = Cache.getTaskVarUnitOfMeasure(TaskIndex, x); + + if (uomIndex != 0) { + uom = toUnitOfMeasureName(uomIndex); + } +#else // if FEATURE_TASKVALUE_UNIT_OF_MEASURE + const String uom; +#endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE + handle_json_stream_task_value_data(taskValueWriter.get(), + x + 1, + Cache.getTaskDeviceValueName(TaskIndex, x), + nrDecimals, + value, +#if FEATURE_STRING_VARIABLES + presentation, +#else // if FEATURE_STRING_VARIABLES + EMPTY_STRING, +#endif // if FEATURE_STRING_VARIABLES + uom); + } +#if FEATURE_STRING_VARIABLES + + if (Settings.ShowDerivedTaskValues(TaskIndex)) { + int varNr = VARS_PER_TASK; + String taskName = getTaskDeviceName(TaskIndex); + taskName.toLowerCase(); + String postfix; + const String search = getDerivedValueSearchAndPostfix(taskName, postfix); + + auto it = customStringVar.begin(); + + while (it != customStringVar.end()) { + if (it->first.startsWith(search) && it->first.endsWith(postfix)) { + String valueName = it->first.substring(search.length(), it->first.indexOf('-')); + String uom; + String vType; + const String vname2 = getDerivedValueNameUomAndVType(taskName, valueName, uom, vType); + + if (!vname2.isEmpty()) { + valueName = vname2; + } + + if (!it->second.isEmpty()) { + String value(it->second); + stripEscapeCharacters(value); + value = parseTemplate(value); + uint8_t nrDecimals = 255; // FIXME Use the minimal number of decimals needed + bool hasPresentation; + const String presentation = + formatUserVarForPresentation(&TempEvent, + INVALID_TASKVAR_INDEX, + hasPresentation, + value, + DeviceIndex, + valueName); + + handle_json_stream_task_value_data(taskValueWriter.get(), + varNr + 1, + valueName, + nrDecimals, + value, + presentation, + uom); + ++varNr; + } + } + else if (it->first.substring(0, search.length()).compareTo(search) > 0) { + break; + } + ++it; + } + } +#endif // if FEATURE_STRING_VARIABLES + } + } #if FEATURE_PLUGIN_STATS && FEATURE_CHART_JS - if (showPluginStats && Device[DeviceIndex].PluginStats) { - PluginTaskData_base *taskData = getPluginTaskDataBaseClassOnly(TaskIndex); - if (taskData != nullptr && taskData->nrSamplesPresent() > 0) { - addHtml(F("\"PluginStats\":\n")); - taskData->plot_ChartJS(true); - stream_comma_newline(); - } - } -#endif + + if (showPluginStats && Device[DeviceIndex].PluginStats) { + PluginTaskData_base *taskData = getPluginTaskDataBaseClassOnly(TaskIndex); + + if ((taskData != nullptr) && (taskData->nrSamplesPresent() > 0)) { + stream_comma_newline(); + addHtml(F("\"PluginStats\":\n")); + taskData->plot_ChartJS(true); + } + } +#endif // if FEATURE_PLUGIN_STATS && FEATURE_CHART_JS - if (showSpecificTask) { - stream_next_json_object_value(F("TTL"), ttl_json * 1000); - #if FEATURE_TASKVALUE_UNIT_OF_MEASURE - stream_next_json_object_value(F("ShowUoM"), jsonBool(Settings.ShowUnitOfMeasureOnDevicesPage())); - #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE - } + if (showDataAcquisition) { + auto dataAquisitionWriter = taskWriter->createChildArray(F("DataAcquisition")); - if (showDataAcquisition) { - addHtml(F("\"DataAcquisition\": [\n")); + if (dataAquisitionWriter) { + for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) + { + auto controllerWriter = dataAquisitionWriter->createChild(); - for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) - { - addHtml('{'); - stream_next_json_object_value(F("Controller"), x + 1); - stream_next_json_object_value(F("IDX"), Settings.TaskDeviceID[x][TaskIndex]); - stream_last_json_object_value(F("Enabled"), jsonBool(Settings.TaskDeviceSendData[x][TaskIndex])); + if (controllerWriter) { + controllerWriter->write({ F("Controller"), x + 1 }); + controllerWriter->write({ F("IDX"), Settings.TaskDeviceID[x][TaskIndex] }); + controllerWriter->write({ F("Enabled"), !!Settings.TaskDeviceSendData[x][TaskIndex] }); + } + } + } + } - if (x < (CONTROLLER_MAX - 1)) { - stream_comma_newline(); - } - } - addHtml(F("],\n")); - } + if (showTaskDetails) { + taskWriter->write({ F("TaskInterval"), taskInterval }); + taskWriter->write({ F("Type"), getPluginNameFromDeviceIndex(DeviceIndex) }); + taskWriter->write({ F("TaskName"), getTaskDeviceName(TaskIndex) }); + taskWriter->write({ F("TaskDeviceNumber"), Settings.getPluginID_for_task(TaskIndex).value }); - if (showTaskDetails) { - stream_next_json_object_value(F("TaskInterval"), taskInterval); - stream_next_json_object_value(F("Type"), getPluginNameFromDeviceIndex(DeviceIndex)); - stream_next_json_object_value(F("TaskName"), getTaskDeviceName(TaskIndex)); - stream_next_json_object_value(F("TaskDeviceNumber"), Settings.getPluginID_for_task(TaskIndex).value); - for(int i = 0; i < 3; i++) { - if (Settings.TaskDevicePin[i][TaskIndex] >= 0) { - stream_next_json_object_value(concat(F("TaskDeviceGPIO"), i + 1) , static_cast(Settings.TaskDevicePin[i][TaskIndex])); - } - } + for (int i = 0; i < 3; i++) { + if (Settings.TaskDevicePin[i][TaskIndex] >= 0) { + taskWriter->write({ concat(F("TaskDeviceGPIO"), i + 1), static_cast(Settings.TaskDevicePin[i][TaskIndex]) }); + } + } - #if FEATURE_I2CMULTIPLEXER - uint8_t i2cBus = 0; - #if FEATURE_I2C_MULTIPLE - i2cBus = Settings.getI2CInterface(TaskIndex); - #endif - if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && isI2CMultiplexerEnabled(i2cBus)) { - #if FEATURE_I2C_MULTIPLE - stream_next_json_object_value(F("I2C_Interface"), static_cast(i2cBus + 1)); - #endif - int8_t channel = Settings.I2C_Multiplexer_Channel[TaskIndex]; - if (bitRead(Settings.I2C_Flags[TaskIndex], I2C_FLAGS_MUX_MULTICHANNEL)) { - addHtml(F("\"I2CBus\" : [")); - uint8_t b = 0; - for (uint8_t c = 0; c < I2CMultiplexerMaxChannels(i2cBus); ++c) { - if (bitRead(channel, c)) { - if (b > 0) { stream_comma_newline(); } - b++; - addHtml(F("\"Multiplexer channel ")); - addHtmlInt(c); - addHtml('"'); +#if FEATURE_I2CMULTIPLEXER + uint8_t i2cBus = 0; +# if FEATURE_I2C_MULTIPLE + i2cBus = Settings.getI2CInterface(TaskIndex); +# endif + + if ((Device[DeviceIndex].Type == DEVICE_TYPE_I2C) && isI2CMultiplexerEnabled(i2cBus)) { +# if FEATURE_I2C_MULTIPLE + taskWriter->write({ F("I2C_Interface"), static_cast(i2cBus + 1) }); +# endif + int8_t channel = Settings.I2C_Multiplexer_Channel[TaskIndex]; + + if (bitRead(Settings.I2C_Flags[TaskIndex], I2C_FLAGS_MUX_MULTICHANNEL)) { + + KeyValueStruct kv(F("I2CBus")); + + addHtml(F("\"I2CBus\" : [")); + + for (uint8_t c = 0; c < I2CMultiplexerMaxChannels(i2cBus); ++c) { + if (bitRead(channel, c)) { + kv.appendValue(concat(F("Multiplexer channel "), c)); + } + } + taskWriter->write(kv); + } else { + if (channel == -1) { + taskWriter->write({ F("I2Cbus"), F("Standard I2C bus") }); + } else { + taskWriter->write({ F("I2Cbus"), concat(F("Multiplexer channel "), channel) }); + } + } + } +#endif // if FEATURE_I2CMULTIPLEXER + } + + taskWriter->write({ F("TaskEnabled"), + Settings.TaskDeviceEnabled[TaskIndex] }); + taskWriter->write({ F("TaskNumber"), TaskIndex + 1 }); + + if (showSpecificTask) { +#if FEATURE_TASKVALUE_UNIT_OF_MEASURE + taskWriter->write({ F("ShowUoM"), Settings.ShowUnitOfMeasureOnDevicesPage() }); +#endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE + taskWriter->write({ F("TTL"), ttl_json * 1000 }); } } - addHtml(F("],\n")); - } else { - if (channel == -1){ - stream_next_json_object_value(F("I2Cbus"), F("Standard I2C bus")); - } else { - String i2cChannel = concat(F("Multiplexer channel "), channel); - stream_next_json_object_value(F("I2Cbus"), i2cChannel); - } } } - #endif // if FEATURE_I2CMULTIPLEXER } - stream_next_json_object_value(F("TaskEnabled"), - // jsonBool(Settings.TaskDeviceEnabled[TaskIndex].enabled)); - jsonBool(Settings.TaskDeviceEnabled[TaskIndex])); - - stream_last_json_object_value(F("TaskNumber"), TaskIndex + 1); - - if (TaskIndex != lastActiveTaskIndex) { - addHtml(','); - } - addHtml('\n'); } - } - if (!showSpecificTask) { - addHtml(F("],\n")); - #if FEATURE_TASKVALUE_UNIT_OF_MEASURE - stream_next_json_object_value(F("ShowUoM"), jsonBool(Settings.ShowUnitOfMeasureOnDevicesPage())); - #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE - stream_last_json_object_value(F("TTL"), lowest_ttl_json * 1000); + if (!showSpecificTask) { +#if FEATURE_TASKVALUE_UNIT_OF_MEASURE + mainLevelWriter.write({ F("ShowUoM"), Settings.ShowUnitOfMeasureOnDevicesPage() }); +#endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE + mainLevelWriter.write({ F("TTL"), lowest_ttl_json * 1000 }); + } } TXBuffer.endStream(); STOP_TIMER(HANDLE_SERVING_WEBPAGE_JSON); } -void handle_json_stream_task_value_data(uint16_t valueNumber, +void handle_json_stream_task_value_data(KeyValueWriter*parent, + uint16_t valueNumber, const String & valueName, uint8_t nrDecimals, const String & value, const String & presentation, - const String & uom, - bool appendComma) { - addHtml('{'); - stream_next_json_object_value(F("ValueNumber"), valueNumber); - stream_next_json_object_value(F("Name"), valueName); - stream_next_json_object_value(F("NrDecimals"), nrDecimals); - #if FEATURE_STRING_VARIABLES - if (!presentation.isEmpty()) { - stream_next_json_object_value(F("Presentation"), presentation); - } - #endif // if FEATURE_STRING_VARIABLES - if (!uom.isEmpty()) { - stream_next_json_object_value(F("UoM"), uom); - } - stream_last_json_object_value(F("Value"), value); + const String & uom) +{ + auto writer = parent->createChild(); - if (appendComma) { - stream_comma_newline(); + if (writer) { + writer->write({ F("ValueNumber"), valueNumber }); + writer->write({ F("Name"), valueName }); + writer->write({ F("NrDecimals"), nrDecimals }); +#if FEATURE_STRING_VARIABLES + + if (!presentation.isEmpty()) { + writer->write({ F("Presentation"), presentation }); + } +#endif // if FEATURE_STRING_VARIABLES + + if (!uom.isEmpty()) { + writer->write({ F("UoM"), uom }); + } + writer->write({ F("Value"), value }); } } +#endif + // ******************************************************************************** // JSON formatted timing statistics // ******************************************************************************** #ifdef WEBSERVER_NEW_UI + void handle_timingstats_json() { TXBuffer.startJsonStream(); json_init(); @@ -683,7 +733,8 @@ void handle_timingstats_json() { #ifdef WEBSERVER_NEW_UI -#if FEATURE_ESPEASY_P2P +# if FEATURE_ESPEASY_P2P + void handle_nodes_list_json() { if (!isLoggedIn()) { return; } TXBuffer.startJsonStream(); @@ -714,7 +765,8 @@ void handle_nodes_list_json() { json_close(true); TXBuffer.endStream(); } -#endif + +# endif // if FEATURE_ESPEASY_P2P void handle_buildinfo() { if (!isLoggedIn()) { return; } @@ -726,6 +778,7 @@ void handle_buildinfo() { for (deviceIndex_t x; x <= getDeviceCount(); x++) { const pluginID_t pluginID = getPluginID_from_DeviceIndex(x); + if (validPluginID(pluginID)) { json_open(); json_number(F("id"), String(pluginID)); @@ -748,7 +801,7 @@ void handle_buildinfo() { } json_close(true); } -#if FEATURE_NOTIFIER +# if FEATURE_NOTIFIER { json_open(true, F("notifications")); @@ -762,13 +815,13 @@ void handle_buildinfo() { } json_close(true); } -#endif +# endif // if FEATURE_NOTIFIER json_prop(LabelType::BUILD_DESC); json_prop(LabelType::GIT_BUILD); json_prop(LabelType::SYSTEM_LIBRARIES); -#ifdef ESP32 +# ifdef ESP32 json_prop(LabelType::ESP_IDF_SDK_VERSION); -#endif +# endif json_prop(LabelType::PLUGIN_COUNT); json_prop(LabelType::PLUGIN_DESCRIPTION); json_close(); @@ -777,11 +830,12 @@ void handle_buildinfo() { #endif // WEBSERVER_NEW_UI +#if FEATURE_CHART_JS || defined(WEBSERVER_NEW_UI) /*********************************************************************************************\ Streaming versions directly to TXBuffer \*********************************************************************************************/ -void stream_to_json_object_value(const __FlashStringHelper * object, const String& value) { +void stream_to_json_object_value(const __FlashStringHelper *object, const String& value) { stream_to_json_object_value(String(object), value); } @@ -791,9 +845,7 @@ void stream_to_json_object_value(const String& object, const String& value) { addHtml(to_json_value(value)); } -void stream_to_json_object_value(const __FlashStringHelper * object, int value) { - stream_to_json_object_value(String(object), value); -} +void stream_to_json_object_value(const __FlashStringHelper *object, int value) { stream_to_json_object_value(String(object), value); } void stream_to_json_object_value(const String& object, int value) { addHtml(wrap_String(object, '"', '"')); @@ -801,18 +853,19 @@ void stream_to_json_object_value(const String& object, int value) { addHtmlInt(value); } -String jsonBool(bool value) { - return boolToString(value); -} +#endif // if FEATURE_CHART_JS || defined(WEBSERVER_NEW_UI) +String jsonBool(bool value) { return boolToString(value); } + +#ifdef WEBSERVER_NEW_UI // Add JSON formatted data directly to the TXbuffer, including a trailing comma. -void stream_next_json_object_value(const __FlashStringHelper * object, const String& value) { +void stream_next_json_object_value(const __FlashStringHelper *object, const String& value) { stream_to_json_object_value(object, value); stream_comma_newline(); } -void stream_next_json_object_value(const __FlashStringHelper * object, String&& value) { +void stream_next_json_object_value(const __FlashStringHelper *object, String&& value) { stream_to_json_object_value(object, value); stream_comma_newline(); } @@ -822,7 +875,7 @@ void stream_next_json_object_value(const String& object, const String& value) { stream_comma_newline(); } -void stream_next_json_object_value(const __FlashStringHelper * object, int value) { +void stream_next_json_object_value(const __FlashStringHelper *object, int value) { stream_to_json_object_value(object, value); stream_comma_newline(); } @@ -832,18 +885,15 @@ void stream_next_json_object_value(const String& object, int value) { stream_comma_newline(); } -void stream_newline_close_brace() { - addHtml('\n', '}'); -} - +void stream_newline_close_brace() { addHtml('\n', '}'); } // Add JSON formatted data directly to the TXbuffer, including a closing '}' -void stream_last_json_object_value(const __FlashStringHelper * object, const String& value) { +void stream_last_json_object_value(const __FlashStringHelper *object, const String& value) { stream_to_json_object_value(object, value); stream_newline_close_brace(); } -void stream_last_json_object_value(const __FlashStringHelper * object, String&& value) { +void stream_last_json_object_value(const __FlashStringHelper *object, String&& value) { stream_to_json_object_value(object, value); stream_newline_close_brace(); } @@ -853,17 +903,18 @@ void stream_last_json_object_value(const String& object, const String& value) { stream_newline_close_brace(); } -void stream_last_json_object_value(const __FlashStringHelper * object, int value) { +void stream_last_json_object_value(const __FlashStringHelper *object, int value) { stream_to_json_object_value(object, value); stream_newline_close_brace(); } -void stream_json_object_values(const LabelType::Enum labels[]) -{ - size_t i = 0; - LabelType::Enum cur = static_cast(pgm_read_byte(labels + i)); +/* + void stream_json_object_values(const LabelType::Enum labels[]) + { + size_t i = 0; + LabelType::Enum cur = static_cast(pgm_read_byte(labels + i)); - while (true) { + while (true) { const LabelType::Enum next = static_cast(pgm_read_byte(labels + i + 1)); const bool nextIsLast = next == LabelType::MAX_LABEL; @@ -873,15 +924,13 @@ void stream_json_object_values(const LabelType::Enum labels[]) } else { stream_next_json_object_value(cur); } - ++i; + ++i; cur = next; - } -} + } + } + */ +void stream_next_json_object_value(LabelType::Enum label) { stream_next_json_object_value(getLabel(label), getValue(label)); } -void stream_next_json_object_value(LabelType::Enum label) { - stream_next_json_object_value(getLabel(label), getValue(label)); -} +void stream_last_json_object_value(LabelType::Enum label) { stream_last_json_object_value(getLabel(label), getValue(label)); } -void stream_last_json_object_value(LabelType::Enum label) { - stream_last_json_object_value(getLabel(label), getValue(label)); -} \ No newline at end of file +#endif // ifdef WEBSERVER_NEW_UI diff --git a/src/src/WebServer/JSON.h b/src/src/WebServer/JSON.h index 045b1f93a..8ecbef398 100644 --- a/src/src/WebServer/JSON.h +++ b/src/src/WebServer/JSON.h @@ -4,6 +4,7 @@ #include "../WebServer/common.h" +#include "../Helpers/KeyValueWriter_JSON.h" // ******************************************************************************** // Web Interface get CSV value from task @@ -12,19 +13,20 @@ void handle_csvval(); #endif +#ifdef WEBSERVER_JSON // ******************************************************************************** // Web Interface JSON page (no password!) // ******************************************************************************** void handle_json(); -void handle_json_stream_task_value_data(uint16_t valueNumber, +void handle_json_stream_task_value_data(KeyValueWriter* parent, + uint16_t valueNumber, const String & valueName, uint8_t nrDecimals, const String & value, const String & presentation, - const String & uom, - bool appendComma); - + const String & uom); +#endif // ******************************************************************************** // JSON formatted timing statistics // ******************************************************************************** @@ -46,14 +48,15 @@ void handle_buildinfo(); /*********************************************************************************************\ Streaming versions directly to TXBuffer \*********************************************************************************************/ +#if FEATURE_CHART_JS || defined(WEBSERVER_NEW_UI) void stream_to_json_object_value(const __FlashStringHelper * object, const String& value); void stream_to_json_object_value(const String& object, const String& value); void stream_to_json_object_value(const __FlashStringHelper * object, int value); void stream_to_json_object_value(const String& object, int value); - +#endif String jsonBool(bool value); - +#ifdef WEBSERVER_NEW_UI // Add JSON formatted data directly to the TXbuffer, including a trailing comma. void stream_next_json_object_value(const __FlashStringHelper * object, const String& value); void stream_next_json_object_value(const __FlashStringHelper * object, String&& value); @@ -67,12 +70,12 @@ void stream_last_json_object_value(const __FlashStringHelper * object, String&& void stream_last_json_object_value(const String& object, const String& value); void stream_last_json_object_value(const __FlashStringHelper * object, int value); -void stream_json_object_values(const LabelType::Enum labels[]); +//void stream_json_object_values(const LabelType::Enum labels[]); void stream_next_json_object_value(LabelType::Enum label); void stream_last_json_object_value(LabelType::Enum label); - +#endif diff --git a/src/src/WebServer/KeyValueWriter_WebForm.cpp b/src/src/WebServer/KeyValueWriter_WebForm.cpp new file mode 100644 index 000000000..b7c2820ae --- /dev/null +++ b/src/src/WebServer/KeyValueWriter_WebForm.cpp @@ -0,0 +1,198 @@ +#include "../WebServer/KeyValueWriter_WebForm.h" + +#include "../Helpers/StringConverter.h" + +#include "../WebServer/HTML_wrappers.h" +#include "../WebServer/Markup.h" + + +KeyValueWriter_WebForm::KeyValueWriter_WebForm(bool emptyHeader, PrintToString *toStr) + : KeyValueWriter(emptyHeader, toStr) +{} + +KeyValueWriter_WebForm::KeyValueWriter_WebForm(KeyValueWriter_WebForm*parent, PrintToString *toStr) + : KeyValueWriter(parent, toStr) +{} + +KeyValueWriter_WebForm::KeyValueWriter_WebForm(bool emptyHeader, KeyValueWriter_WebForm*parent, PrintToString *toStr) + : KeyValueWriter(emptyHeader, parent, toStr) +{} + +KeyValueWriter_WebForm::KeyValueWriter_WebForm(const String& header, PrintToString *toStr) + : KeyValueWriter(header, nullptr, toStr) +{} + +KeyValueWriter_WebForm::KeyValueWriter_WebForm(const __FlashStringHelper *header, PrintToString *toStr) + : KeyValueWriter(String(header), nullptr, toStr) +{} + + +KeyValueWriter_WebForm::KeyValueWriter_WebForm(const String& header, KeyValueWriter_WebForm*parent, PrintToString *toStr) + : KeyValueWriter(header, parent, toStr) +{} + +KeyValueWriter_WebForm::KeyValueWriter_WebForm(const __FlashStringHelper *header, KeyValueWriter_WebForm*parent, PrintToString *toStr) + : KeyValueWriter(String(header), parent, toStr) +{} + +KeyValueWriter_WebForm::~KeyValueWriter_WebForm() +{ + if (!summaryValueOnly() && !_isEmpty && _hasHeader && _header.isEmpty()) { + // TODO TD-er: Should we add a separator line here? + } + +} + +void KeyValueWriter_WebForm::write() +{ + if (_isEmpty) { + if (_parent != nullptr) { _parent->write(); } + + if (!summaryValueOnly() && _hasHeader && !_header.isEmpty()) { + if (plainText()) { + getPrint().print(concat(_header, F(": "))); + } else { + addFormSubHeader(_header); + } + } + _isEmpty = false; + } +} + +void KeyValueWriter_WebForm::write(const KeyValueStruct& kv) +{ + write(); + const bool plain_text = plainText(); + const bool summary_value_only = summaryValueOnly(); + + const size_t nrValues = kv._values.size(); + const bool format_pre = + !plain_text && + !summary_value_only && + (nrValues > 1 || kv._format == KeyValueStruct::Format::PreFormatted); + + const bool format_note = + !plain_text && + !summary_value_only && + (nrValues == 1 && kv._format == KeyValueStruct::Format::Note); + + if (!summary_value_only) { + if (format_note) { addRowLabel_tr_id(EMPTY_STRING, EMPTY_STRING); } + else { + // Use the __id value, not the getID function, so we only output the set value, not a generated value based on the key. + addRowLabel(kv._key.toString(), kv.__id.toString()); + } + } + + if (format_note) { getPrint().print(F("
Note: ")); } + + if (format_pre) { getPrint().print(F("
")); }
+
+  for (size_t i = 0; i < nrValues; ++i) {
+    if (i != 0) {
+      if (plain_text) {
+        getPrint().write('\n');
+      }
+      else {
+        getPrint().print(F("
")); + } + } + + if (kv._values[i].isSet()) { + + String str(kv._values[i].toString()); + + if (!plain_text) { + + const auto vtype = kv._values[i].getValueType(); + + if (vtype == ValueStruct::ValueType::Bool) { + str = concat(F("❌") + : F("on'>✔")); + str += F(""); + } else { + if (str.indexOf('\n') != -1) + { + str.replace(F("\n"), F("
")); + } + } + } + getPrint().print(str); + } + } + +#if FEATURE_TASKVALUE_UNIT_OF_MEASURE + // May need to include the unit before ending
+ // or else it will be shown on the next line + if (!plain_text && (nrValues == 1) && !summary_value_only) { + addUnit(kv.getUnit()); + } +#endif + + if (format_pre) { getPrint().print(F("")); } + + if (format_note) { getPrint().print(F("
")); } + + if (summary_value_only) { + // Must add a newline + if (plain_text) { + getPrint().write('\n'); + } + else { + getPrint().print(F("
")); + } + } +} + +UP_KeyValueWriter KeyValueWriter_WebForm::createChild() +{ + UP_KeyValueWriter_WebForm child(new (std::nothrow) KeyValueWriter_WebForm(this)); + + return std::move(child); + + // return std::make_unique(this); +} + +UP_KeyValueWriter KeyValueWriter_WebForm::createChild(const String& header) +{ + UP_KeyValueWriter_WebForm child(new (std::nothrow) KeyValueWriter_WebForm(header, this)); + + return std::move(child); + + // return std::make_unique(header, this); +} + +UP_KeyValueWriter KeyValueWriter_WebForm::createChildArray(const String& header) +{ + auto child = createChild(header); + + if (child) { + child->setIsArray(); + } + + // return std::move(child); + return child; +} + +UP_KeyValueWriter KeyValueWriter_WebForm::createNew() +{ + UP_KeyValueWriter_WebForm child(new (std::nothrow) KeyValueWriter_WebForm()); + + return std::move(child); + + // return std::make_unique(); +} + +UP_KeyValueWriter KeyValueWriter_WebForm::createNew(const String& header) +{ + UP_KeyValueWriter_WebForm child(new (std::nothrow) KeyValueWriter_WebForm(header)); + + return std::move(child); + + // return std::make_unique(header); +} + +bool KeyValueWriter_WebForm::allowHTML() const { + return true; +} \ No newline at end of file diff --git a/src/src/WebServer/KeyValueWriter_WebForm.h b/src/src/WebServer/KeyValueWriter_WebForm.h new file mode 100644 index 000000000..90065476a --- /dev/null +++ b/src/src/WebServer/KeyValueWriter_WebForm.h @@ -0,0 +1,56 @@ +#pragma once + +#include "../Helpers/KeyValueWriter.h" + + +class KeyValueWriter_WebForm : public KeyValueWriter +{ +public: + + using KeyValueWriter::writeLabels; + + KeyValueWriter_WebForm(bool emptyHeader = false, + PrintToString *toStr = nullptr); + + KeyValueWriter_WebForm(KeyValueWriter_WebForm*parent, + PrintToString *toStr = nullptr); + + KeyValueWriter_WebForm(bool emptyHeader, + KeyValueWriter_WebForm*parent, + PrintToString *toStr = nullptr); + + KeyValueWriter_WebForm(const String & header, + PrintToString *toStr = nullptr); + KeyValueWriter_WebForm(const __FlashStringHelper *header, + PrintToString *toStr = nullptr); + + KeyValueWriter_WebForm(const String & header, + KeyValueWriter_WebForm*parent, + PrintToString *toStr = nullptr); + KeyValueWriter_WebForm(const __FlashStringHelper *header, + KeyValueWriter_WebForm *parent, + PrintToString *toStr = nullptr); + + + virtual ~KeyValueWriter_WebForm(); + + virtual void write(); + + virtual void write(const KeyValueStruct& kv); + + // Create writer of the same derived type, with this set as parent + virtual UP_KeyValueWriter createChild(); + virtual UP_KeyValueWriter createChild(const String& header); + virtual UP_KeyValueWriter createChildArray(const String& header); + + // Create new writer of the same derived type, without parent + virtual UP_KeyValueWriter createNew(); + virtual UP_KeyValueWriter createNew(const String& header); + + virtual bool allowHTML() const override; + + +}; // class KeyValueWriter_WebForm + + +DEF_UP(KeyValueWriter_WebForm); diff --git a/src/src/WebServer/LoadFromFS.cpp b/src/src/WebServer/LoadFromFS.cpp index 0d815e99c..cf67dd8a6 100644 --- a/src/src/WebServer/LoadFromFS.cpp +++ b/src/src/WebServer/LoadFromFS.cpp @@ -6,7 +6,7 @@ #include "../Globals/RamTracker.h" #include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/Network.h" +#include "../Helpers/NetworkStatusLED.h" #include "../Helpers/Numerical.h" #include "../WebServer/CustomPage.h" @@ -278,7 +278,7 @@ bool loadFromFS(String path) { // prevent reloading stuff on every click if (static_file) { - sendHeader(F("Cache-Control"), F("public, max-age=31536000, s-maxage=31536000, immutable")); + sendHeader(F("Cache-Control"), F("public,max-age=31536000,s-maxage=31536000,immutable")); // sendHeader(F("Cache-Control"), F("max-age=86400")); // sendHeader(F("Expires"), F("-1")); @@ -298,7 +298,7 @@ bool loadFromFS(String path) { } if (!web_server.client().connected()) { - #ifndef BUILD_MINIMAL_OTA + #ifndef LIMIT_BUILD_SIZE addLog(LOG_LEVEL_INFO, strformat( F("loadFromFS: Client %s not connected"), web_server.client().remoteIP().toString().c_str() diff --git a/src/src/WebServer/Log.cpp b/src/src/WebServer/Log.cpp index ab43fe2a2..75d7fe265 100644 --- a/src/src/WebServer/Log.cpp +++ b/src/src/WebServer/Log.cpp @@ -7,7 +7,8 @@ #include "../WebServer/Markup.h" #include "../WebServer/Markup_Buttons.h" -#include "../DataStructs/LogStruct.h" +#include "../DataStructs/LogBuffer.h" +#include "../DataStructs/TimingStats.h" #include "../Globals/Logging.h" #include "../Globals/Settings.h" @@ -27,8 +28,11 @@ void handle_log() { #ifdef WEBSERVER_LOG addHtml(F("
")); } void html_TR_TD() { - html_TR(); - html_TD(); + addHtml(F("\n
")); } void html_BR() { @@ -69,7 +60,7 @@ void html_BR() { } void html_TR() { - addHtml(F("
Log")); + #ifdef WEBSERVER_GITHUB_COPY addCopyButton(F("copyText"), EMPTY_STRING, F("Copy log to clipboard")); - addHtml(F("
Logging:
")); + #endif + addHtml(F( + "
Logging:
")); addHtml(F("Autoscroll: ")); addCheckBox(F("autoscroll"), true); addHtml(F("
")); @@ -48,74 +52,96 @@ void handle_log() { void handle_log_JSON() { if (!isLoggedIn()) { return; } #ifdef WEBSERVER_LOG + START_TIMER; TXBuffer.startJsonStream(); - String webrequest = webArg(F("view")); - addHtml(F("{\"Log\": {")); + { + KeyValueWriter_JSON top(true); + { + String webrequest = webArg(F("view")); - if (equals(webrequest, F("legend"))) { - addHtml(F("\"Legend\": [")); + auto mainWriter = top.createChild(F("Log")); - for (uint8_t i = 0; i < LOG_LEVEL_NRELEMENTS; ++i) { - if (i != 0) { - addHtml(','); + if (mainWriter) { + + if (equals(webrequest, F("legend"))) { + + auto legendWriter = mainWriter->createChildArray(F("Legend")); + + if (legendWriter) { + for (uint8_t i = LOG_LEVEL_ERROR; i < LOG_LEVEL_NRELEMENTS(); ++i) { + auto loglevelWriter = legendWriter->createChild(); + + if (loglevelWriter) { + int loglevel; + loglevelWriter->write({ F("label"), getLogLevelDisplayStringFromIndex(i, loglevel) }); + loglevelWriter->write({ F("loglevel"), loglevel }); + } + } + } + } + uint32_t firstTimeStamp = 0; + uint32_t lastTimeStamp = 0; + int nrEntries = 0; + + bool logLinesAvailable = true; + { + auto entriesWriter = mainWriter->createChildArray(F("Entries")); + + if (entriesWriter) { + uint32_t startTime = millis(); + + while (logLinesAvailable && timePassedSince(startTime) < 200) { + String message; + uint8_t loglevel; + + if (Logging.getNext(LOG_TO_WEBLOG, lastTimeStamp, message, loglevel)) { + auto logWriter = entriesWriter->createChild(); + + if (logWriter) { + logWriter->write({ F("timestamp"), format_msec_duration(lastTimeStamp) }); + logWriter->write({ F("text"), std::move(message) }); + logWriter->write({ F("level"), loglevel }); + + if (nrEntries == 0) { + firstTimeStamp = lastTimeStamp; + } + ++nrEntries; + } + + // Do we need to do something here and maybe limit number of lines at once? + } else { logLinesAvailable = false; } + } + } + } + const uint32_t nrEntriesLeft = Logging.getNrMessages(LOG_TO_WEBLOG); + int32_t logTimeSpan = timeDiff(firstTimeStamp, lastTimeStamp); + int32_t refreshSuggestion = (nrEntriesLeft > 0) ? 200 : 1000; + int32_t newOptimum = 1000; + + + if ((nrEntries > 2) && (logTimeSpan > 1)) { + // May need to lower the TTL for refresh when time needed + // to fill half the log is lower than current TTL + newOptimum = logTimeSpan * (LOG_STRUCT_MESSAGE_LINES / 2); + newOptimum = newOptimum / (nrEntries - 1); + } + + if (newOptimum < refreshSuggestion) { refreshSuggestion = newOptimum; } + + if (refreshSuggestion < 100) { + // Reload times no lower than 100 msec. + refreshSuggestion = 100; + } + mainWriter->write({ F("TTL"), refreshSuggestion }); + mainWriter->write({ F("timeHalfBuffer"), newOptimum }); + mainWriter->write({ F("nrEntries"), nrEntries }); + mainWriter->write({ F("SettingsWebLogLevel"), Settings.WebLogLevel }); + mainWriter->write({ F("logTimeSpan"), logTimeSpan }); } - addHtml('{'); - int loglevel; - stream_next_json_object_value(F("label"), getLogLevelDisplayStringFromIndex(i, loglevel)); - stream_last_json_object_value(F("loglevel"), loglevel); } - addHtml(F("],\n")); } - addHtml(F("\"Entries\": [")); - bool logLinesAvailable = true; - int nrEntries = 0; - unsigned long firstTimeStamp = 0; - unsigned long lastTimeStamp = 0; - - while (logLinesAvailable) { - String message; - uint8_t loglevel; - if (Logging.getNext(logLinesAvailable, lastTimeStamp, message, loglevel)) { - addHtml('{'); - stream_next_json_object_value(F("timestamp"), lastTimeStamp); - stream_next_json_object_value(F("text"), std::move(message)); - stream_last_json_object_value(F("level"), loglevel); - if (logLinesAvailable) { - addHtml(',', '\n'); - } - if (nrEntries == 0) { - firstTimeStamp = lastTimeStamp; - } - ++nrEntries; - } - - // Do we need to do something here and maybe limit number of lines at once? - } - addHtml(F("],\n")); - long logTimeSpan = timeDiff(firstTimeStamp, lastTimeStamp); - long refreshSuggestion = 1000; - long newOptimum = 1000; - - if ((nrEntries > 2) && (logTimeSpan > 1)) { - // May need to lower the TTL for refresh when time needed - // to fill half the log is lower than current TTL - newOptimum = logTimeSpan * (LOG_STRUCT_MESSAGE_LINES / 2); - newOptimum = newOptimum / (nrEntries - 1); - } - - if (newOptimum < refreshSuggestion) { refreshSuggestion = newOptimum; } - - if (refreshSuggestion < 100) { - // Reload times no lower than 100 msec. - refreshSuggestion = 100; - } - stream_next_json_object_value(F("TTL"), refreshSuggestion); - stream_next_json_object_value(F("timeHalfBuffer"), newOptimum); - stream_next_json_object_value(F("nrEntries"), nrEntries); - stream_next_json_object_value(F("SettingsWebLogLevel"), Settings.WebLogLevel); - stream_last_json_object_value(F("logTimeSpan"), logTimeSpan); - addHtml(F("}\n")); TXBuffer.endStream(); + STOP_TIMER(HANDLE_SERVING_WEBPAGE_JSON); updateLogLevelCache(); #else // ifdef WEBSERVER_LOG diff --git a/src/src/WebServer/Markup.cpp b/src/src/WebServer/Markup.cpp index 579d2da4c..7153a1add 100644 --- a/src/src/WebServer/Markup.cpp +++ b/src/src/WebServer/Markup.cpp @@ -2,6 +2,7 @@ #include "../WebServer/Markup.h" #include "../WebServer/HTML_wrappers.h" +#include "../WebServer/KeyValueWriter_WebForm.h" #include "../WebServer/Markup_Forms.h" #include "../CustomBuild/ESPEasyLimits.h" @@ -9,6 +10,7 @@ #include "../Globals/Settings.h" #include "../Helpers/Convert.h" +#include "../Helpers/ESPEasy_UnitOfMeasure.h" #include "../Helpers/Hardware_GPIO.h" #include "../Helpers/StringConverter_Numerical.h" #include "../Helpers/StringConverter.h" @@ -132,11 +134,11 @@ void addPinSelector_Item(PinSelectPurpose purpose, const String& gpio_label, int return; } break; +#if FEATURE_ETHERNET case PinSelectPurpose::Ethernet: - #if FEATURE_ETHERNET includeEthernet = false; - #endif // if FEATURE_ETHERNET break; +#endif case PinSelectPurpose::Generic: if (!input && !output) { @@ -351,131 +353,11 @@ void addUnit(char unit) } #if FEATURE_TASKVALUE_UNIT_OF_MEASURE -const char unit_of_measure_list[] PROGMEM = // *** DO NOT CHANGE ORDER, SAVED IN TASK SETTINGS! *** - "|" // 0 = Empty/none - "°C|°F|K|" // 1..3 - "%|" // 4 - "Pa|hPa|bar|mbar|inHg|psi|" // 5..10 - "W|kW|" // 11..12 - "V|" // 13 - "Wh|kWh|" // 14..15 - "A|VA|" // 16..17 - "mm|cm|m|km|" // 18..21 - "L|mL|m³|ft³|" // 22..25 - "m³/h|ft³/h|" // 26..27 - "lx|" // 28 - "UV index|" // 29 - "µg/m³|mg/m³|p/m³|ppm|ppb|" // 30..34 - "°|" // 35 - "€|$|¢|" // 36..38 - "μs|ms|s|min|h|d|w|m|y|" // 39..47 - "in|ft|yd|mi|" // 48..51 - "Hz|GHz|" // 52..53 - "gal|fl. oz|" // 54..55 - "m²|" // 56 - "g|kg|mg|µg|" // 57..60 - "oz|lb|" // 61..62 - "µS/cm|" // 63 - "W/m²|" // 64 - "mm/h|" // 65 - "mm/s|in/s|m/s|in/h|km/h|mph|" // 66..71 - "db|dBm|" // 72..73 - "bit|kbit|Mbit|Gbit|B|kB|MB|GB|TB|PB|EB|ZB|YB|KiB|MiB|GiB|TiB|PiB|EiB|ZiB|YiB|" // 74..94 - "bit/s|kbit/s|Mbit/s|Gbit/s|B/s|kB/s|MB/s|GB/s|KiB/s|MiB/s|GiB/s|" // 95..105 - "ft/s|kn|" // 106..107 - "mW|MW|GW|TW|" // 108..111 - "BTU/(h⋅ft²)|" // 112 - "pH|" // 113 - "cbar|mmHg|kPa|" // 114..116 - "mA|µA|mV|µV|kV|" // 117..121 - "cm²|km²|mm²|in²|ft²|yd²|mi²|ac|ha|" // 122..130 - "kHz|MHz|" // 131..132 - "mWh|MWh|GWh|TWh|cal|kcal|Mcal|Gcal|J|kJ|MJ|GJ|" // 133..144 - "var|kvar|varh|kvarh|" // 145..148 - "st|" // 149 - "mg/dL|mmol/L|" // 150..151 - "μSv|μSv/h|" // 152..153 - "m³/s|ft³/min|L/h|L/min|L/s|gal/min|mL/s|" // 154..160 - "g/m³|kWh/100km|Wh/km|mi/kWh|km/kWh|" // 161..165 - "in/d|mm/d" // 166 .. 167 - ; // *** DO NOT CHANGE ORDER, SAVED IN TASK SETTINGS! *** - - -const char unit_of_measure_labels[] PROGMEM = // Not stored, when UoM index >= 1024 it's a label-index with 1024 subtracted - "Apparent power|Air quality/CO/CO2|Area|(Atmospheric) Pressure|" // A 1024..1027 - "Blood glucose concentr.|" // B 1028 - "Data rate|Data size|Distance|Duration|" // D 1029..1032 - "Energy distance|Energy(-storage)|" // E 1033..1034 - "Frequency|" // F 1035 - "Gas|" // G 1036 - "Percent Hum./Batt./Moist.|" // H 1037 - "Illuminance|Irradiance|" // I 1038..1039 - "Monetary|" // M 1040 - "Nitrogen (di-/mon-)oxide|" // N 1041 - "Voc/Ozone|" // O 1042 - "Ph|PM/CO/CO2/NO(x)/Voc/Ozone|Power|" // P 1043..1045 - "Radiation|Reactive energy/power|" // R 1046..1047 - "Signal strength|Sound pressure|Speed|" // S 1048..1050 - "Temperature|" // T 1051 - "Voltage/Current|Volume/Water cons.|Volume flow rate|" // V 1052..1054 - "Weight|Wind direction|" // W 1055..1056 - "Various units|" // Additional 1057 - ; - -const uint16_t unit_of_measure_map[] PROGMEM = { - 1051, 1, 2, 3, // Temperature - 1037, 4, // Percent Battery, Humidity, Moisture - 1027, 8, 6, 116, 7, 115, 10, 5, 114, 9, // (Atmospheric) Pressure - 1052, 13, 119, 120, 121, 16, 117, 118, // Voltage/Current - 1045, 11, 12, 108, 109, 110, 111, // Power - 1024, 17, // Apparent power - 1047, 145, 146, 147, 148, // Reactive power/energy - 1044, 30, 31, 32, 33, 34, // Particle matter - 1031, 18, 19, 20, 21, 48, 49, 50, 51, // Distance - 1055, 57, 58, 59, 60, 61, 62, 149, // Weight - 1053, 22, 23, 24, 25, 54, 55, // Volume/Water - 1054, 26, 27, 153, 154, 155, 156, 157, 158, 159, 160, // Volume flow rate - 1032, 39, 40, 41, 42, 43, 44, 45, 46, 47, // Duration - 1034, 14, 15, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, // Energy(-storage) - 1033, 162, 163, 164, 165, // Energy distance - 1050, 66, 67, 68, 69, 70, 71, 65, 106, 107, // Speed - 1056, 35, // (Wind) direction - 1038, 28, // Illuminance - 1039, 64, 112, // Irradiance - 1046, 152, 153, // Radiation - 1057, 29, 63, 161, 166, 167, // Various units - 1035, 52, 53, 131, 132, // Frequency - 1043, 113, // Potential hydrogen - 1026, 56, 122, 123, 124, 125, 126, 127, 128, 129, 130, // Area - 1029, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, // Data rate - 1030, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, // Data size - 1049, 72, 73, // Sound pressure - 1028, 150, 151, // Blood glucose - 1040, 36, 37, 38, // Monetary -}; - -String toUnitOfMeasureName(const uint32_t unitOfMeasureIndex, - const String & defUoM) { - char tmp[26]{}; - String result; - - if (unitOfMeasureIndex < 1024) { - result = GetTextIndexed(tmp, sizeof(tmp), unitOfMeasureIndex, unit_of_measure_list); - } else { - result = GetTextIndexed(tmp, sizeof(tmp), unitOfMeasureIndex - 1024, unit_of_measure_labels); - } - - return result.isEmpty() ? defUoM : result; -} - -int getUnitOfMeasureIndex(const String& uomName) { - return GetCommandCode(uomName.c_str(), unit_of_measure_list); -} - -void addUnitOfMeasureSelector(const String& id, - const uint8_t unitOfMeasure) { - constexpr uint16_t asize = NR_ELEMENTS(unit_of_measure_map); - bool firstGrp = true; +void addUnitOfMeasureSelector(const String& id, + const uint8_t unitOfMeasure, + const uint64_t groupMap) { + bool firstGrp = true; + bool includeGrp = false; do_addSelector_Head(id, F("xwide"), EMPTY_STRING, false #if FEATURE_TOOLTIPS @@ -487,19 +369,24 @@ void addUnitOfMeasureSelector(const String& id, 0, unitOfMeasure == 0); - for (uint16_t idx = 0; idx < asize; ++idx) { + for (uint16_t idx = 0; idx < unit_of_measure_map_size; ++idx) { const uint16_t uomIdx = pgm_read_word_near(&unit_of_measure_map[idx]); if (uomIdx < 1024) { - addSelector_Item( - toUnitOfMeasureName(uomIdx), - uomIdx, - unitOfMeasure == uomIdx); + if (includeGrp) { + addSelector_Item( + toUnitOfMeasureName(uomIdx), + uomIdx, + unitOfMeasure == uomIdx); + } } else { - if (!firstGrp) { - addSelector_OptGroupFoot(); + includeGrp = bitRead(groupMap, uomIdx - 1024); + if (includeGrp) { + if (!firstGrp) { + addSelector_OptGroupFoot(); + } + addSelector_OptGroup(toUnitOfMeasureName(uomIdx)); + firstGrp = false; } - addSelector_OptGroup(toUnitOfMeasureName(uomIdx)); - firstGrp = false; } if ((idx & 0x07) == 0) { delay(0); } } @@ -517,11 +404,7 @@ void addRowLabel_tr_id(const __FlashStringHelper *label, const __FlashStringHelp void addRowLabel_tr_id(const __FlashStringHelper *label, const String& id) { - if (id.isEmpty()) { - addRowLabel(label); - } else { - addRowLabel_tr_id(String(label), id); - } + addRowLabel_tr_id(String(label), id); } void addRowLabel_tr_id(const String& label, const String& id) @@ -535,9 +418,7 @@ void addRowLabel_tr_id(const String& label, const String& id) void addRowLabel(const __FlashStringHelper *label) { - html_TR_TD(); - addHtml(concat(label, F(":"))); - html_TD(); + addRowLabel(String(label), EMPTY_STRING); } void addRowLabel(const String& label, const String& id) @@ -557,15 +438,10 @@ void addRowLabel(const String& label, const String& id) addHtml(F("")); html_TD(); } - +#ifdef WEBSERVER_GITHUB_COPY // Add a row label and mark it with copy markers to copy it to clipboard. void addRowLabel_copy(const __FlashStringHelper *label) { - addHtml(F("")); - html_copyText_TD(); - addHtml(label); - addHtml(':'); - html_copyText_marker(); - html_copyText_TD(); + addRowLabel_copy(String(label)); } void addRowLabel_copy(const String& label) { @@ -576,6 +452,7 @@ void addRowLabel_copy(const String& label) { html_copyText_marker(); html_copyText_TD(); } +#endif void addRowLabel(LabelType::Enum label) { addRowLabel(getLabel(label)); @@ -584,28 +461,29 @@ void addRowLabel(LabelType::Enum label) { void addRowLabelValue(LabelType::Enum label) { addRowLabel(getLabel(label)); addHtml(getValue(label)); +#if FEATURE_TASKVALUE_UNIT_OF_MEASURE addUnit(getFormUnit(label)); +#endif } void addRowLabelValues(const LabelType::Enum labels[]) { - size_t i = 0; - LabelType::Enum cur = static_cast(pgm_read_byte(labels + i)); - while (true) { - const LabelType::Enum next = static_cast(pgm_read_byte(labels + i + 1)); - addRowLabelValue(cur); - if (next == LabelType::MAX_LABEL) { - return; - } - ++i; - cur = next; - } + KeyValueWriter_WebForm writer(true); + writer.writeLabels(labels, true); } void addRowLabelValue_copy(LabelType::Enum label) { addRowLabel_copy(getLabel(label)); addHtml(getValue(label)); +#if FEATURE_TASKVALUE_UNIT_OF_MEASURE addUnit(getFormUnit(label)); +#endif +} + +void addRowColspan(int colspan) { + addHtml(strformat( + F(""), + colspan)); } // ******************************************************************************** @@ -613,13 +491,7 @@ void addRowLabelValue_copy(LabelType::Enum label) { // ******************************************************************************** void addTableSeparator(const __FlashStringHelper *label, int colspan, int h_size) { - addHtml(strformat( - F(""), - colspan, h_size)); - addHtml(label); - addHtml(strformat( - F(""), - h_size)); + addTableSeparator(String(label), colspan, h_size); } void addTableSeparator(const __FlashStringHelper *label, int colspan, int h_size, const __FlashStringHelper *helpButton) @@ -628,10 +500,8 @@ void addTableSeparator(const __FlashStringHelper *label, int colspan, int h_size } void addTableSeparator(const String& label, int colspan, int h_size, const String& helpButton) { - addHtml(strformat( - F(""), - colspan, h_size)); - addHtml(label); + addRowColspan(colspan); + addHtml(strformat(F("%s"), h_size, label.c_str())); if (!helpButton.isEmpty()) { addHelpButton(helpButton); @@ -784,7 +654,7 @@ void addNumericBox(const String& id, int value, int min, int max, bool disabled) #endif // if FEATURE_TOOLTIPS -void addFloatNumberBox(const String& id, float value, float min, float max, unsigned int nrDecimals, float stepsize +void addFloatNumberBox(const String& id, float value, float min, float max, uint8_t nrDecimals, float stepsize #if FEATURE_TOOLTIPS , const String& tooltip #endif // if FEATURE_TOOLTIPS @@ -1010,6 +880,14 @@ void addRTDControllerButton(cpluginID_t cpluginID) { F("Controller/%s.html"), get_formatted_Controller_number(cpluginID).c_str())); } + +void addRTDNetworkDriverButton(ESPEasy::net::nwpluginID_t nwpluginID) +{ + addRTDHelpButton( + strformat( + F("Network/%s.html"), + nwpluginID.toDisplayString().c_str())); +} # endif // ifndef LIMIT_BUILD_SIZE String makeDocLink(const String& url, bool isRTD) { diff --git a/src/src/WebServer/Markup.h b/src/src/WebServer/Markup.h index 10a8efc03..cf2720a1b 100644 --- a/src/src/WebServer/Markup.h +++ b/src/src/WebServer/Markup.h @@ -4,37 +4,42 @@ #include "../WebServer/common.h" #include "../DataTypes/ProtocolIndex.h" #include "../DataTypes/CPluginID.h" +#include "../ESPEasy/net/DataTypes/NWPluginID.h" #include "../DataTypes/PluginID.h" #include "../Globals/Plugins.h" #include "../Helpers/StringGenerator_GPIO.h" -#ifdef ESP32_CLASSIC -#define ESP32XX "esp32" -#elif defined(ESP32C2) -#define ESP32XX "esp32c2" -#elif defined(ESP32C3) -#define ESP32XX "esp32c3" -#elif defined(ESP32C5) -#define ESP32XX "esp32c5" -#elif defined(ESP32C6) -#define ESP32XX "esp32c6" -#elif defined(ESP32C61) -#define ESP32XX "esp32c61" -#elif defined(ESP32H2) -#define ESP32XX "esp32h2" -#elif defined(ESP32H21) -#define ESP32XX "esp32h21" -#elif defined(ESP32H4) -#define ESP32XX "esp32h4" -#elif defined(ESP32S2) -#define ESP32XX "esp32s2" -#elif defined(ESP32S3) -#define ESP32XX "esp32s3" -#elif defined(ESP32P4) -#define ESP32XX "esp32p4" -#endif - +#ifdef CONFIG_ARDUINO_VARIANT +// TD-er: Might also use CONFIG_IDF_TARGET +# define ESP32XX CONFIG_ARDUINO_VARIANT +#else +# ifdef ESP32_CLASSIC +# define ESP32XX "esp32" +# elif defined(ESP32C2) +# define ESP32XX "esp32c2" +# elif defined(ESP32C3) +# define ESP32XX "esp32c3" +# elif defined(ESP32C5) +# define ESP32XX "esp32c5" +# elif defined(ESP32C6) +# define ESP32XX "esp32c6" +# elif defined(ESP32C61) +# define ESP32XX "esp32c61" +# elif defined(ESP32H2) +# define ESP32XX "esp32h2" +# elif defined(ESP32H21) +# define ESP32XX "esp32h21" +# elif defined(ESP32H4) +# define ESP32XX "esp32h4" +# elif defined(ESP32S2) +# define ESP32XX "esp32s2" +# elif defined(ESP32S3) +# define ESP32XX "esp32s3" +# elif defined(ESP32P4) +# define ESP32XX "esp32p4" +# endif // ifdef ESP32_CLASSIC +#endif // ifdef CONFIG_ARDUINO_VARIANT #if FEATURE_TOOLTIPS void addTooltip(const String& tooltip); @@ -46,58 +51,59 @@ void addTooltip(const String& tooltip); void addSelector_Head(const String& id); -void addSelector_Head_reloadOnChange(const __FlashStringHelper * id); -//void addSelector_Head_reloadOnChange(const String& id); +void addSelector_Head_reloadOnChange(const __FlashStringHelper *id); + +// void addSelector_Head_reloadOnChange(const String& id); -void addSelector_Head_reloadOnChange(const String& id, - const __FlashStringHelper * classname, +void addSelector_Head_reloadOnChange(const String & id, + const __FlashStringHelper *classname, bool disabled - #if FEATURE_TOOLTIPS + #if FEATURE_TOOLTIPS , - const String& tooltip = EMPTY_STRING + const String & tooltip = EMPTY_STRING #endif // if FEATURE_TOOLTIPS ); -void addSelector_Head_reloadOnChange(const String& id, - const __FlashStringHelper * classname, - const String& onChangeCall, +void addSelector_Head_reloadOnChange(const String & id, + const __FlashStringHelper *classname, + const String & onChangeCall, bool disabled - #if FEATURE_TOOLTIPS + #if FEATURE_TOOLTIPS , - const String& tooltip = EMPTY_STRING + const String & tooltip = EMPTY_STRING #endif // if FEATURE_TOOLTIPS ); -void do_addSelector_Head(const String& id, - const __FlashStringHelper * classname, - const String& onChangeCall, +void do_addSelector_Head(const String & id, + const __FlashStringHelper *classname, + const String & onChangeCall, const bool& disabled - #if FEATURE_TOOLTIPS + #if FEATURE_TOOLTIPS , - const String& tooltip = EMPTY_STRING + const String & tooltip = EMPTY_STRING #endif // if FEATURE_TOOLTIPS ); void addPinSelector_Item(PinSelectPurpose purpose, const String & gpio_label, int gpio, - bool selected, - bool disabled = false, + bool selected, + bool disabled = false, const String & attr = EMPTY_STRING); void addSelector_Item(const __FlashStringHelper *option, int index, - bool selected, - bool disabled = false, + bool selected, + bool disabled = false, const String & attr = EMPTY_STRING); void addSelector_Item(const String& option, int index, - bool selected, - bool disabled = false, + bool selected, + bool disabled = false, const String& attr = EMPTY_STRING); -void addSelector_Foot(bool reloadonchange = false); +void addSelector_Foot(bool reloadonchange = false); void addSelector_OptGroup(const String& label); void addSelector_OptGroupFoot(); @@ -107,11 +113,10 @@ void addUnit(const String& unit); void addUnit(char unit); #if FEATURE_TASKVALUE_UNIT_OF_MEASURE -String toUnitOfMeasureName(const uint32_t unitOfMeasureIndex, - const String & defUoM = EMPTY_STRING); -int getUnitOfMeasureIndex(const String& uomName); -void addUnitOfMeasureSelector(const String& id, - const uint8_t unitOfMeasure); + +void addUnitOfMeasureSelector(const String& id, + const uint8_t unitOfMeasure, + const uint64_t groupMap); #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE void addRowLabel_tr_id(const __FlashStringHelper *label, @@ -124,10 +129,15 @@ void addRowLabel_tr_id(const String& label, void addRowLabel(const __FlashStringHelper *label); void addRowLabel(const String& label, const String& id = EMPTY_STRING); - +#ifdef WEBSERVER_GITHUB_COPY // Add a row label and mark it with copy markers to copy it to clipboard. void addRowLabel_copy(const __FlashStringHelper *label); void addRowLabel_copy(const String& label); +#else +#ifndef addRowLabel_copy +#define addRowLabel_copy(L) addRowLabel(L) +#endif +#endif void addRowLabel(LabelType::Enum label); @@ -137,6 +147,8 @@ void addRowLabelValues(const LabelType::Enum labels[]); void addRowLabelValue_copy(LabelType::Enum label); +void addRowColspan(int colspan); + // ******************************************************************************** // Add a header // ******************************************************************************** @@ -158,13 +170,15 @@ void addFormHeader(const __FlashStringHelper *header, void addFormHeader(const __FlashStringHelper *header, const __FlashStringHelper *helpButton, const __FlashStringHelper *rtdHelpButton); -/* -void addFormHeader(const String& header, + +/* + void addFormHeader(const String& header, const String& helpButton = EMPTY_STRING); -void addFormHeader(const String& header, + void addFormHeader(const String& header, const String& helpButton, const String& rtdHelpButton); -*/ + */ + // ******************************************************************************** // Add a sub header // ******************************************************************************** @@ -175,35 +189,35 @@ void addFormSubHeader(const String& header); // Add a checkbox // ******************************************************************************** void addCheckBox(const String& id, - bool checked, + bool checked, bool disabled = false - #if FEATURE_TOOLTIPS + #if FEATURE_TOOLTIPS , const String& tooltip = EMPTY_STRING #endif // if FEATURE_TOOLTIPS ); void addCheckBox(const __FlashStringHelper *id, - bool checked, + bool checked, bool disabled = false); // ******************************************************************************** // Add a numeric box // ******************************************************************************** #if FEATURE_TOOLTIPS -void addNumericBox(const String& id, - int value, - int min, - int max, - const __FlashStringHelper * classname, - const String& tooltip = EMPTY_STRING, - bool disabled = false); +void addNumericBox(const String & id, + int value, + int min, + int max, + const __FlashStringHelper *classname, + const String & tooltip = EMPTY_STRING, + bool disabled = false); #endif // if FEATURE_TOOLTIPS void addFloatNumberBox(const String& id, float value, float min, float max, - unsigned int nrDecimals = 6, + uint8_t nrDecimals = 6, float stepsize = 0.0f #if FEATURE_TOOLTIPS , @@ -224,23 +238,23 @@ void addNumericBox(const String& id, // ******************************************************************************** // Add Textbox // ******************************************************************************** -void addTextBox(const __FlashStringHelper * id, - const String& value, - int maxlength, - const __FlashStringHelper * classname); +void addTextBox(const __FlashStringHelper *id, + const String & value, + int maxlength, + const __FlashStringHelper *classname); -void addTextBox(const String& id, - const String& value, - int maxlength, - const __FlashStringHelper * classname); +void addTextBox(const String & id, + const String & value, + int maxlength, + const __FlashStringHelper *classname); -void addTextBox(const __FlashStringHelper * id, - const String& value, - int maxlength, - bool readonly = false, - bool required = false, - const String& pattern = EMPTY_STRING); +void addTextBox(const __FlashStringHelper *id, + const String & value, + int maxlength, + bool readonly = false, + bool required = false, + const String & pattern = EMPTY_STRING); void addTextBox(const String& id, const String& value, @@ -254,8 +268,8 @@ void addTextBox(const String& id, bool readonly, bool required, const String& pattern, - const __FlashStringHelper * classname - #if FEATURE_TOOLTIPS + const __FlashStringHelper *classname + #if FEATURE_TOOLTIPS , const String& tooltip = EMPTY_STRING #endif // if FEATURE_TOOLTIPS @@ -273,7 +287,7 @@ void addTextArea(const String& id, int columns, bool readonly, bool required - #if FEATURE_TOOLTIPS + #if FEATURE_TOOLTIPS , const String& tooltip = EMPTY_STRING #endif // if FEATURE_TOOLTIPS @@ -294,9 +308,10 @@ void addHelpButton(const String& url, bool isRTD); void addRTDPluginButton(pluginID_t pluginID); -# ifndef LIMIT_BUILD_SIZE +#ifndef LIMIT_BUILD_SIZE void addRTDControllerButton(cpluginID_t cpluginID); -# endif // ifndef LIMIT_BUILD_SIZE +void addRTDNetworkDriverButton(ESPEasy::net::nwpluginID_t nwpluginID); +#endif // ifndef LIMIT_BUILD_SIZE String makeDocLink(const String& url, bool isRTD); @@ -311,24 +326,25 @@ void addPinSelect(PinSelectPurpose purpose, #ifdef ESP32 -#if SOC_ADC_SUPPORTED +# if SOC_ADC_SUPPORTED enum class AdcPinSelectPurpose { TouchOnly, ADC_Touch, -#if HAS_HALL_EFFECT_SENSOR +# if HAS_HALL_EFFECT_SENSOR ADC_Touch_HallEffect, -#endif +# endif ADC_Touch_Optional + }; void addADC_PinSelect(AdcPinSelectPurpose purpose, const String & id, int choice); -#endif -#if SOC_DAC_SUPPORTED -void addDAC_PinSelect(const String& id, +# endif // if SOC_ADC_SUPPORTED +# if SOC_DAC_SUPPORTED +void addDAC_PinSelect(const String& id, int choice); -#endif +# endif // if SOC_DAC_SUPPORTED #endif // ifdef ESP32 diff --git a/src/src/WebServer/Markup_Buttons.cpp b/src/src/WebServer/Markup_Buttons.cpp index 0b074508c..29c5845f7 100644 --- a/src/src/WebServer/Markup_Buttons.cpp +++ b/src/src/WebServer/Markup_Buttons.cpp @@ -34,7 +34,7 @@ void addButton(const String& url, const String& label, const String& classes, bo addHtml(label); addHtml(F("")); } - +# ifdef WEBSERVER_NEW_RULES void addButtonWithSvg(const String& url, const String& label) { addButtonWithSvg(url, label, EMPTY_STRING, false); @@ -44,7 +44,7 @@ void addButtonWithSvg(const String& url, const String& label, const String& svgP addHtml(F(" 0; if (hasSVG) @@ -58,7 +58,7 @@ void addButtonWithSvg(const String& url, const String& label, const String& svgP } addHtml('>'); - #ifndef BUILD_MINIMAL_OTA + #ifndef LIMIT_BUILD_SIZE if (hasSVG) { addHtml(F("")); @@ -105,7 +105,7 @@ void addDeleteButton(const String& url, const String& label) true); #endif // ifdef BUILD_MINIMAL_OTA } - +#endif void addWideButton(const __FlashStringHelper * url, const __FlashStringHelper * label) { html_add_wide_button_prefix(EMPTY_STRING, true); addHtml(url); @@ -167,6 +167,7 @@ void addSubmitButton(const String& value, const String& name, const String& clas addHtml(F("/>
")); } +#ifdef WEBSERVER_GITHUB_COPY // add copy to clipboard button void addCopyButton(const String& value, const String& delimiter, const String& name) { @@ -186,3 +187,31 @@ void addCopyButton(const String& value, const String& delimiter, const String& n html_copyText_marker(); addHtml(F(")")); } +#endif + +void addPlugin_Add_Edit_Button(const __FlashStringHelper * urlPrefix, size_t index, bool plugin_set, bool plugin_supported, const String& symbol) +{ + if (plugin_set && !plugin_supported) { + html_add_button_prefix(F("red"), true); + } else { + html_add_button_prefix(); + } + + addHtml(urlPrefix); + addHtml(F("?index=")); + addHtmlInt(index + 1); + addHtml('\'', '>'); + + if (plugin_set) { + addHtml(F("Edit")); + } else { + addHtml(F("Add")); + } + addHtml(F("
")); + if (symbol.isEmpty()) { + addHtmlInt(index + 1); + } else { + addHtml(symbol); + } + html_TD(); +} \ No newline at end of file diff --git a/src/src/WebServer/Markup_Buttons.h b/src/src/WebServer/Markup_Buttons.h index eb6c323f6..e8a3f5754 100644 --- a/src/src/WebServer/Markup_Buttons.h +++ b/src/src/WebServer/Markup_Buttons.h @@ -12,6 +12,7 @@ void addButton(const String& url, const String& label); void addButton(const String& url, const String& label, const String& classes, bool enabled = true); +# ifdef WEBSERVER_NEW_RULES void addButtonWithSvg(const String& url, const String& label); void addButtonWithSvg(const String& url, const String& label, const String& svgPath, bool needConfirm); @@ -19,7 +20,7 @@ void addButtonWithSvg(const String& url, const String& label, const String& svgP void addSaveButton(const String& url, const String& label); void addDeleteButton(const String& url, const String& label); - +#endif void addWideButton(const __FlashStringHelper * url, const __FlashStringHelper * label); void addWideButton(const String& url, const String& label); @@ -36,9 +37,12 @@ void addSubmitButton(const String& value, const String& name); void addSubmitButton(const __FlashStringHelper * value, const __FlashStringHelper * name, const __FlashStringHelper * classes); void addSubmitButton(const String& value, const String& name, const String& classes); +#ifdef WEBSERVER_GITHUB_COPY // add copy to clipboard button void addCopyButton(const String& value, const String& delimiter, const String& name); +#endif +void addPlugin_Add_Edit_Button(const __FlashStringHelper * urlPrefix, size_t index, bool plugin_set, bool plugin_supported, const String& symbol = EMPTY_STRING); #endif \ No newline at end of file diff --git a/src/src/WebServer/Markup_Forms.cpp b/src/src/WebServer/Markup_Forms.cpp index 4ab8d9b33..8580a8de0 100644 --- a/src/src/WebServer/Markup_Forms.cpp +++ b/src/src/WebServer/Markup_Forms.cpp @@ -12,6 +12,9 @@ #include "../Helpers/StringConverter.h" #include "../Helpers/StringGenerator_GPIO.h" + +#define MARKUP_FORMS_PASSWORD_MASK_ASTERISKS "****" + # if FEATURE_MQTT_DISCOVER && FEATURE_MQTT_DEVICECLASS #include "../Helpers/_CPlugin_Helper_mqtt.h" # endif // if FEATURE_MQTT_DISCOVER && FEATURE_MQTT_DEVICECLASS @@ -21,9 +24,8 @@ // ******************************************************************************** void addFormSeparator(int clspan) { - addHtml(strformat( - F("
"), - clspan)); + addRowColspan(clspan); + addHtml(F("
")); } // ******************************************************************************** @@ -32,9 +34,7 @@ void addFormSeparator(int clspan) void addFormNote(const __FlashStringHelper * text) { addRowLabel_tr_id(EMPTY_STRING, EMPTY_STRING); - addHtml(F("
Note: ")); - addHtml(text); - addHtml(F("
")); + addHtmlDiv(F("note"), concat(F("Note: "), text)); } void addFormNote(const String& text, const String& id) @@ -44,12 +44,6 @@ void addFormNote(const String& text, const String& id) addHtmlDiv(F("note"), concat(F("Note: "), text)); } -void addFormNote(const LabelType::Enum& label) -{ - addUnit(getFormUnit(label)); - addFormNote(getFormNote(label)); -} - // ******************************************************************************** // Create Forms // ******************************************************************************** @@ -97,40 +91,59 @@ void addFormCheckBox(const String& label, const String& id, bool checked, bool d ); } -void addFormCheckBox(LabelType::Enum label, bool checked, bool disabled +void addFormCheckBoxes(const LabelType::Enum* label, size_t nrLabels) +{ + if (label == nullptr) return; + for (size_t i = 0; i < nrLabels; ++i) { + addFormCheckBox(*label); + ++label; + } +} + +void addFormCheckBox(LabelType::Enum label, bool disabled #if FEATURE_TOOLTIPS , const String& tooltip #endif // if FEATURE_TOOLTIPS ) { - addFormCheckBox(getLabel(label), getInternalLabel(label), checked, disabled + auto kv = getKeyValue(label); + const bool checked = getValue_int(kv) != 0; + + addFormCheckBox(getLabel(kv), getInternalLabel(kv), checked, disabled #if FEATURE_TOOLTIPS , tooltip #endif // if FEATURE_TOOLTIPS ); - addFormNote(label); + #if FEATURE_TASKVALUE_UNIT_OF_MEASURE + addUnit(kv.getUnit()); + #endif + addFormNote(getFormNote(label)); } -void addFormCheckBox_disabled(LabelType::Enum label, bool checked) { - addFormCheckBox(label, checked, true); +void addFormCheckBox_disabled(LabelType::Enum label) { + addFormCheckBox(label, true); } // ******************************************************************************** // Add a Numeric Box form // ******************************************************************************** -void addFormNumericBox(LabelType::Enum label, int value, int min, int max +void addFormNumericBox(LabelType::Enum label, int min, int max #if FEATURE_TOOLTIPS , const String& tooltip #endif // if FEATURE_TOOLTIPS , bool disabled ) { - addFormNumericBox(getLabel(label), getInternalLabel(label), value, min, max + auto kv = getKeyValue(label); + addFormNumericBox(getLabel(kv), getInternalLabel(kv), getValue(kv).toInt(), min, max #if FEATURE_TOOLTIPS , tooltip #endif // if FEATURE_TOOLTIPS , disabled ); - addFormNote(label); + #if FEATURE_TASKVALUE_UNIT_OF_MEASURE + addUnit(kv.getUnit()); + #endif + addFormNote(getFormNote(label)); } void addFormNumericBox(const __FlashStringHelper * label, @@ -172,17 +185,22 @@ void addFormNumericBox(const String& label, const String& id, int value, int min ); } -void addFormFloatNumberBox(LabelType::Enum label, float value, float min, float max, uint8_t nrDecimals, float stepsize +void addFormFloatNumberBox(LabelType::Enum label, float min, float max, uint8_t nrDecimals, float stepsize #if FEATURE_TOOLTIPS , const String& tooltip #endif // if FEATURE_TOOLTIPS ) { - addFormFloatNumberBox(getLabel(label), getInternalLabel(label), value, min, max, nrDecimals, stepsize + auto kv = getKeyValue(label); + + addFormFloatNumberBox(getLabel(kv), getInternalLabel(kv), getValue_float(kv), min, max, nrDecimals, stepsize #if FEATURE_TOOLTIPS , tooltip #endif // if FEATURE_TOOLTIPS ); - addFormNote(label); + #if FEATURE_TASKVALUE_UNIT_OF_MEASURE + addUnit(kv.getUnit()); + #endif + addFormNote(getFormNote(label)); } void addFormFloatNumberBox(const String& label, @@ -346,7 +364,7 @@ void addFormPasswordBox(const String& label, const String& id, const String& pas #if FEATURE_TOOLTIPS addTooltip(tooltip); #endif // if FEATURE_TOOLTIPS - addHtmlAttribute(F("value"), (password.length() == 0) ? F("") : F("*****")); + addHtmlAttribute(F("value"), (password.length() == 0) ? F("") : F(MARKUP_FORMS_PASSWORD_MASK_ASTERISKS)); addHtml('>'); } @@ -358,7 +376,7 @@ bool getFormPassword(const String& id, String& password) addLog(LOG_LEVEL_DEBUG, concat(F("getFormPassword: "), password)); } */ - return !equals(password, F("*****")); + return !equals(password, F(MARKUP_FORMS_PASSWORD_MASK_ASTERISKS)); } // ******************************************************************************** @@ -600,11 +618,14 @@ void addFormPinStateSelect(int gpio, int choice) createGPIO_label(gpio, pinnr, input, output, warning)), id, choice); - addUnit(getConflictingUse(gpio)); #ifdef ESP32 if (isPSRAMInterfacePin(gpio)) { addUnit(getConflictingUse(gpio, PinSelectPurpose::Generic, true)); + } else { + addUnit(getConflictingUse(gpio)); } + #else + addUnit(getConflictingUse(gpio)); #endif // ifdef ESP32 } } @@ -741,6 +762,13 @@ bool isFormItem(const String& id) } void copyFormPassword(const __FlashStringHelper * id, char *pPassword, int maxlength) +{ + copyFormPassword(String(id), pPassword, maxlength); +} + +void copyFormPassword(const String& id, + char *pPassword, + int maxlength) { String password; @@ -764,6 +792,9 @@ void addFormSelector_binarySensorDeviceClass(const __FlashStringHelper*label, if (MQTT_binary_deviceClassTwoWay(devClassIndex)) { devClassName += F("²"); } + if (MQTT_binary_deviceClassSwitch(devClassIndex)) { + devClassName += F("÷"); // These are multi-byte chars, so we have to use the F() macro + } while (!devClassName.isEmpty() || (0 == devClassIndex)) { binaryDeviceClasses.push_back(devClassName); @@ -772,6 +803,9 @@ void addFormSelector_binarySensorDeviceClass(const __FlashStringHelper*label, if (MQTT_binary_deviceClassTwoWay(devClassIndex)) { devClassName += F("²"); } + if (MQTT_binary_deviceClassSwitch(devClassIndex)) { + devClassName += F("÷"); + } } const FormSelectorOptions deviceClass( devClassIndex, diff --git a/src/src/WebServer/Markup_Forms.h b/src/src/WebServer/Markup_Forms.h index 143945795..2251e76dd 100644 --- a/src/src/WebServer/Markup_Forms.h +++ b/src/src/WebServer/Markup_Forms.h @@ -4,7 +4,7 @@ #include "../WebServer/common.h" #include "../DataTypes/FormSelectorOptions.h" -#include "../DataStructs/MAC_address.h" +#include "../../ESPEasy/net/DataStructs/MAC_address.h" #include "../Globals/Plugins.h" #include "../Helpers/StringGenerator_GPIO.h" @@ -19,7 +19,6 @@ void addFormSeparator(int clspan); // ******************************************************************************** void addFormNote(const __FlashStringHelper * text); void addFormNote(const String& text, const String& id = EMPTY_STRING); -void addFormNote(const LabelType::Enum& label); // ******************************************************************************** // Create Forms @@ -49,8 +48,9 @@ void addFormCheckBox(const String& label, #endif // if FEATURE_TOOLTIPS ); +void addFormCheckBoxes(const LabelType::Enum* label, size_t nrLabels); + void addFormCheckBox(LabelType::Enum label, - bool checked, bool disabled = false #if FEATURE_TOOLTIPS , @@ -58,8 +58,7 @@ void addFormCheckBox(LabelType::Enum label, #endif // if FEATURE_TOOLTIPS ); -void addFormCheckBox_disabled(LabelType::Enum label, - bool checked); +void addFormCheckBox_disabled(LabelType::Enum label); void addFormCheckBox(const __FlashStringHelper * label, const __FlashStringHelper * id, bool checked, bool disabled = false); void addFormCheckBox(const __FlashStringHelper * label, const String& id, bool checked, bool disabled = false); @@ -67,7 +66,6 @@ void addFormCheckBox(const __FlashStringHelper * label, const String& id, bool c // Add a Numeric Box form // ******************************************************************************** void addFormNumericBox(LabelType::Enum label, - int value, int min = INT_MIN, int max = INT_MAX #if FEATURE_TOOLTIPS @@ -106,7 +104,6 @@ void addFormNumericBox(const String& label, void addFormFloatNumberBox(LabelType::Enum label, - float value, float min, float max, uint8_t nrDecimals = 6, @@ -357,6 +354,9 @@ bool isFormItem(const String& id); void copyFormPassword(const __FlashStringHelper * id, char *pPassword, int maxlength); +void copyFormPassword(const String& id, + char *pPassword, + int maxlength); # if FEATURE_MQTT_DISCOVER && FEATURE_MQTT_DEVICECLASS void addFormSelector_binarySensorDeviceClass(const __FlashStringHelper*label, diff --git a/src/src/WebServer/Metrics.cpp b/src/src/WebServer/Metrics.cpp index 7fd4526b2..e3b8469c3 100644 --- a/src/src/WebServer/Metrics.cpp +++ b/src/src/WebServer/Metrics.cpp @@ -2,8 +2,8 @@ #include "../WebServer/ESPEasy_WebServer.h" #include "../../ESPEasy-Globals.h" #include "../Commands/Diagnostic.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyWifi.h" +#include "../../ESPEasy/net/ESPEasyNetwork.h" +#include "../../ESPEasy/net/wifi/ESPEasyWifi.h" #include "../../_Plugin_Helper.h" #include "../Helpers/ESPEasyStatistics.h" #include "../Static/WebStaticData.h" @@ -137,7 +137,13 @@ void handle_metrics_devices() { addHtml(F("{valueName=\"")); addHtml(Cache.getTaskDeviceValueName(x, varNr)); addHtml(F("\"} ")); - addHtml(formatUserVarNoCheck(&TempEvent, varNr)); + const String value(formatUserVarNoCheck(&TempEvent, varNr)); + + if (value.isEmpty()) { + addHtml('0'); // Return 0 for not-set values + } else { + addHtml(value); + } addHtml('\n'); } } diff --git a/src/src/WebServer/NetworkPage.cpp b/src/src/WebServer/NetworkPage.cpp new file mode 100644 index 000000000..4a7ec01c6 --- /dev/null +++ b/src/src/WebServer/NetworkPage.cpp @@ -0,0 +1,456 @@ +#include "../WebServer/NetworkPage.h" + +#ifdef WEBSERVER_NETWORK + + +# include "../DataStructs/ESPEasy_EventStruct.h" +# include "../Globals/Settings.h" +# include "../Helpers/ESPEasy_Storage.h" +# include "../Helpers/ESPEasy_Storage.h" +# include "../Helpers/PrintToString.h" +# include "../Helpers/StringConverter.h" +# include "../WebServer/ESPEasy_WebServer.h" +# include "../WebServer/HTML_wrappers.h" +# include "../WebServer/KeyValueWriter_WebForm.h" +# include "../WebServer/Markup.h" +# include "../WebServer/Markup_Buttons.h" +# include "../WebServer/Markup_Forms.h" +# include "../../ESPEasy/net/Globals/NWPlugins.h" +# include "../../ESPEasy/net/Helpers/_NWPlugin_Helper_webform.h" +# include "../../ESPEasy/net/Helpers/_NWPlugin_init.h" +# include "../../ESPEasy/net/Helpers/NW_info_writer.h" +# include "../../ESPEasy/net/_NWPlugin_Helper.h" + +# ifdef ESP8266 +# define MAX_NR_NETWORKS_IN_TABLE 2 +# endif +# ifdef ESP32 +# define MAX_NR_NETWORKS_IN_TABLE NETWORK_MAX +# endif + + +using namespace ESPEasy::net; + +void handle_networks() +{ +# ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("handle_networks")); +# endif // ifndef BUILD_NO_RAM_TRACKER + + if (!isLoggedIn()) { return; } + navMenuIndex = MENU_INDEX_NETWORK; + TXBuffer.startStream(); + sendHeadandTail_stdtemplate(_HEAD); + + + // 'index' value in the URL + uint8_t networkindex = getFormItemInt(F("index"), 0); + const bool networkIndexSet = networkindex != 0 && validNetworkIndex(networkindex - 1); + --networkindex; // Index in URL is starting from 1, but starting from 0 in the array. + + const int networkDriver_webarg_value = getFormItemInt(F("networkDriver"), -1); + + // submitted data + if ((networkDriver_webarg_value != -1) && networkIndexSet) + { + bool mustInit = false; + bool mustCallNWpluginSave = false; + + // TODO TD-er: Implement saving submitted settings + const nwpluginID_t nwpluginID = nwpluginID_t::toPluginID(networkDriver_webarg_value); + + auto NetworkSettings = MakeNetworkSettings(); + + if (Settings.getNWPluginID_for_network(networkindex) != nwpluginID) + { +# ifndef LIMIT_BUILD_SIZE + addLog(LOG_LEVEL_INFO, strformat( + F("HandleNW: Driver changed from %d to %d"), + Settings.getNWPluginID_for_network(networkindex).value, + nwpluginID.value)); +# endif // ifndef LIMIT_BUILD_SIZE + + // NetworkDriver has changed. + Settings.setNWPluginID_for_network(networkindex, nwpluginID); + + // there is a networkDriverIndex selected? + if (nwpluginID.isValid()) + { + mustInit = true; + + handle_networks_clearLoadDefaults(networkindex, *NetworkSettings); + } + } + + // subitted same networkDriverIndex + else + { + // there is a networkDriverIndex selected +# ifndef LIMIT_BUILD_SIZE + addLog(LOG_LEVEL_INFO, concat( + F("HandleNW: Driver selected: "), + nwpluginID.value) + (nwpluginID.isValid() ? F("valid") : F("invalid"))); +# endif // ifndef LIMIT_BUILD_SIZE + + if (nwpluginID.isValid()) + { + mustInit = true; + + handle_networks_CopySubmittedSettings(networkindex, *NetworkSettings); + mustCallNWpluginSave = true; + } + } + + if (mustCallNWpluginSave) { + // Call NWPLUGIN_WEBFORM_SAVE after destructing NetworkSettings object to reduce RAM usage. + // Network plugin almost only deals with custom network settings. + // Even if they need to save things to the NetworkSettings, then the changes must + // already be saved first as the NWPluginCall does not have the NetworkSettings as argument. + handle_networks_CopySubmittedSettings_NWPluginCall(networkindex); + } + addHtmlError(SaveSettings()); + + if (mustInit) { + // Init network plugin using the new settings. + NWPlugin_Exit_Init(networkindex); + } + + } + + html_add_form(); + + if (networkIndexSet) + { + handle_networks_NetworkSettingsPage(networkindex); + } + else + { + handle_networks_ShowAllNetworksTable(); + } + + sendHeadandTail_stdtemplate(_TAIL); + TXBuffer.endStream(); +} + +void handle_networks_clearLoadDefaults(ESPEasy::net::networkIndex_t networkindex, NetworkSettingsStruct& NetworkSettings) { + + // TODO TD-er: Must also check NetworkDriverStruct to see if something else must be done +} + +void handle_networks_CopySubmittedSettings(ESPEasy::net::networkIndex_t networkindex, NetworkSettingsStruct& NetworkSettings) +{ + // copy all settings to network settings struct + for (int parameterIdx = 0; parameterIdx <= NetworkSettingsStruct::NETWORK_ENABLED; ++parameterIdx) { + NetworkSettingsStruct::VarType varType = static_cast(parameterIdx); + saveNetworkParameterForm(NetworkSettings, networkindex, varType); + } + +} + +void handle_networks_CopySubmittedSettings_NWPluginCall(ESPEasy::net::networkIndex_t networkindex) { + networkDriverIndex_t NetworkDriverIndex = getNetworkDriverIndex_from_NetworkIndex(networkindex); + + if (validNetworkDriverIndex(NetworkDriverIndex)) { + struct EventStruct TempEvent; + TempEvent.NetworkIndex = networkindex; + + // Call network plugin to save CustomNetworkSettings +# ifndef LIMIT_BUILD_SIZE + addLog(LOG_LEVEL_INFO, F("Call network plugin to save CustomNetworkSettings")); +# endif +# ifdef ESP32 + Settings.setRoutePrio_for_network(networkindex, getFormItemInt(F("routeprio"), 0)); + Settings.setNetworkInterfaceSubnetBlockClientIP(networkindex, isFormItemChecked(F("block_web_access"))); +# endif // ifdef ESP32 +# if FEATURE_USE_IPV6 + Settings.setNetworkEnabled_IPv6(networkindex, isFormItemChecked(F("en_ipv6"))); +# endif + Settings.setNetworkInterfaceStartupDelayAtBoot(networkindex, getFormItemInt(F("delay_start"))); + String dummy; + ESPEasy::net::NWPluginCall(NWPlugin::Function::NWPLUGIN_WEBFORM_SAVE, &TempEvent, dummy); + } + +} + +void handle_networks_ShowAllNetworksTable() +{ + html_table_class_multirow(); + html_TR(); + html_table_header(F(""), 70); + html_table_header(F("Nr"), 50); + html_table_header(F("Enabled"), 100); + html_table_header(F("Network Adapter")); + html_table_header(F("Active"), 100); + html_table_header(F("Name"), 50); + # ifdef ESP32 + html_table_header(F("Route Prio"), 50); + # endif + html_table_header(F("Connected")); + html_table_header(F("Hostname/SSID")); + html_table_header(F("HW Address")); + html_table_header(F("IP")); +# ifndef LIMIT_BUILD_SIZE + html_table_header(F("Port")); +# endif + + for (ESPEasy::net::networkIndex_t x = 0; x < MAX_NR_NETWORKS_IN_TABLE; x++) + { + const nwpluginID_t nwpluginID = Settings.getNWPluginID_for_network(x); + const bool nwplugin_set = nwpluginID.isValid(); + + html_TR_TD(); + + addPlugin_Add_Edit_Button(F("network"), x, nwplugin_set, supportedNWPluginID(nwpluginID)); + + if (nwplugin_set) + { + addEnabled(Settings.getNetworkEnabled(x)); + html_TD(); + + // Network Adapter + addHtml(getNWPluginNameFromNWPluginID(Settings.getNWPluginID_for_network(x))); + + const NWPlugin::Function functions[] { + NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_ACTIVE, + NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_NAME, +# ifdef ESP32 + NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_ROUTE_PRIO, +# endif + NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_CONNECTED, + NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_HOSTNAME, + NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_HW_ADDRESS, + NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_IP +# ifndef LIMIT_BUILD_SIZE + , NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_PORT +# endif + }; + + // const networkDriverIndex_t NetworkDriverIndex = getNetworkDriverIndex_from_NetworkIndex(x); + + for (uint8_t i = 0; i < NR_ELEMENTS(functions); ++i) { + html_TD(); + + if (Settings.getNetworkEnabled(x) +# ifndef LIMIT_BUILD_SIZE + || (functions[i] == NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_PORT) +# endif + ) { + KeyValueWriter_WebForm webFormWriter; + webFormWriter.setSummaryValueOnly(); + struct EventStruct TempEvent; + TempEvent.kvWriter = &webFormWriter; + + TempEvent.NetworkIndex = x; + + String str; + + const bool res = ESPEasy::net::NWPluginCall(functions[i], &TempEvent); + + switch (functions[i]) + { +# ifdef ESP32 + + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_ROUTE_PRIO: + + if (TempEvent.Par1 > 0) { + addHtmlInt(TempEvent.Par1); + + if (TempEvent.Par2) { + addHtml(F("(*)")); + } + } + break; +# endif // ifdef ESP32 + + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_ACTIVE: + addEnabled(res); + break; + case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_CONNECTED: + + ESPEasy::net::NWPluginCall(NWPlugin::Function::NWPLUGIN_GET_CONNECTED_DURATION, &TempEvent); + + if (!res) { + addEnabled(res); + } + break; + default: break; + } + } + } + + + /* + const NetworkDriverStruct& driver = ESPEasy::net::getNetworkDriverStruct(NetworkDriverIndex); + + if ((INVALID_NETWORKDRIVER_INDEX == NetworkDriverIndex) || driver.usesPort) { + addHtmlInt(13 == nwpluginID ? Settings.UDPPort : NetworkSettings->Port); // P2P/C013 exception + } + */ + + } + else { +# ifndef LIMIT_BUILD_SIZE + html_TD(9); +# else + html_TD(8); +# endif // ifndef LIMIT_BUILD_SIZE + } + } + + html_end_table(); + html_end_form(); +} + +void handle_networks_NetworkSettingsPage(ESPEasy::net::networkIndex_t networkindex) +{ + if (!validNetworkIndex(networkindex)) { return; } + + const networkDriverIndex_t networkDriverIndex = + getNetworkDriverIndex_from_NWPluginID( + Settings.getNWPluginID_for_network(networkindex)); + const NetworkDriverStruct& cur_driver = ESPEasy::net::getNetworkDriverStruct(networkDriverIndex); + + + // Show network settings page + { + html_table_class_normal(); + addFormHeader(F("Network Settings")); + addRowLabel(F("Network Driver")); + const nwpluginID_t choice = Settings.getNWPluginID_for_network(networkindex); + + const bool networkDriverSelectorDisabled = cur_driver.alwaysPresent && validNetworkIndex(cur_driver.fixedNetworkIndex) && + networkindex == cur_driver.fixedNetworkIndex; + + if (networkDriverSelectorDisabled) { + addSelector_Head(F("networkDriver")); + + // Must add the fixed network driver label here. + + addSelector_Item(getNWPluginNameFromNetworkDriverIndex(networkDriverIndex), + choice.value, + true); + } else { + addSelector_Head_reloadOnChange(F("networkDriver")); + addSelector_Item(F("- Standalone -"), 0, false, false, EMPTY_STRING); + networkDriverIndex_t tmpNetworkDriverIndex{}; + + while (validNetworkDriverIndex(tmpNetworkDriverIndex)) + { + const NetworkDriverStruct& driver = getNetworkDriverStruct(tmpNetworkDriverIndex); + const nwpluginID_t number = getNWPluginID_from_NetworkDriverIndex(tmpNetworkDriverIndex); + const bool disabled = driver.alwaysPresent && + validNetworkIndex(driver.fixedNetworkIndex) && + networkindex != driver.fixedNetworkIndex; + addSelector_Item(getNWPluginNameFromNetworkDriverIndex(tmpNetworkDriverIndex), + number.value, + choice == number, + disabled); + ++tmpNetworkDriverIndex; + } + } + addSelector_Foot(); + + } + + # ifndef LIMIT_BUILD_SIZE + addRTDNetworkDriverButton(getNWPluginID_from_NetworkDriverIndex(networkDriverIndex)); + # endif // ifndef LIMIT_BUILD_SIZE + + if (Settings.getNWPluginID_for_network(networkindex)) { + // Separate enabled checkbox as it doesn't need to use the NetworkSettings. + // So NetworkSettings object can be destructed before network specific settings are loaded. + addNetworkEnabledForm(networkindex); + addFormSeparator(2); + + // TODO TD-er: Add driver specifics from NetworkDriverStruct + + // Load network specific settings + struct EventStruct TempEvent; + TempEvent.NetworkIndex = networkindex; + +# ifdef ESP32 + addFormNumericBox( + F("Route Priority"), + F("routeprio"), + Settings.getRoutePrio_for_network(networkindex), + 0, 255); + addFormNote(F("The active interface with highest priority will be used for default route (gateway).")); +# endif // ifdef ESP32 + addFormCheckBox(F("Block Web Access"), F("block_web_access"), Settings.getNetworkInterfaceSubnetBlockClientIP(networkindex)); + addFormNote(F("When checked, any host from a subnet on this network interface will be blocked")); + addFormNumericBox(F("Delay Startup At Boot"), F("delay_start"), Settings.getNetworkInterfaceStartupDelayAtBoot(networkindex), 0, 60000); + addUnit(F("ms")); + +# if FEATURE_USE_IPV6 + addFormCheckBox(F("Enable IPv6"), F("en_ipv6"), Settings.getNetworkEnabled_IPv6(networkindex)); + + if (!Settings.EnableIPv6()) { + addFormNote(F("IPv6 is disabled on tools->Advanced page")); + } +# endif // if FEATURE_USE_IPV6 + + String str; + ESPEasy::net::NWPluginCall(NWPlugin::Function::NWPLUGIN_WEBFORM_LOAD, &TempEvent, str); + +# if FEATURE_NETWORK_STATS + + if (Settings.getNetworkEnabled(TempEvent.NetworkIndex)) + { + // Task statistics and historic data in a chart + auto *NW_data = ESPEasy::net::getNWPluginData(TempEvent.NetworkIndex); + + if (NW_data && NW_data->hasPluginStats()) { + addFormSubHeader(F("Statistics")); +# if FEATURE_CHART_JS + + if (NW_data->nrSamplesPresent() > 0) { + addRowColspan(2); + + // addRowLabel(F("Historic data")); + NW_data->plot_ChartJS(); + addHtml(F("")); + } +# endif // if FEATURE_CHART_JS + String dummy; + + // bool somethingAdded = false; + + if (!ESPEasy::net::NWPluginCall(NWPlugin::Function::NWPLUGIN_WEBFORM_LOAD_SHOW_STATS, &TempEvent, dummy)) { + /*somethingAdded =*/ NW_data->webformLoad_show_stats(&TempEvent); + + // } else { + // somethingAdded = true; + } + } + } + +# endif // if FEATURE_NETWORK_STATS + +# ifdef ESP32 + + if (ESPEasy::net::NWPluginCall(NWPlugin::Function::NWPLUGIN_GET_INTERFACE, &TempEvent, str)) + { + KeyValueWriter_WebForm writer(true); + + ESPEasy::net::write_NetworkAdapterFlags(networkindex, writer.createChild(F("Network Interface")).get()); +# ifndef LIMIT_BUILD_SIZE + ESPEasy::net::write_NetworkAdapterPort(networkindex, writer.createChild(F("Port")).get()); +# endif + ESPEasy::net::write_IP_config(networkindex, writer.createChild(F("IP Config")).get()); + ESPEasy::net::write_NetworkConnectionInfo(networkindex, writer.createChild(F("Connection Information")).get()); + } +# endif // ifdef ESP32 + + } + + addFormSeparator(2); + html_TR_TD(); + html_TD(); + addButton(F("network"), F("Close")); + addSubmitButton(); + html_end_table(); + html_end_form(); + +} + +#endif // ifdef WEBSERVER_NETWORK diff --git a/src/src/WebServer/NetworkPage.h b/src/src/WebServer/NetworkPage.h new file mode 100644 index 000000000..6da6e2c3c --- /dev/null +++ b/src/src/WebServer/NetworkPage.h @@ -0,0 +1,44 @@ +#ifndef WEBSERVER_WEBSERVER_NETWORKPAGE_H +#define WEBSERVER_WEBSERVER_NETWORKPAGE_H + +#include "../WebServer/common.h" + +#ifdef WEBSERVER_NETWORK + +#include "../Helpers/KeyValueWriter.h" + +#include "../../ESPEasy/net/DataTypes/NetworkIndex.h" +#include "../../ESPEasy/net/DataStructs/NetworkSettingsStruct.h" + + +// ******************************************************************************** +// Web Interface Network page +// ******************************************************************************** +void handle_networks(); + +// ******************************************************************************** +// Selected network has changed. +// Clear all Network settings and load some defaults +// ******************************************************************************** +void handle_networks_clearLoadDefaults(ESPEasy::net::networkIndex_t networkindex, ESPEasy::net::NetworkSettingsStruct& NetworkSettings); + +// ******************************************************************************** +// Collect all submitted form data and store in the NetworkSettings +// ******************************************************************************** +void handle_networks_CopySubmittedSettings(ESPEasy::net::networkIndex_t networkindex, ESPEasy::net::NetworkSettingsStruct& NetworkSettings); + +void handle_networks_CopySubmittedSettings_NWPluginCall(ESPEasy::net::networkIndex_t networkindex); + +// ******************************************************************************** +// Show table with all selected networks +// ******************************************************************************** +void handle_networks_ShowAllNetworksTable(); + +// ******************************************************************************** +// Show the network settings page +// ******************************************************************************** +void handle_networks_NetworkSettingsPage(ESPEasy::net::networkIndex_t networkindex); + + +#endif // ifdef WEBSERVER_NETWORK +#endif // ifndef WEBSERVER_WEBSERVER_NETWORKPAGE_H diff --git a/src/src/WebServer/NotificationPage.cpp b/src/src/WebServer/NotificationPage.cpp index 7852bd71a..6014f2ce5 100644 --- a/src/src/WebServer/NotificationPage.cpp +++ b/src/src/WebServer/NotificationPage.cpp @@ -152,22 +152,20 @@ void handle_notifications() { { LoadNotificationSettings(x, reinterpret_cast(NotificationSettings.get()), sizeof(NotificationSettingsStruct)); NotificationSettings->validate(); - html_TR_TD(); - html_add_button_prefix(); - addHtml(F("notifications?index=")); - addHtmlInt(x + 1); - addHtml(F("'>Edit")); - html_TD(); - addHtmlInt(x + 1); - html_TD(); + const bool nplugin_set = Settings.Notification[x] != INVALID_N_PLUGIN_ID.value; - if (Settings.Notification[x] != INVALID_N_PLUGIN_ID.value) + uint8_t NotificationProtocolIndex = getNProtocolIndex(npluginID_t::toPluginID(Settings.Notification[x])); + + html_TR_TD(); + + addPlugin_Add_Edit_Button(F("notifications"), x, nplugin_set, validNProtocolIndex(NotificationProtocolIndex)); + + if (nplugin_set) { addEnabled(Settings.NotificationEnabled[x]); html_TD(); - uint8_t NotificationProtocolIndex = getNProtocolIndex(npluginID_t::toPluginID(Settings.Notification[x])); - String NotificationName = F("(plugin not found?)"); + String NotificationName = F("(plugin not found?)"); if (validNProtocolIndex(NotificationProtocolIndex)) { @@ -223,6 +221,10 @@ void handle_notifications() { if (Settings.Notification[notificationindex] != INVALID_N_PLUGIN_ID.value) { + addRowLabel(F("Enabled")); + addCheckBox(F("notificationenabled"), Settings.NotificationEnabled[notificationindex]); + addFormSeparator(2); + MakeNotificationSettings(NotificationSettings); if (!AllocatedNotificationSettings()) { @@ -301,9 +303,6 @@ void handle_notifications() { addPinSelect(PinSelectPurpose::Generic, F("pin1"), NotificationSettings->Pin1); } - addRowLabel(F("Enabled")); - addCheckBox(F("notificationenabled"), Settings.NotificationEnabled[notificationindex]); - TempEvent.NotificationIndex = notificationindex; String webformLoadString; NPlugin_ptr[NotificationProtocolIndex](NPlugin::Function::NPLUGIN_WEBFORM_LOAD, &TempEvent, webformLoadString); diff --git a/src/src/WebServer/PinStates.cpp b/src/src/WebServer/PinStates.cpp index 0a45e2687..a37ad84c9 100644 --- a/src/src/WebServer/PinStates.cpp +++ b/src/src/WebServer/PinStates.cpp @@ -7,6 +7,7 @@ #include "../DataStructs/PinMode.h" #include "../Globals/GlobalMapPortStatus.h" +#include "../Helpers/Hardware_GPIO.h" #include "../Helpers/PortStatus.h" #ifdef ESP32 @@ -24,7 +25,6 @@ #ifdef WEBSERVER_NEW_UI - // ******************************************************************************** // Web Interface pin state list // ******************************************************************************** @@ -140,46 +140,25 @@ void handle_pinstates() { html_table_header(F("Bus Channel")); for (int i = 0; i <= MAX_GPIO; ++i) { - if (!perimanPinIsValid(i)) { - continue; // invalid pin - } - peripheral_bus_type_t type = perimanGetPinBusType(i); + peripheral_bus_type_t bus_type = ESP32_BUS_TYPE_INIT; + String gpio_str, typeName; + int bus_number{}; + int bus_channel{}; - if (type == ESP32_BUS_TYPE_INIT) { - continue; // unused pin - } + if (!getPeriman_gpio_info(i, bus_type, gpio_str, typeName, bus_number, bus_channel)) { continue; } - -# if defined(BOARD_HAS_PIN_REMAP) - int dpin = gpioNumberToDigitalPin(i); - - if (dpin < 0) { - continue; // pin is not exported - } else { - html_TR_TD(); - addHtml(strformat(F("D%-3d|%4u"), dpin, i)); - } -# else // if defined(BOARD_HAS_PIN_REMAP) html_TR_TD(); - addHtml(strformat(F("%4u"), i)); -# endif // if defined(BOARD_HAS_PIN_REMAP) - const char *extra_type = perimanGetPinBusExtraType(i); - html_TD(); + addHtml(gpio_str); - if (extra_type) { - addHtml(strformat(F("%s [%s]"), perimanGetTypeName(type), extra_type)); - } else { - addHtml(strformat(F("%s"), perimanGetTypeName(type))); - } - int8_t bus_number = perimanGetPinBusNum(i); html_TD(); + addHtml(typeName); + html_TD(); if (bus_number != -1) { addHtmlInt(bus_number); } - int8_t bus_channel = perimanGetPinBusChannel(i); - html_TD(); + html_TD(); if (bus_channel != -1) { addHtmlInt(bus_channel); } @@ -248,6 +227,7 @@ void handle_pinstates() { // OutputEn html_TD(); + // When the IO is used as a simple GPIO output, oe signal can only be controlled by the oe register // When the IO is not used as a simple GPIO output, oe signal could be controlled by the peripheral if ((io_config.sig_out != SIG_GPIO_OUT_IDX) && io_config.oe_ctrl_by_periph) { @@ -255,6 +235,7 @@ void handle_pinstates() { } else { addEnabled(io_config.oe); } + if ((io_config.fun_sel == PIN_FUNC_GPIO) && (io_config.oe_inv)) { addHtml(F(" (inversed)")); } diff --git a/src/src/WebServer/PluginListPage.cpp b/src/src/WebServer/PluginListPage.cpp new file mode 100644 index 000000000..4e5197bc7 --- /dev/null +++ b/src/src/WebServer/PluginListPage.cpp @@ -0,0 +1,192 @@ +#include "../WebServer/PluginListPage.h" + + +#if FEATURE_PLUGIN_LIST + +# include "../WebServer/ESPEasy_WebServer.h" +# include "../WebServer/HTML_wrappers.h" + +# include "../_Plugin_Helper.h" +# if FEATURE_NOTIFIER +# include "../Globals/NPlugins.h" +# include "../DataTypes/NPluginID.h" +# endif // if FEATURE_NOTIFIER +# ifdef WEBSERVER_NETWORK +# include "../ESPEasy/net/DataTypes/NetworkDriverIndex.h" +# endif // ifdef WEBSERVER_NETWORK +# include "../DataStructs/ProtocolStruct.h" +# include "../Helpers/_CPlugin_Helper.h" + +void handle_pluginlist() { + # ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("handle_pluginlist")); + # endif // ifndef BUILD_NO_RAM_TRACKER + + # if FEATURE_MQTT_TLS || FEATURE_HTTP_TLS + const int colspan = 6; + # else // if FEATURE_MQTT_TLS || FEATURE_HTTP_TLS + const int colspan = 5; + # endif // if FEATURE_MQTT_TLS || FEATURE_HTTP_TLS + + TXBuffer.startStream(); + sendHeadandTail_stdtemplate(_HEAD); + + // the table header + html_table_class_normal(); + + for (uint8_t p = 0; p < 2; ++p) { // Sorted by Description and by PluginID + addTableSeparator(concat(F("Plugins sorted by "), 0 == p ? F("Description") : F("Plugin ID")), colspan, 3); + html_TR(); + html_table_header(F("Plugin"), 80); + html_table_header(F(""), 25); + html_table_header(F("Description"), 800); + html_table_header(F(""), 50); + # if FEATURE_MQTT_TLS || FEATURE_HTTP_TLS + html_table_header(F(""), 50); + # endif // if FEATURE_MQTT_TLS || FEATURE_HTTP_TLS + html_table_header(F("")); + + deviceIndex_t x; + bool done = false; + + while (!done) { + const deviceIndex_t deviceIndex = (0 == p) ? getDeviceIndex_sorted(x) : x; + + if (!validDeviceIndex(deviceIndex)) { + done = true; + } else { + const pluginID_t pluginID = getPluginID_from_DeviceIndex(deviceIndex); + + if (validPluginID(pluginID)) { + html_TR_TD(); + addHtml(get_formatted_Plugin_number(pluginID)); + html_TD(); + addRTDPluginButton(pluginID); + html_TD(); + addHtml(getPluginNameFromDeviceIndex(deviceIndex)); + html_TD(); + } + } + ++x; + } + } + + { + addTableSeparator(F("Controllers"), colspan, 3); + html_TR(); + html_table_header(F("Controller"), 80); + html_table_header(F(""), 25); + html_table_header(F("Description"), 800); + html_table_header(F("MQTT"), 50); + # if FEATURE_MQTT_TLS || FEATURE_HTTP_TLS + html_table_header(F("TLS"), 50); + # endif // if FEATURE_MQTT_TLS || FEATURE_HTTP_TLS + html_table_header(F("")); + protocolIndex_t x; + bool done = false; + + while (!done) { + if (!validProtocolIndex(x)) { + done = true; + } else { + const cpluginID_t cpluginID = getCPluginID_from_ProtocolIndex(x); + const ProtocolStruct& proto = getProtocolStruct(x); + + if (validCPluginID(cpluginID)) { + html_TR_TD(); + addHtml(get_formatted_Controller_number(cpluginID)); + html_TD(); + # ifndef LIMIT_BUILD_SIZE + addRTDControllerButton(cpluginID); + # endif // ifndef LIMIT_BUILD_SIZE + html_TD(); + addHtml(getCPluginNameFromProtocolIndex(x)); + + html_TD(); + + if (proto.usesMQTT) { + addEnabled(true); + } + # if FEATURE_MQTT_TLS || FEATURE_HTTP_TLS + + html_TD(); + + if (proto.usesTLS) { + addEnabled(true); + } + # endif // if FEATURE_MQTT_TLS || FEATURE_HTTP_TLS + html_TD(); + } + } + ++x; + } + } + + # if FEATURE_NOTIFIER + { + addTableSeparator(F("Notifications"), colspan, 3); + html_TR(); + html_table_header(F("Notifier"), 80); + html_table_header(F(""), 25); + html_table_header(F("Description"), 800); + html_table_header(F(""), 50); + # if FEATURE_MQTT_TLS || FEATURE_HTTP_TLS + html_table_header(F(""), 50); + # endif // if FEATURE_MQTT_TLS || FEATURE_HTTP_TLS + html_table_header(F("")); + + for (uint8_t x = 0; x <= notificationCount; x++) + { + html_TR_TD(); + const npluginID_t plugin(Notification[x].Number); + addHtml(plugin.toDisplayString()); + html_TD(); + addRTDHelpButton(strformat(F("Notify/%s.html"), plugin.toDisplayString().c_str())); + html_TD(); + String NotificationName; + NPlugin_ptr[x](NPlugin::Function::NPLUGIN_GET_DEVICENAME, 0, NotificationName); + addHtml(NotificationName); + html_TD(); + } + } + # endif // if FEATURE_NOTIFIER + + # ifdef WEBSERVER_NETWORK + { + addTableSeparator(F("Networks"), colspan, 3); + html_TR(); + html_table_header(F("Network"), 80); + html_table_header(F(""), 25); + html_table_header(F("Description"), 800); + html_table_header(F(""), 50); + # if FEATURE_MQTT_TLS || FEATURE_HTTP_TLS + html_table_header(F(""), 50); + # endif // if FEATURE_MQTT_TLS || FEATURE_HTTP_TLS + html_table_header(F("")); + + ESPEasy::net::networkDriverIndex_t tmpNetworkDriverIndex{}; + + while (validNetworkDriverIndex(tmpNetworkDriverIndex)) + { + html_TR_TD(); + const ESPEasy::net::nwpluginID_t number = getNWPluginID_from_NetworkDriverIndex(tmpNetworkDriverIndex); + addHtml(number.toDisplayString()); + html_TD(); + # ifndef LIMIT_BUILD_SIZE + addRTDNetworkDriverButton(number); + # endif // ifndef LIMIT_BUILD_SIZE + html_TD(); + addHtml(getNWPluginNameFromNetworkDriverIndex(tmpNetworkDriverIndex)); + html_TD(); + ++tmpNetworkDriverIndex; + } + } + # endif // ifdef WEBSERVER_NETWORK + + html_end_table(); + html_end_form(); + sendHeadandTail_stdtemplate(_TAIL); + TXBuffer.endStream(); +} + +#endif // if FEATURE_PLUGIN_LIST diff --git a/src/src/WebServer/PluginListPage.h b/src/src/WebServer/PluginListPage.h new file mode 100644 index 000000000..888d400da --- /dev/null +++ b/src/src/WebServer/PluginListPage.h @@ -0,0 +1,13 @@ +#pragma once + +#include "../WebServer/common.h" + +#if FEATURE_PLUGIN_LIST + + +// ******************************************************************************** +// Web Interface List all included plugins. +// ******************************************************************************** +void handle_pluginlist(); + +#endif // if FEATURE_PLUGIN_LIST diff --git a/src/src/WebServer/RootPage.cpp b/src/src/WebServer/RootPage.cpp index df0749f29..e919644b6 100644 --- a/src/src/WebServer/RootPage.cpp +++ b/src/src/WebServer/RootPage.cpp @@ -11,11 +11,11 @@ # include "../WebServer/Markup_Forms.h" # include "../Commands/ExecuteCommand.h" -# include "../ESPEasyCore/ESPEasyNetwork.h" +# include "../../ESPEasy/net/ESPEasyNetwork.h" # include "../Globals/ESPEasy_time.h" -# include "../Globals/ESPEasyWiFiEvent.h" +# include "../../ESPEasy/net/Globals/ESPEasyWiFiEvent.h" # include "../Globals/MainLoopCommand.h" -# include "../Globals/NetworkState.h" +# include "../../ESPEasy/net/Globals/NetworkState.h" # include "../Globals/Nodes.h" # include "../Globals/Settings.h" # include "../Globals/Statistics.h" @@ -54,7 +54,6 @@ # define MAIN_PAGE_SHOW_NODE_LIST_TYPE true # endif // ifndef MAIN_PAGE_SHOW_NODE_LIST_TYPE - // ******************************************************************************** // Web Interface root page // ******************************************************************************** @@ -71,8 +70,9 @@ void handle_root() { return; } - // if Wifi setup, launch setup wizard if AP_DONT_FORCE_SETUP is not set. - if (WiFiEventData.wifiSetup && !Settings.ApDontForceSetup()) + // if Wifi setup, launch setup wizard if AP_FORCE_SETUP is set. + if (!ESPEasy::net::NetworkConnected() && + Settings.ApCaptivePortal()) { web_server.send_P(200, (PGM_P)F("text/html"), (PGM_P)F("")); return; @@ -98,8 +98,6 @@ void handle_root() { rebootCmd = strcasecmp_P(sCommand.c_str(), PSTR("reboot")) == 0; sendHeadandTail_stdtemplate(_HEAD, rebootCmd); - int freeMem = ESP.getFreeHeap(); - // TODO: move this to handle_tools, from where it is actually called? // have to disconnect or reboot from within the main loop @@ -107,29 +105,29 @@ void handle_root() { // disconnect here could result into a crash/reboot... if (strcasecmp_P(sCommand.c_str(), PSTR("wifidisconnect")) == 0) { - #ifndef BUILD_MINIMAL_OTA + # ifndef LIMIT_BUILD_SIZE addLog(LOG_LEVEL_INFO, F("WIFI : Disconnecting...")); - #endif + # endif cmd_within_mainloop = CMD_WIFI_DISCONNECT; addHtml(F("OK")); } else if (strcasecmp_P(sCommand.c_str(), PSTR("reboot")) == 0) { - #ifndef BUILD_MINIMAL_OTA + # ifndef LIMIT_BUILD_SIZE addLog(LOG_LEVEL_INFO, F(" : Rebooting...")); - #endif + # endif cmd_within_mainloop = CMD_REBOOT; addHtml(F("OK")); } else if (strcasecmp_P(sCommand.c_str(), PSTR("reset")) == 0) { if (loggedIn) { - #ifndef BUILD_MINIMAL_OTA + # ifndef LIMIT_BUILD_SIZE addLog(LOG_LEVEL_INFO, F(" : factory reset...")); - #endif + # endif cmd_within_mainloop = CMD_REBOOT; addHtml(F( "OK. Please wait > 1 min and connect to Access point.

PW=configesp
URL=192.168.4.1")); TXBuffer.endStream(); - ExecuteCommand_internal({EventValueSource::Enum::VALUE_SOURCE_HTTP, sCommand.c_str()}, true); + ExecuteCommand_internal({ EventValueSource::Enum::VALUE_SOURCE_HTTP, sCommand.c_str() }, true); return; } } else { @@ -143,110 +141,52 @@ void handle_root() { html_table_class_normal(); addFormHeader(F("System Info")); - addRowLabelValue(LabelType::UNIT_NR); - addRowLabelValue(LabelType::GIT_BUILD); - addRowLabel(LabelType::LOCAL_TIME); + static const LabelType::Enum labels[] PROGMEM = + { - if (node_time.systemTimePresent()) - { - addHtml(getValue(LabelType::LOCAL_TIME)); - } - else { - addHtml(F("No system time source")); - } - addRowLabelValue(LabelType::TIME_SOURCE); + LabelType::UNIT_NR, + LabelType::GIT_BUILD, + LabelType::LOCAL_TIME, + LabelType::TIME_SOURCE, - addRowLabel(LabelType::UPTIME); - { - addHtml(getExtendedValue(LabelType::UPTIME)); - } - addRowLabel(LabelType::LOAD_PCT); + LabelType::UPTIME, + LabelType::LOAD_PCT, - if (wdcounter > 0) - { - addHtml(strformat( - F("%.2f [%%] (LC=%d)"), - getCPUload(), - getLoopCountPerSec())); - } - -#if FEATURE_INTERNAL_TEMPERATURE - addRowLabelValue(LabelType::INTERNAL_TEMPERATURE); -#endif - { - addRowLabel(LabelType::FREE_MEM); - addHtmlInt(freeMem); - addUnit(getFormUnit(LabelType::FREE_MEM)); -# ifndef BUILD_NO_RAM_TRACKER - addHtml(strformat( - F(" (%d - %s)"), - lowestRAM, - lowestRAMfunction.c_str())); -# endif // ifndef BUILD_NO_RAM_TRACKER - } - { +# if FEATURE_INTERNAL_TEMPERATURE + LabelType::INTERNAL_TEMPERATURE, +# endif + LabelType::FREE_MEM, # ifdef USE_SECOND_HEAP - addRowLabelValue(LabelType::FREE_HEAP_IRAM); + LabelType::FREE_HEAP_IRAM, # endif // ifdef USE_SECOND_HEAP - } - { - addRowLabel(LabelType::FREE_STACK); - addHtmlInt(getCurrentFreeStack()); - addUnit(getFormUnit(LabelType::FREE_STACK)); -# ifndef BUILD_NO_RAM_TRACKER - addHtml(strformat( - F(" (%d - %s)"), - lowestFreeStack, - lowestFreeStackfunction.c_str())); -# endif // ifndef BUILD_NO_RAM_TRACKER - } + LabelType::FREE_STACK, # if FEATURE_ETHERNET - addRowLabelValue(LabelType::ETH_WIFI_MODE); + LabelType::ETH_WIFI_MODE, # endif // if FEATURE_ETHERNET - if (!WiFiEventData.WiFiDisconnected()) - { - addRowLabelValue(LabelType::IP_ADDRESS); -#if FEATURE_USE_IPV6 - if (Settings.EnableIPv6()) { - addRowLabelValue(LabelType::IP6_LOCAL); - // Do not show global IPv6 on the root page - } -#endif - addRowLabel(LabelType::WIFI_RSSI); - addHtml(strformat( - F("%d [dBm] (%s)"), - WiFi.RSSI(), - WiFi.SSID().c_str())); - } + LabelType::IP_ADDRESS, +# if FEATURE_USE_IPV6 + LabelType::IP6_LOCAL, + + // Do not show global IPv6 on the root page + +# endif // if FEATURE_USE_IPV6 + LabelType::WIFI_RSSI, # if FEATURE_ETHERNET + LabelType::ETH_SPEED_STATE, - if (active_network_medium == NetworkMedium_t::Ethernet) { - addRowLabelValue(LabelType::ETH_SPEED_STATE); - addRowLabelValue(LabelType::ETH_IP_ADDRESS); -#if FEATURE_USE_IPV6 - if (Settings.EnableIPv6()) { - addRowLabelValue(LabelType::ETH_IP6_LOCAL); - // Do not show global IPv6 on the root page - } -#endif - } # endif // if FEATURE_ETHERNET # if FEATURE_MDNS - { - addRowLabel(LabelType::M_DNS); - addHtml(F("")); - addHtml(url); - addHtml(F("")); - } + LabelType::M_DNS, # endif // if FEATURE_MDNS + LabelType::MAX_LABEL + }; + addRowLabelValues(labels); + # if FEATURE_MQTT { if (validControllerIndex(firstEnabledMQTT_ControllerIndex())) { @@ -276,8 +216,9 @@ void handle_root() { addFormHeader(F("Command Argument")); addRowLabel(F("Command")); addHtml(sCommand); - - addHtml(F("Command Output
")); free_string(printWebString); @@ -304,7 +245,7 @@ void handle_root() { html_table_header(F("Type")); } html_table_header(F("IP"), 160); // Should fit "255.255.255.255" - html_table_header(F("Load")); + html_table_header(F("Load (%)")); html_table_header(F("Age (s)")); # ifdef USES_ESPEASY_NOW @@ -352,33 +293,36 @@ void handle_root() { } if (it->second.ip[0] != 0 -#if FEATURE_USE_IPV6 +# if FEATURE_USE_IPV6 || (Settings.EnableIPv6() && - (it->second.hasIPv6_mac_based_link_local || + (it->second.hasIPv6_mac_based_link_local || it->second.hasIPv6_mac_based_link_global) - ) -#endif - ) + ) +# endif // if FEATURE_USE_IPV6 + ) { - IPAddress ip = it->second.IP(); + IPAddress ip = it->second.IP(); const uint16_t port = it->second.webgui_portnumber; -#if FEATURE_USE_IPV6 +# if FEATURE_USE_IPV6 bool isIPv6 = false; + if (Settings.EnableIPv6()) { if (it->second.hasIPv6_mac_based_link_local) { - ip = it->second.IPv6_link_local(true); + ip = it->second.IPv6_link_local(true); isIPv6 = true; } else if (it->second.hasIPv6_mac_based_link_global) { - ip = it->second.IPv6_global(); + ip = it->second.IPv6_global(); isIPv6 = true; } } + if (it->second.hasIPv4 && it->second.hasIPv6()) { // Add 2 buttons for IPv4 and IPv6 address html_add_wide_button_prefix(); addHtml(F("http://")); addHtml(wrap_String(formatIP(ip), '[', ']')); + if ((port != 0) && (port != 80)) { addHtml(':'); addHtmlInt(port); @@ -390,27 +334,27 @@ void handle_root() { // Now prepare 2nd button prefix addHtml(F("
")); html_add_wide_button_prefix(); - ip = it->second.IP(); + ip = it->second.IP(); isIPv6 = false; } else { // Add single wide button html_add_wide_button_prefix(); } -#else +# else // if FEATURE_USE_IPV6 html_add_wide_button_prefix(); -#endif +# endif // if FEATURE_USE_IPV6 addHtml(F("http://")); -#if FEATURE_USE_IPV6 +# if FEATURE_USE_IPV6 if (isIPv6) { addHtml(wrap_String(formatIP(ip), '[', ']')); } else { addHtml(formatIP(ip)); } - #else + # else // if FEATURE_USE_IPV6 addHtml(formatIP(ip)); - #endif - + # endif // if FEATURE_USE_IPV6 + if ((port != 0) && (port != 80)) { addHtml(':'); addHtmlInt(port); @@ -467,7 +411,7 @@ void handle_root() { html_end_form(); free_string(printWebString); - printToWeb = false; + printToWeb = false; sendHeadandTail_stdtemplate(_TAIL); } TXBuffer.endStream(); diff --git a/src/src/WebServer/Rules.cpp b/src/src/WebServer/Rules.cpp index ecd00870e..97b38a906 100644 --- a/src/src/WebServer/Rules.cpp +++ b/src/src/WebServer/Rules.cpp @@ -48,9 +48,11 @@ void handle_rules() { // Make sure file exists if (!fileExists(fileName)) { +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove(LOG_LEVEL_INFO, concat(F("Rules : Create new file: %s"), fileName)); } +#endif fs::File f = tryOpenFile(fileName, "w"); if (f) { f.close(); } @@ -520,7 +522,7 @@ bool handle_rules_edit(String originalUri, bool isAddNew) { addHelpButton(F("Tutorial_Rules")); // load form data from flash - addHtml(F("")); + addRowColspan(2); Rule_showRuleTextArea(fileName); @@ -549,7 +551,7 @@ void Rule_showRuleTextArea(const String& fileName) { size = streamFromFS(fileName, true); addHtml(F("")); #if FEATURE_RULES_EASY_COLOR_CODE - addHtml(F("")); + html_add_script(F("initCM();"), false); #endif html_TR_TD(); @@ -575,7 +577,7 @@ bool Rule_Download(const String& path) filename.replace(RULE_FILE_SEPARAROR, '_'); String str = concat(F("attachment; filename="), filename); sendHeader(F("Content-Disposition"), str); - sendHeader(F("Cache-Control"), F("max-age=3600, public")); + sendHeader(F("Cache-Control"), F("public,max-age=3600")); sendHeader(F("Vary"), "*"); sendHeader(F("ETag"), F("\"2.0.0\"")); diff --git a/src/src/WebServer/SecurityStruct_deviceSpecific_webform.cpp b/src/src/WebServer/SecurityStruct_deviceSpecific_webform.cpp new file mode 100644 index 000000000..ab74b53b2 --- /dev/null +++ b/src/src/WebServer/SecurityStruct_deviceSpecific_webform.cpp @@ -0,0 +1,93 @@ +#include "../WebServer/SecurityStruct_deviceSpecific_webform.h" + +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + +# include "../Globals/SecuritySettings.h" +# include "../Helpers/StringConverter.h" +# include "../WebServer/Markup_Forms.h" + +String DevSpecific_Security_getLabelString(SecurityStruct_deviceSpecific::KeyType keytype, + uint16_t index, + bool displayString, + KVS_StorageType::Enum & storageType, + bool suppressIndex_displayString = false) +{ + storageType = KVS_StorageType::Enum::string_type; + String res = SecurityStruct_deviceSpecific::toString(keytype); + + if ((keytype == SecurityStruct_deviceSpecific::KeyType::WiFi_SSID) || + (keytype == SecurityStruct_deviceSpecific::KeyType::WiFi_Password)) { + if (displayString) { + if (!suppressIndex_displayString) { + res += ' '; + res += index + 1; + } + } else { + res += index; + } + } + + if (!displayString) { + res.toLowerCase(); + res.replace(' ', '_'); + return concat(F("KVS_ID_"), res); + } + return res; +} + +WebFormItemParams make_DevSpecific_Security_WebFormItemParams( + SecurityStruct_deviceSpecific::KeyType keytype, + uint16_t index, + bool suppressIndex_displayString) +{ + KVS_StorageType::Enum storageType; + + auto res = WebFormItemParams( + DevSpecific_Security_getLabelString( + keytype, index, true, storageType, suppressIndex_displayString), + DevSpecific_Security_getLabelString( + keytype, index, false, storageType, suppressIndex_displayString), + storageType, + SecurityStruct_deviceSpecific::createKey(keytype, index)); + + res._password = SecurityStruct_deviceSpecific::isPassword(keytype); + res._maxLength = SecurityStruct_deviceSpecific::maxLength(keytype); + return res; +} + +void show_SecurityStruct_deviceSpecific_WebFormItem( + SecurityStruct_deviceSpecific::KeyType keytype, + uint16_t index, + bool suppressIndex_displayString) +{ + showWebformItem( + *SecuritySettings_deviceSpecific._getKVS(), + make_DevSpecific_Security_WebFormItemParams(keytype, index, suppressIndex_displayString)); +} + +void store_SecurityStruct_deviceSpecific_WebFormItem( + SecurityStruct_deviceSpecific::KeyType keytype, + uint16_t index) +{ + KVS_StorageType::Enum storageType; + + auto id = + DevSpecific_Security_getLabelString(keytype, index, false, storageType); + + if (SecurityStruct_deviceSpecific::isPassword(keytype)) { + String passwd; + + if (!getFormPassword(id, passwd)) { + // Not changed, so do not try to store. + return; + } + } + + storeWebformItem( + *SecuritySettings_deviceSpecific._getKVS(), + SecurityStruct_deviceSpecific::createKey(keytype, index), + storageType, + id); +} + +#endif // if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE diff --git a/src/src/WebServer/SecurityStruct_deviceSpecific_webform.h b/src/src/WebServer/SecurityStruct_deviceSpecific_webform.h new file mode 100644 index 000000000..e8aadd81d --- /dev/null +++ b/src/src/WebServer/SecurityStruct_deviceSpecific_webform.h @@ -0,0 +1,18 @@ +#pragma once + +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + +# include "../WebServer/ESPEasy_key_value_store_webform.h" +# include "../DataStructs/SecurityStruct_deviceSpecific.h" + +void show_SecurityStruct_deviceSpecific_WebFormItem( + SecurityStruct_deviceSpecific::KeyType keytype, + uint16_t index, + bool suppressIndex_displayString = false); + +void store_SecurityStruct_deviceSpecific_WebFormItem( + SecurityStruct_deviceSpecific::KeyType keytype, + uint16_t index); + + +#endif // if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE diff --git a/src/src/WebServer/SettingsArchive.cpp b/src/src/WebServer/SettingsArchive.cpp index c0e2ec9bc..35721ea68 100644 --- a/src/src/WebServer/SettingsArchive.cpp +++ b/src/src/WebServer/SettingsArchive.cpp @@ -257,6 +257,9 @@ void storeDownloadFiletypeCheckbox(FileType::Enum filetype, unsigned int filenr) case FileType::NOTIFICATION_DAT: ResetFactoryDefaultPreference.fetchNotificationDat(isChecked); break; case FileType::RULES_TXT: { ResetFactoryDefaultPreference.fetchRulesTXT(filenr, isChecked); break; } case FileType::PROVISIONING_DAT: { ResetFactoryDefaultPreference.fetchProvisioningDat(isChecked); break; } +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + case FileType::DEV_SECURITY_DAT: { ResetFactoryDefaultPreference.fetchDeviceSecurityDat(isChecked); break; } +#endif case FileType::MAX_FILETYPE: break; diff --git a/src/src/WebServer/SetupPage.cpp b/src/src/WebServer/SetupPage.cpp index 373f98634..219402b85 100644 --- a/src/src/WebServer/SetupPage.cpp +++ b/src/src/WebServer/SetupPage.cpp @@ -11,15 +11,16 @@ # include "../WebServer/Markup_Forms.h" # include "../WebServer/SysInfoPage.h" -# include "../ESPEasyCore/ESPEasyNetwork.h" -# include "../ESPEasyCore/ESPEasyWifi.h" +# include "../../ESPEasy/net/ESPEasyNetwork.h" +# include "../../ESPEasy/net/wifi/ESPEasyWifi.h" -# include "../Globals/ESPEasyWiFiEvent.h" -# include "../Globals/NetworkState.h" + +# include "../../ESPEasy/net/Globals/ESPEasyWiFiEvent.h" +# include "../../ESPEasy/net/Globals/NetworkState.h" # include "../Globals/RTC.h" # include "../Globals/Settings.h" # include "../Globals/SecuritySettings.h" -# include "../Globals/WiFi_AP_Candidates.h" +# include "../../ESPEasy/net/Globals/WiFi_AP_Candidates.h" # include "../Helpers/Misc.h" # include "../Helpers/Networking.h" @@ -27,11 +28,9 @@ # include "../Helpers/StringConverter.h" - -#ifndef SETUP_PAGE_SHOW_CONFIG_BUTTON - #define SETUP_PAGE_SHOW_CONFIG_BUTTON true -#endif - +# ifndef SETUP_PAGE_SHOW_CONFIG_BUTTON + # define SETUP_PAGE_SHOW_CONFIG_BUTTON true +# endif // ******************************************************************************** @@ -49,24 +48,36 @@ void handle_setup() { // Do not check client IP range allowed. TXBuffer.startStream(); - const bool connected = NetworkConnected(); + const bool connected = ESPEasy::net::NetworkConnected(); -// if (connected) { - navMenuIndex = MENU_INDEX_SETUP; - sendHeadandTail_stdtemplate(_HEAD); -/* } else { - sendHeadandTail(F("TmplAP")); - } - */ + // if (connected) { + navMenuIndex = MENU_INDEX_SETUP; + sendHeadandTail_stdtemplate(_HEAD); - const bool clearButtonPressed = hasArg(F("performclearcredentials")); - const bool clearWiFiCredentials = + /* } else { + sendHeadandTail(F("TmplAP")); + } + */ + + const bool clearButtonPressed = hasArg(F("performclearcredentials")); + const bool clearWiFiCredentials = isFormItemChecked(F("clearcredentials")) && clearButtonPressed; { if (clearWiFiCredentials) { +# if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + + if (SecuritySettings.Password[0] == 0) { + // No password set, so also clear the regular credentials + // SecuritySettings.clearWiFiCredentials(); + } + SecuritySettings_deviceSpecific.clearWiFiCredentials(); + +# else // if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE SecuritySettings.clearWiFiCredentials(); +# endif // if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + addHtmlError(SaveSecuritySettings()); html_add_form(); @@ -74,123 +85,142 @@ void handle_setup() { addFormHeader(F("WiFi credentials cleared, reboot now")); html_end_table(); - } else { - // if (active_network_medium == NetworkMedium_t::WIFI) - // { - static uint8_t status = HANDLE_SETUP_SCAN_STAGE; - static uint8_t refreshCount = 0; + } else { + // if (active_network_medium == ESPEasy::net::NetworkMedium_t::WIFI) + // { + static uint8_t status = HANDLE_SETUP_SCAN_STAGE; + static uint8_t refreshCount = 0; - String ssid = webArg(F("ssid")); - String other = webArg(F("other")); - String password; - bool passwordGiven = getFormPassword(F("pass"), password); - if (passwordGiven) { - passwordGiven = !password.isEmpty(); - } - const bool emptyPassAllowed = isFormItemChecked(F("emptypass")); - const bool performRescan = hasArg(F("performrescan")); - if (performRescan) { - WiFiEventData.lastScanMoment.clear(); - WifiScan(false); - } - - if (!other.isEmpty()) - { - ssid = other; - } - - if (!performRescan) { - // if ssid config not set and params are both provided - if ((status == HANDLE_SETUP_SCAN_STAGE) && (!ssid.isEmpty()) /*&& strcasecmp(SecuritySettings.WifiSSID, "ssid") == 0 */) - { - if (clearButtonPressed) { - addHtmlError(F("Warning: Need to confirm to clear WiFi credentials")); - } else if (!passwordGiven && !emptyPassAllowed) { - addHtmlError(F("No password entered")); - } else { - safe_strncpy(SecuritySettings.WifiKey, password.c_str(), sizeof(SecuritySettings.WifiKey)); - safe_strncpy(SecuritySettings.WifiSSID, ssid.c_str(), sizeof(SecuritySettings.WifiSSID)); - // Hidden SSID - Settings.IncludeHiddenSSID(isFormItemChecked(LabelType::CONNECT_HIDDEN_SSID)); - Settings.HiddenSSID_SlowConnectPerBSSID(isFormItemChecked(LabelType::HIDDEN_SSID_SLOW_CONNECT)); -#ifdef ESP32 - Settings.PassiveWiFiScan(isFormItemChecked(LabelType::WIFI_PASSIVE_SCAN)); -#endif - - addHtmlError(SaveSettings()); - WiFiEventData.wifiSetupConnect = true; - WiFiEventData.wifiConnectAttemptNeeded = true; - WiFi_AP_Candidates.force_reload(); // Force reload of the credentials and found APs from the last scan - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String reconnectlog = F("WIFI : Credentials Changed, retry connection. SSID: "); - reconnectlog += ssid; - addLogMove(LOG_LEVEL_INFO, reconnectlog); - } - status = HANDLE_SETUP_CONNECTING_STAGE; - refreshCount = 0; - AttemptWiFiConnect(); - } - } - } - html_BR(); - wrap_html_tag(F("h1"), connected ? F("Connected to a network") : F("Wifi Setup wizard")); - html_add_form(); - - switch (status) { - case HANDLE_SETUP_SCAN_STAGE: - { - // first step, scan and show access points within reach... - handle_setup_scan_and_show(ssid, other, password); - break; - } - case HANDLE_SETUP_CONNECTING_STAGE: - { - if (!handle_setup_connectingStage(refreshCount)) { - status = HANDLE_SETUP_SCAN_STAGE; - } - ++refreshCount; - break; - } - } - /* - } else { - html_add_form(); - addFormHeader(F("Ethernet Setup Complete")); + String ssid = webArg(F("ssid")); + String other = webArg(F("other")); + String password; + bool passwordGiven = getFormPassword(F("pass"), password); + if (passwordGiven) { + passwordGiven = !password.isEmpty(); } - */ + const bool emptyPassAllowed = isFormItemChecked(F("emptypass")); + const bool performRescan = hasArg(F("performrescan")); + + if (performRescan) { + // WiFiEventData.lastScanMoment.clear(); + ESPEasy::net::wifi::WifiScan(false); + } + + if (!other.isEmpty()) + { + ssid = other; + } + + if (!performRescan) { + // if ssid config not set and params are both provided + if ((status == HANDLE_SETUP_SCAN_STAGE) && (!ssid.isEmpty()) /*&& strcasecmp(SecuritySettings.WifiSSID, "ssid") == 0 */) + { + if (clearButtonPressed) { + addHtmlError(F("Warning: Need to confirm to clear WiFi credentials")); + } else if (!passwordGiven && !emptyPassAllowed) { + addHtmlError(F("No password entered")); + } else { + // TODO TD-er: Must store in separate file when system password is set +# if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + + // TODO TD-er: Must check whether password is set: SecuritySettings.Password + SecuritySettings_deviceSpecific.setWiFiCredentials(0, ssid, password); +# else // if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + safe_strncpy(SecuritySettings.WifiKey, password.c_str(), sizeof(SecuritySettings.WifiKey)); + safe_strncpy(SecuritySettings.WifiSSID, ssid.c_str(), sizeof(SecuritySettings.WifiSSID)); +# endif // if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + + // Hidden SSID + Settings.IncludeHiddenSSID(isFormItemChecked(LabelType::CONNECT_HIDDEN_SSID)); + Settings.HiddenSSID_SlowConnectPerBSSID(isFormItemChecked(LabelType::HIDDEN_SSID_SLOW_CONNECT)); +# ifdef ESP32 + Settings.PassiveWiFiScan(isFormItemChecked(LabelType::WIFI_PASSIVE_SCAN)); +# endif + + addHtmlError(SaveSettings()); + + // WiFiEventData.wifiSetupConnect = true; + // WiFiEventData.wifiConnectAttemptNeeded = true; + ESPEasy::net::wifi::WiFi_AP_Candidates.force_reload(); // Force reload of the credentials and found APs from the last scan +# ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String reconnectlog = F("WIFI : Credentials Changed, retry connection. SSID: "); + reconnectlog += ssid; + addLogMove(LOG_LEVEL_INFO, reconnectlog); + } +# endif // ifndef BUILD_NO_DEBUG + status = HANDLE_SETUP_CONNECTING_STAGE; + refreshCount = 0; + String dummy; + ESPEasy::net::NWPluginCall( + NWPlugin::Function::NWPLUGIN_CREDENTIALS_CHANGED, 0, dummy); + } + } + } + html_BR(); + wrap_html_tag(F("h1"), connected ? F("Connected to a network") : F("Wifi Setup wizard")); + html_add_form(); + + switch (status) + { + case HANDLE_SETUP_SCAN_STAGE: + { + // first step, scan and show access points within reach... + handle_setup_scan_and_show(ssid, other, password); + break; + } + case HANDLE_SETUP_CONNECTING_STAGE: + { + if (!handle_setup_connectingStage(refreshCount)) { + status = HANDLE_SETUP_SCAN_STAGE; + } + ++refreshCount; + break; + } + } + + /* + } else { + html_add_form(); + addFormHeader(F("Ethernet Setup Complete")); + + } + */ html_table_class_normal(); html_TR(); -#if defined(WEBSERVER_SYSINFO) && !defined(WEBSERVER_SYSINFO_MINIMAL) +# if defined(WEBSERVER_SYSINFO) && !defined(WEBSERVER_SYSINFO_MINIMAL) handle_sysinfo_NetworkServices(); -#endif +# endif + if (connected) { - //addFormHeader(F("Current network configuration")); + // addFormHeader(F("Current network configuration")); -#ifdef WEBSERVER_SYSINFO +# ifdef WEBSERVER_SYSINFO handle_sysinfo_Network(); -#endif +# endif addFormSeparator(2); html_TR_TD(); html_TD(); - - #if SETUP_PAGE_SHOW_CONFIG_BUTTON - if (!clientIPinSubnet()) { - String host = formatIP(NetworkLocalIP()); + + # if SETUP_PAGE_SHOW_CONFIG_BUTTON + + if (!clientIPinSubnetDefaultNetwork()) { + String host = formatIP(ESPEasy::net::NetworkLocalIP()); String url = F("http://"); url += host; url += F("/config"); addButton(url, host); } - #endif + # endif // if SETUP_PAGE_SHOW_CONFIG_BUTTON - WiFiEventData.wifiSetup = false; - } + // WiFiEventData.wifiSetup = false; + } html_end_table(); html_BR(); @@ -204,14 +234,16 @@ void handle_setup() { html_table_class_normal(); addFormHeader(F("Advanced WiFi settings")); - - addFormCheckBox(LabelType::CONNECT_HIDDEN_SSID, Settings.IncludeHiddenSSID()); - -#ifdef ESP32 - addFormCheckBox(LabelType::WIFI_PASSIVE_SCAN, Settings.PassiveWiFiScan()); -#endif - - addFormCheckBox(LabelType::HIDDEN_SSID_SLOW_CONNECT, Settings.HiddenSSID_SlowConnectPerBSSID()); + { + LabelType::Enum labels[]{ + LabelType::CONNECT_HIDDEN_SSID +# ifdef ESP32 + , LabelType::WIFI_PASSIVE_SCAN +# endif + , LabelType::HIDDEN_SSID_SLOW_CONNECT + }; + addFormCheckBoxes(labels, NR_ELEMENTS(labels)); + } html_BR(); html_BR(); @@ -227,30 +259,34 @@ void handle_setup() { html_end_form(); } -// if (connected) { - sendHeadandTail_stdtemplate(_TAIL); -/* } else { - sendHeadandTail(F("TmplAP"), true); - } -*/ + + // if (connected) { + sendHeadandTail_stdtemplate(_TAIL); + + /* } else { + sendHeadandTail(F("TmplAP"), true); + } + */ TXBuffer.endStream(); delay(10); + if (clearWiFiCredentials) { reboot(IntendedRebootReason_e::RestoreSettings); } } void handle_setup_scan_and_show(const String& ssid, const String& other, const String& password) { - int8_t scanCompleteStatus = WiFi_AP_Candidates.scanComplete(); - const bool needsRescan = + int8_t scanCompleteStatus = ESPEasy::net::wifi::WiFi_AP_Candidates.scanComplete(); + const bool needsRescan = (scanCompleteStatus == 0 || // No AP found scanCompleteStatus == -2) // Scan not triggered - && WiFiScanAllowed(); + && ESPEasy::net::wifi::WiFiScanAllowed(); + if (needsRescan) { WiFiMode_t cur_wifimode = WiFi.getMode(); - WifiScan(false); - scanCompleteStatus = WiFi_AP_Candidates.scanComplete(); - setWifiMode(cur_wifimode); + ESPEasy::net::wifi::WifiScan(false); + scanCompleteStatus = ESPEasy::net::wifi::WiFi_AP_Candidates.scanComplete(); + ESPEasy::net::wifi::setWifiMode(cur_wifimode); } @@ -265,16 +301,17 @@ void handle_setup_scan_and_show(const String& ssid, const String& other, const S html_table_header(F("Network info")); html_table_header(F("RSSI"), 50); - for (auto it = WiFi_AP_Candidates.scanned_begin(); it != WiFi_AP_Candidates.scanned_end(); ++it) + for (auto it = ESPEasy::net::wifi::WiFi_AP_Candidates.scanned_begin(); it != ESPEasy::net::wifi::WiFi_AP_Candidates.scanned_end(); ++it) { html_TR_TD(); const String id = it->toString(""); addHtml(F("